import subprocess def test_rebase_basic(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test basic rebase operation with fast-forward""" assert (tmp_path / "initial.txt").exists() # Create a feature branch checkout_cmd = [git2cpp_path, "checkout", "-b", "feature"] p_checkout = subprocess.run(checkout_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_checkout.returncode == 0 # Create a commit on feature branch file_path = tmp_path / "feature_file.txt" file_path.write_text("feature content") add_cmd = [git2cpp_path, "add", "feature_file.txt"] p_add = subprocess.run(add_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_add.returncode == 0 commit_cmd = [git2cpp_path, "commit", "-m", "feature commit"] p_commit = subprocess.run(commit_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_commit.returncode == 0 # Go back to master and create another commit checkout_master_cmd = [git2cpp_path, "checkout", "main"] p_checkout_master = subprocess.run( checkout_master_cmd, capture_output=True, cwd=tmp_path, text=True ) assert p_checkout_master.returncode == 0 file_path_2 = tmp_path / "master_file.txt" file_path_2.write_text("master content") add_cmd_2 = [git2cpp_path, "add", "master_file.txt"] p_add_2 = subprocess.run(add_cmd_2, capture_output=True, cwd=tmp_path, text=True) assert p_add_2.returncode == 0 commit_cmd_2 = [git2cpp_path, "commit", "-m", "master commit"] p_commit_2 = subprocess.run(commit_cmd_2, capture_output=True, cwd=tmp_path, text=True) assert p_commit_2.returncode == 0 # Switch to feature and rebase onto master checkout_feature_cmd = [git2cpp_path, "checkout", "feature"] p_checkout_feature = subprocess.run( checkout_feature_cmd, capture_output=True, cwd=tmp_path, text=True ) assert p_checkout_feature.returncode == 0 rebase_cmd = [git2cpp_path, "rebase", "main"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode == 0 assert "Successfully rebased" in p_rebase.stdout assert "Rebasing 1 commit(s)" in p_rebase.stdout # Verify both files exist assert (tmp_path / "feature_file.txt").exists() assert (tmp_path / "master_file.txt").exists() def test_rebase_multiple_commits(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase with multiple commits""" assert (tmp_path / "initial.txt").exists() # Create feature branch with multiple commits checkout_cmd = [git2cpp_path, "checkout", "-b", "feature"] p_checkout = subprocess.run(checkout_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_checkout.returncode == 0 # First commit file_1 = tmp_path / "file_1.txt" file_1.write_text("content 1") add_cmd_1 = [git2cpp_path, "add", "file_1.txt"] subprocess.run(add_cmd_1, cwd=tmp_path, text=True) commit_cmd_1 = [git2cpp_path, "commit", "-m", "commit 1"] subprocess.run(commit_cmd_1, cwd=tmp_path, text=True) # Second commit file_2 = tmp_path / "file_2.txt" file_2.write_text("content 2") add_cmd_2 = [git2cpp_path, "add", "file_2.txt"] subprocess.run(add_cmd_2, cwd=tmp_path, text=True) commit_cmd_2 = [git2cpp_path, "commit", "-m", "commit 2"] subprocess.run(commit_cmd_2, cwd=tmp_path, text=True) # Third commit file_3 = tmp_path / "file_3.txt" file_3.write_text("content 3") add_cmd_3 = [git2cpp_path, "add", "file_3.txt"] subprocess.run(add_cmd_3, cwd=tmp_path, text=True) commit_cmd_3 = [git2cpp_path, "commit", "-m", "commit 3"] subprocess.run(commit_cmd_3, cwd=tmp_path, text=True) # Go to master and add a commit checkout_master_cmd = [git2cpp_path, "checkout", "main"] subprocess.run(checkout_master_cmd, cwd=tmp_path) master_file = tmp_path / "master_file.txt" master_file.write_text("master") subprocess.run([git2cpp_path, "add", "master_file.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Rebase feature onto master checkout_feature_cmd = [git2cpp_path, "checkout", "feature"] subprocess.run(checkout_feature_cmd, cwd=tmp_path) rebase_cmd = [git2cpp_path, "rebase", "main"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode == 0 assert "Rebasing 3 commit(s)" in p_rebase.stdout assert "Successfully rebased" in p_rebase.stdout # Verify all files exist assert (tmp_path / "file_1.txt").exists() assert (tmp_path / "file_2.txt").exists() assert (tmp_path / "file_3.txt").exists() assert (tmp_path / "master_file.txt").exists() def test_rebase_with_conflicts(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase with conflicts""" assert (tmp_path / "initial.txt").exists() # Create feature branch checkout_cmd = [git2cpp_path, "checkout", "-b", "feature"] subprocess.run(checkout_cmd, capture_output=True, cwd=tmp_path, text=True) # Create conflicting file on feature conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature commit"], cwd=tmp_path) # Go to master and create conflicting commit checkout_master_cmd = [git2cpp_path, "checkout", "main"] subprocess.run(checkout_master_cmd, cwd=tmp_path) conflict_file.write_text("master content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Try to rebase feature onto master checkout_feature_cmd = [git2cpp_path, "checkout", "feature"] subprocess.run(checkout_feature_cmd, cwd=tmp_path) rebase_cmd = [git2cpp_path, "rebase", "main"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode == 0 assert "Conflicts detected" in p_rebase.stdout assert "rebase --continue" in p_rebase.stdout assert "rebase --skip" in p_rebase.stdout assert "rebase --abort" in p_rebase.stdout def test_rebase_abort(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase abort after conflicts""" assert (tmp_path / "initial.txt").exists() # Create feature branch checkout_cmd = [git2cpp_path, "checkout", "-b", "feature"] subprocess.run(checkout_cmd, cwd=tmp_path) # Create conflicting file on feature conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature commit"], cwd=tmp_path) # Go to master and create conflicting commit subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("master content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Rebase and get conflict subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Abort the rebase abort_cmd = [git2cpp_path, "rebase", "--abort"] p_abort = subprocess.run(abort_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_abort.returncode == 0 assert "Rebase aborted" in p_abort.stdout # Verify we're back to original state assert conflict_file.read_text() == "feature content" def test_rebase_continue(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase continue after resolving conflicts""" assert (tmp_path / "initial.txt").exists() # Create feature branch subprocess.run([git2cpp_path, "checkout", "-b", "feature"], cwd=tmp_path) # Create conflicting file on feature conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature commit"], cwd=tmp_path) # Go to master and create conflicting commit subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("master content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Rebase and get conflict subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Resolve conflict conflict_file.write_text("resolved content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) # Continue rebase continue_cmd = [git2cpp_path, "rebase", "--continue"] p_continue = subprocess.run(continue_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_continue.returncode == 0 assert "Successfully rebased" in p_continue.stdout # Verify resolution assert conflict_file.read_text() == "resolved content" def test_rebase_skip(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase skip to skip current commit""" assert (tmp_path / "initial.txt").exists() # Create feature branch subprocess.run([git2cpp_path, "checkout", "-b", "feature"], cwd=tmp_path) # Create conflicting file on feature conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature commit"], cwd=tmp_path) # Go to master and create conflicting commit subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("master content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Rebase and get conflict subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Skip the conflicting commit skip_cmd = [git2cpp_path, "rebase", "--skip"] p_skip = subprocess.run(skip_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_skip.returncode == 0 assert "Skipping" in p_skip.stdout def test_rebase_quit(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase quit to cleanup state without resetting HEAD""" assert (tmp_path / "initial.txt").exists() # Create feature branch subprocess.run([git2cpp_path, "checkout", "-b", "feature"], cwd=tmp_path) # Create conflicting file conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature commit"], cwd=tmp_path) # Create conflict on master subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("master content") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "master commit"], cwd=tmp_path) # Start rebase subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Quit rebase quit_cmd = [git2cpp_path, "rebase", "--quit"] p_quit = subprocess.run(quit_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_quit.returncode == 0 assert "Rebase state cleaned up" in p_quit.stdout assert "HEAD not reset" in p_quit.stdout def test_rebase_onto(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test rebase with --onto option""" assert (tmp_path / "initial.txt").exists() # Create first branch subprocess.run([git2cpp_path, "checkout", "-b", "branch1"], cwd=tmp_path) file1 = tmp_path / "file1.txt" file1.write_text("branch1") subprocess.run([git2cpp_path, "add", "file1.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "branch1 commit"], cwd=tmp_path) # Create second branch from branch1 subprocess.run([git2cpp_path, "checkout", "-b", "branch2"], cwd=tmp_path) file2 = tmp_path / "file2.txt" file2.write_text("branch2") subprocess.run([git2cpp_path, "add", "file2.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "branch2 commit"], cwd=tmp_path) # Create target branch from master subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) subprocess.run([git2cpp_path, "checkout", "-b", "target"], cwd=tmp_path) target_file = tmp_path / "target.txt" target_file.write_text("target") subprocess.run([git2cpp_path, "add", "target.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "target commit"], cwd=tmp_path) # Rebase branch2 onto target, upstream is branch1 rebase_cmd = [git2cpp_path, "rebase", "branch1", "branch2", "--onto", "target"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode == 0 # Verify target file exists and branch2 file exists, but not branch1 file assert not (tmp_path / "target.txt").exists() assert (tmp_path / "file2.txt").exists() def test_rebase_no_upstream_error(repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path): """Test that rebase without upstream argument fails""" assert (tmp_path / "initial.txt").exists() rebase_cmd = [git2cpp_path, "rebase"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode != 0 assert "upstream is required for rebase" in p_rebase.stderr def test_rebase_invalid_upstream_error( repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path ): """Test that rebase with invalid upstream fails""" assert (tmp_path / "initial.txt").exists() rebase_cmd = [git2cpp_path, "rebase", "nonexistent-branch"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode != 0 assert ( "could not resolve upstream" in p_rebase.stderr or "could not resolve upstream" in p_rebase.stdout ) def test_rebase_already_in_progress_error( repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path ): """Test that starting rebase when one is in progress fails""" assert (tmp_path / "initial.txt").exists() # Create feature branch with conflict subprocess.run([git2cpp_path, "checkout", "-b", "feature"], cwd=tmp_path) conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature"], cwd=tmp_path) # Create conflict on master subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("main") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "main"], cwd=tmp_path) # Start rebase with conflict subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Try to start another rebase rebase_cmd = [git2cpp_path, "rebase", "main"] p_rebase = subprocess.run(rebase_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_rebase.returncode != 0 assert ( "rebase is already in progress" in p_rebase.stderr or "rebase is already in progress" in p_rebase.stdout ) def test_rebase_continue_without_rebase_error( repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path ): """Test that --continue without rebase in progress fails""" assert (tmp_path / "initial.txt").exists() continue_cmd = [git2cpp_path, "rebase", "--continue"] p_continue = subprocess.run(continue_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_continue.returncode != 0 assert ( "No rebase in progress" in p_continue.stderr or "No rebase in progress" in p_continue.stdout ) def test_rebase_continue_with_unresolved_conflicts( repo_init_with_commit, commit_env_config, git2cpp_path, tmp_path ): """Test that --continue with unresolved conflicts fails""" assert (tmp_path / "initial.txt").exists() # Create conflict scenario subprocess.run([git2cpp_path, "checkout", "-b", "feature"], cwd=tmp_path) conflict_file = tmp_path / "conflict.txt" conflict_file.write_text("feature") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "checkout", "main"], cwd=tmp_path) conflict_file.write_text("main") subprocess.run([git2cpp_path, "add", "conflict.txt"], cwd=tmp_path) subprocess.run([git2cpp_path, "commit", "-m", "main"], cwd=tmp_path) # Start rebase subprocess.run([git2cpp_path, "checkout", "feature"], cwd=tmp_path) subprocess.run([git2cpp_path, "rebase", "main"], cwd=tmp_path) # Try to continue without resolving continue_cmd = [git2cpp_path, "rebase", "--continue"] p_continue = subprocess.run(continue_cmd, capture_output=True, cwd=tmp_path, text=True) assert p_continue.returncode != 0 assert "resolve conflicts" in p_continue.stderr or "resolve conflicts" in p_continue.stdout