File: //usr/local/bin/apply_patch
#!/usr/bin/env python3
"""Small apply_patch-compatible helper for Codex remote shells.
Supports the patch grammar used by Codex for manual edits:
*** Begin Patch
*** Add File: path
*** Delete File: path
*** Update File: path
*** Move to: new_path
@@ optional context
+/-/space lines
*** End Patch
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
class PatchError(Exception):
pass
def read_patch() -> list[str]:
data = sys.stdin.read()
if not data:
raise PatchError("empty patch on stdin")
# Preserve empty patch lines while stripping only line terminators.
return data.splitlines()
def parse_add(lines: list[str], i: int, filename: str) -> tuple[int, str]:
out: list[str] = []
while i < len(lines):
line = lines[i]
if line.startswith("*** "):
break
if not line.startswith("+"):
raise PatchError(f"Add File {filename}: expected '+' line, got: {line!r}")
out.append(line[1:])
i += 1
return i, "\n".join(out) + ("\n" if out else "")
def parse_change(lines: list[str], i: int) -> tuple[int, list[tuple[str, str]]]:
changes: list[tuple[str, str]] = []
while i < len(lines):
line = lines[i]
if line.startswith("*** "):
break
if line == "@@" or line.startswith("@@ "):
i += 1
continue
if line == "*** End of File":
changes.append(("eof", ""))
i += 1
continue
if not line:
raise PatchError("change line missing prefix")
op = line[0]
if op not in " +-":
raise PatchError(f"unknown change prefix {op!r} in line: {line!r}")
changes.append((op, line[1:]))
i += 1
return i, changes
def find_sequence(haystack: list[str], needle: list[str], start: int) -> int:
if not needle:
return start
limit = len(haystack) - len(needle)
for pos in range(start, limit + 1):
if haystack[pos : pos + len(needle)] == needle:
return pos
return -1
def apply_changes(path: Path, changes: list[tuple[str, str]]) -> None:
original_text = path.read_text(encoding="utf-8") if path.exists() else ""
had_final_newline = original_text.endswith("\n")
original = original_text.splitlines()
result: list[str] = []
cursor = 0
idx = 0
while idx < len(changes):
if changes[idx][0] == "eof":
idx += 1
continue
# A hunk is a run until an eof marker. @@ markers are discarded earlier.
hunk: list[tuple[str, str]] = []
while idx < len(changes) and changes[idx][0] != "eof":
hunk.append(changes[idx])
idx += 1
if idx < len(changes) and changes[idx][0] == "eof":
idx += 1
match_lines = [text for op, text in hunk if op in (" ", "-")]
pos = find_sequence(original, match_lines, cursor)
if pos < 0:
preview = "\n".join(match_lines[:8])
raise PatchError(f"context not found in {path}:\n{preview}")
result.extend(original[cursor:pos])
scan = pos
for op, text in hunk:
if op == " ":
if scan >= len(original) or original[scan] != text:
raise PatchError(f"context mismatch in {path}: {text!r}")
result.append(text)
scan += 1
elif op == "-":
if scan >= len(original) or original[scan] != text:
raise PatchError(f"delete mismatch in {path}: {text!r}")
scan += 1
elif op == "+":
result.append(text)
cursor = scan
result.extend(original[cursor:])
output = "\n".join(result)
if result or had_final_newline:
output += "\n"
path.write_text(output, encoding="utf-8")
def main() -> int:
try:
lines = read_patch()
if not lines or lines[0] != "*** Begin Patch":
raise PatchError("patch must start with '*** Begin Patch'")
if lines[-1] != "*** End Patch":
raise PatchError("patch must end with '*** End Patch'")
i = 1
while i < len(lines) - 1:
line = lines[i]
if line.startswith("*** Add File: "):
filename = line.removeprefix("*** Add File: ")
path = Path(filename)
if path.exists():
raise PatchError(f"Add File target already exists: {filename}")
i, content = parse_add(lines, i + 1, filename)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
print(f"added {filename}")
continue
if line.startswith("*** Delete File: "):
filename = line.removeprefix("*** Delete File: ")
path = Path(filename)
if not path.exists():
raise PatchError(f"Delete File target missing: {filename}")
path.unlink()
print(f"deleted {filename}")
i += 1
continue
if line.startswith("*** Update File: "):
filename = line.removeprefix("*** Update File: ")
path = Path(filename)
if not path.exists():
raise PatchError(f"Update File target missing: {filename}")
i += 1
move_to: str | None = None
if i < len(lines) and lines[i].startswith("*** Move to: "):
move_to = lines[i].removeprefix("*** Move to: ")
i += 1
i, changes = parse_change(lines, i)
if changes:
apply_changes(path, changes)
print(f"updated {filename}")
if move_to:
dest = Path(move_to)
dest.parent.mkdir(parents=True, exist_ok=True)
os.replace(path, dest)
print(f"moved {filename} -> {move_to}")
continue
raise PatchError(f"unknown patch header: {line!r}")
return 0
except PatchError as exc:
print(f"apply_patch: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())