diff --git a/automation/patcher.py b/automation/patcher.py index 2264182..a1f415f 100644 --- a/automation/patcher.py +++ b/automation/patcher.py @@ -596,49 +596,36 @@ def apply_patch(repo_root: Path, patch_file: Path, patch_level: str, output_rel: """ absolute_patch = patch_file.resolve() - # Check if the output file is currently staged - # If it is, unstage it so the patch applies to the working directory version - was_staged = False - check_staged = run( - ["git", "diff", "--cached", "--name-only", "--", output_rel.as_posix()], - cwd=repo_root, - check=False - ) - if check_staged.returncode == 0 and check_staged.stdout.strip(): - # File is staged, unstage it temporarily - was_staged = True - run(["git", "reset", "HEAD", "--", output_rel.as_posix()], cwd=repo_root, check=False) + # Note: Patches are generated from staged content (git diff --cached). + # The --index flag applies patches to both working tree and index, which is + # what we want. Do NOT unstage files before applying - that changes the base + # state and causes patch application to fail. - try: - # First, try a dry-run check with --check to see if the patch applies cleanly. - check_args = ["git", "apply", patch_level, "--index", "--check", absolute_patch.as_posix()] - if run(check_args, cwd=repo_root, check=False).returncode == 0: - # If it applies cleanly, perform the actual apply. - run(["git", "apply", patch_level, "--index", absolute_patch.as_posix()], cwd=repo_root) + # First, try a dry-run check with --check to see if the patch applies cleanly. + check_args = ["git", "apply", patch_level, "--index", "--check", absolute_patch.as_posix()] + if run(check_args, cwd=repo_root, check=False).returncode == 0: + # If it applies cleanly, perform the actual apply. + run(["git", "apply", patch_level, "--index", absolute_patch.as_posix()], cwd=repo_root) + return + + # If direct apply --check fails, try 3-way merge with a check first. + three_way_check_args = ["git", "apply", patch_level, "--index", "--3way", "--recount", "--whitespace=nowarn", absolute_patch.as_posix()] + if run(three_way_check_args + ["--check"], cwd=repo_root, check=False).returncode == 0: + # If 3-way check passes, perform the actual 3-way apply. + run(three_way_check_args, cwd=repo_root) + return + + # Special handling for new files: if the patch creates a new file ('--- /dev/null'), + # a simple 'git apply' might work, followed by 'git add'. + text = patch_file.read_text(encoding="utf-8") + if "--- /dev/null" in text: + # Try applying without --index and then explicitly add the file. + if run(["git", "apply", patch_level, absolute_patch.as_posix()], cwd=repo_root, check=False).returncode == 0: + run(["git", "add", "--", output_rel.as_posix()], cwd=repo_root) return - # If direct apply --check fails, try 3-way merge with a check first. - three_way_check_args = ["git", "apply", patch_level, "--index", "--3way", "--recount", "--whitespace=nowarn", absolute_patch.as_posix()] - if run(three_way_check_args + ["--check"], cwd=repo_root, check=False).returncode == 0: - # If 3-way check passes, perform the actual 3-way apply. - run(three_way_check_args, cwd=repo_root) - return - - # Special handling for new files: if the patch creates a new file ('--- /dev/null'), - # a simple 'git apply' might work, followed by 'git add'. - text = patch_file.read_text(encoding="utf-8") - if "--- /dev/null" in text: - # Try applying without --index and then explicitly add the file. - if run(["git", "apply", patch_level, absolute_patch.as_posix()], cwd=repo_root, check=False).returncode == 0: - run(["git", "add", "--", output_rel.as_posix()], cwd=repo_root) - return - - # If all attempts fail, raise an error. - raise PatchGenerationError("Failed to apply patch (strict and 3-way both failed)") - finally: - # If the file was staged before, re-stage it now (whether patch succeeded or failed) - if was_staged: - run(["git", "add", "--", output_rel.as_posix()], cwd=repo_root, check=False) + # If all attempts fail, raise an error. + raise PatchGenerationError("Failed to apply patch (strict and 3-way both failed)") def run(args: list[str], cwd: Path, check: bool = True) -> subprocess.CompletedProcess[str]: