mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 18:18:36 +00:00
fix apply patch when user has no git
This commit is contained in:
parent
ec9181eb64
commit
809ec92e0e
|
|
@ -177,6 +177,7 @@ pipeline:
|
||||||
type: patch_apply
|
type: patch_apply
|
||||||
mode: apply
|
mode: apply
|
||||||
output: patch-apply-output.txt
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
- id: test
|
- id: test
|
||||||
type: command
|
type: command
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ pipeline:
|
||||||
type: patch_apply
|
type: patch_apply
|
||||||
mode: apply
|
mode: apply
|
||||||
output: patch-apply-output.txt
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
- id: test
|
- id: test
|
||||||
type: command
|
type: command
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ In `nightshift.yaml`:
|
||||||
type: patch_apply
|
type: patch_apply
|
||||||
mode: dry_run
|
mode: dry_run
|
||||||
output: patch-apply-output.txt
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
```
|
```
|
||||||
|
|
||||||
Run one task:
|
Run one task:
|
||||||
|
|
@ -199,6 +200,7 @@ If the dry run looks good, switch to apply mode:
|
||||||
type: patch_apply
|
type: patch_apply
|
||||||
mode: apply
|
mode: apply
|
||||||
output: patch-apply-output.txt
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
```
|
```
|
||||||
|
|
||||||
Run again:
|
Run again:
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ pipeline:
|
||||||
type: patch_apply
|
type: patch_apply
|
||||||
mode: apply
|
mode: apply
|
||||||
output: patch-apply-output.txt
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
- id: test
|
- id: test
|
||||||
type: command
|
type: command
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,8 @@ def validate_patch(
|
||||||
|
|
||||||
for path_text in files:
|
for path_text in files:
|
||||||
_validate_patch_path(path_text, root, scoped_roots, forbidden_paths)
|
_validate_patch_path(path_text, root, scoped_roots, forbidden_paths)
|
||||||
|
_validate_hunk_lines(patch)
|
||||||
|
_validate_file_states(patch, root)
|
||||||
return PatchValidationResult(files=tuple(sorted(files)), changed_lines=changed_lines)
|
return PatchValidationResult(files=tuple(sorted(files)), changed_lines=changed_lines)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -174,6 +176,57 @@ def _patch_files(patch: str) -> set[str]:
|
||||||
return {path for path in files if path}
|
return {path for path in files if path}
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_hunk_lines(patch: str) -> None:
|
||||||
|
in_hunk = False
|
||||||
|
for line_number, line in enumerate(patch.splitlines(), start=1):
|
||||||
|
if line.startswith("diff --git "):
|
||||||
|
in_hunk = False
|
||||||
|
continue
|
||||||
|
if line.startswith("@@"):
|
||||||
|
in_hunk = True
|
||||||
|
continue
|
||||||
|
if not in_hunk:
|
||||||
|
continue
|
||||||
|
if line.startswith(("+", "-", " ", "\\")):
|
||||||
|
continue
|
||||||
|
raise PipelineError(
|
||||||
|
"Patch validation failed: malformed hunk line "
|
||||||
|
f"{line_number}; expected ' ', '+', '-', or '\\'."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_file_states(patch: str, root: Path) -> None:
|
||||||
|
current_path: str | None = None
|
||||||
|
current_is_new = False
|
||||||
|
current_is_deleted = False
|
||||||
|
|
||||||
|
def flush() -> None:
|
||||||
|
if not current_path:
|
||||||
|
return
|
||||||
|
target = root / current_path
|
||||||
|
if current_is_new and target.exists():
|
||||||
|
raise PipelineError(
|
||||||
|
f"Patch validation failed: patch creates existing file `{current_path}`."
|
||||||
|
)
|
||||||
|
if current_is_deleted and not target.exists():
|
||||||
|
raise PipelineError(
|
||||||
|
f"Patch validation failed: patch deletes missing file `{current_path}`."
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in patch.splitlines():
|
||||||
|
if line.startswith("diff --git "):
|
||||||
|
flush()
|
||||||
|
parts = line.split()
|
||||||
|
current_path = _strip_prefix(parts[3]) if len(parts) >= 4 else None
|
||||||
|
current_is_new = False
|
||||||
|
current_is_deleted = False
|
||||||
|
elif line.startswith("new file mode "):
|
||||||
|
current_is_new = True
|
||||||
|
elif line.startswith("deleted file mode "):
|
||||||
|
current_is_deleted = True
|
||||||
|
flush()
|
||||||
|
|
||||||
|
|
||||||
def _changed_line_count(patch: str) -> int:
|
def _changed_line_count(patch: str) -> int:
|
||||||
count = 0
|
count = 0
|
||||||
for line in patch.splitlines():
|
for line in patch.splitlines():
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,50 @@ class PatchTests(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(PipelineError, "forbidden path"):
|
with self.assertRaisesRegex(PipelineError, "forbidden path"):
|
||||||
validate_patch(patch, root, safety)
|
validate_patch(patch, root, safety)
|
||||||
|
|
||||||
|
def test_validate_patch_rejects_malformed_hunk_line(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
(root / "src").mkdir()
|
||||||
|
safety = SafetyConfig(
|
||||||
|
require_clean_worktree=False,
|
||||||
|
scoped_paths=("src",),
|
||||||
|
allowed_commands=(),
|
||||||
|
forbidden_commands=(),
|
||||||
|
)
|
||||||
|
patch = """diff --git a/src/app.py b/src/app.py
|
||||||
|
--- a/src/app.py
|
||||||
|
+++ b/src/app.py
|
||||||
|
@@ -1 +1,2 @@
|
||||||
|
-old
|
||||||
|
+new
|
||||||
|
bare line
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(PipelineError, "malformed hunk line"):
|
||||||
|
validate_patch(patch, root, safety)
|
||||||
|
|
||||||
|
def test_validate_patch_rejects_new_file_when_target_exists(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
(root / "src").mkdir()
|
||||||
|
(root / "src" / "app.py").write_text("old\n", encoding="utf-8")
|
||||||
|
safety = SafetyConfig(
|
||||||
|
require_clean_worktree=False,
|
||||||
|
scoped_paths=("src",),
|
||||||
|
allowed_commands=(),
|
||||||
|
forbidden_commands=(),
|
||||||
|
)
|
||||||
|
patch = """diff --git a/src/app.py b/src/app.py
|
||||||
|
new file mode 100644
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/app.py
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+new
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(PipelineError, "creates existing file"):
|
||||||
|
validate_patch(patch, root, safety)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user