Skip to content

fix(work): read copy headers in the attempt diff canvas - #289

Merged
Lucas1479 merged 2 commits into
Code-Amadeus:mainfrom
NopeCharon:fix/unified-diff-copy-headers
Nov 4, 2026
Merged

Lucas1479 merged 2 commits into
Code-Amadeus:mainfrom
NopeCharon:fix/unified-diff-copy-headers

Conversation

@NopeCharon

Copy link
Copy Markdown
Contributor

What and why

With diff.renames=copies, git diff reports a new file copied from a changed file with copy from / copy to extended headers. parse_unified_diff handled the equivalent rename headers but ignored these (details in #286):

  • A copy was shown as modified.
  • An exact copy has no --- / +++ lines. When its source and target paths contain spaces, the diff --git header cannot be split, so the canvas showed a phantom entry (src.txt with oldPath my), plus the real path again as a hunk-less entry from changed_files.

The copy headers are now read like the rename headers: status copied, oldPath from copy from, and path / newPath from copy to. The canvas displays the status as text, and no consumer branches on its value.

Fixes #286

Linked Issue for product-semantic or public-contract changes: none (routine fix)

Change class

  • Routine fix, documentation, test, maintenance, or presentation-only UI
  • Product-semantic or public-contract change discussed in the linked Issue
  • Isolated, default-off experiment

Owning layer: server/unified_diff.py, the parser that turns git diff output into the provider-neutral diff canvas shape.

User-visible effect, or none: for users with diff.renames=copies, copied files appear once in the attempt diff canvas as copied with their real paths.

Compatibility or migration impact, or none: none. copied is a new value of the existing free-text status field; the output shape is unchanged.

Evidence

Commands and manual journeys run:

  • python -m pytest -q tests/test_local_git_provider_diff.py tests/test_work_artifact_registry.py tests/test_unified_diff_canvas.py tests/test_work_export_integration.py: 63 passed (Linux, Python 3.12, uv sync --locked --extra dev, Git 2.43).

  • The new tests test_parse_unified_diff_reads_copy_headers (exact and edited copies) and test_attempt_diff_lists_committed_copies_once_with_copy_detection (committed copy in an attempt finalized with diff.renames=copies) fail on main and pass with this change.

  • python tests/test_unified_diff_canvas.py and python tests/test_work_artifact_registry.py: passed.

  • ruff check server tests/test_unified_diff_canvas.py tests/test_work_artifact_registry.py and git diff --check: passed.

  • pytest tests -k "diff or canvas or artifact": 183 passed, 18 skipped. The 6 failures are test_auip_artifact_style.py::test_real_entry_css_delivery_and_advisory_boundaries, which fails the same way on unmodified main in this environment.

  • Parsed real git -c diff.renames=copies diff HEAD output (Git 2.54) with an exact copy my src.txt -> my dup.txt. Before: src.txt, my src.txt, my dup.txt (all modified). After: my dup.txt (copied from my src.txt) and my src.txt.

  • Relevant Python tests pass

  • CPU/model-less baseline remains supported

  • Electron npm run build passes when Electron code changed

  • Dependency audit passes when dependencies changed

  • Before/after screenshots are attached for visible UI changes

  • Documentation/examples are updated for changed settings or contracts

Final check

  • This PR addresses one coherent problem without unrelated cleanup
  • It does not add a speculative API, fallback, or compatibility path
  • No secrets, local state, model weights, voice material, or restricted assets are included
  • Third-party notices and provenance are preserved

With diff.renames=copies, git diff reports a new file copied from a
changed file with "copy from" / "copy to" extended headers. The parser
handled the equivalent rename headers but ignored these, so a copy was
shown as "modified". An exact copy has no ---/+++ lines; when its source
and target paths contain spaces the diff --git header cannot be split,
so the canvas showed a phantom entry plus the real path again as a
hunk-less entry from changed_files.

Read the copy headers like the rename headers: the entry gets status
"copied", oldPath from "copy from", and path/newPath from "copy to".
The status is displayed as text by the canvas; no consumer branches on
its value.

Tests cover exact and edited copies in the parser and a committed copy
in an attempt finalized with diff.renames=copies; both fail without the
fix.

Fixes Code-Amadeus#286
@NopeCharon
NopeCharon requested a review from Lucas1479 as a code owner November 4, 2026 08:21
# Conflicts:
#	tests/test_work_artifact_registry.py

@Lucas1479 Lucas1479 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the focused copy-header fix and regression coverage. I verified that copied entries use the existing free-text status field and render without a new consumer contract. The branch is now updated with #290; both sides of the test conflict were preserved and their test bodies were checked unchanged. All 65 relevant Windows tests, Ruff and diff checks pass on this head. Existing C-quoted path decoding is a separate non-blocking follow-up; no duplicate rename-only restriction is needed. Merge will wait for hosted CI on the updated head.

@Lucas1479
Lucas1479 merged commit d24d869 into Code-Amadeus:main Nov 4, 2026
15 checks passed
Lucas1479 added a commit that referenced this pull request Nov 4, 2026
## What and why

Fixes #285. With `diff.suppressBlankEmpty=true`, Git emits blank context
as empty patch lines. The parser previously discarded these lines,
omitting context and shifting later line numbers.

The parser now accepts empty lines while the hunk still expects content.
Host patch assembly also removes only one final LF from each patch
segment, rather than stripping every trailing LF. This preserves blank
context at the end of committed and working-tree patches, including when
both are combined. Empty separators outside completed hunks remain
ignored.

Owning layers: `server/unified_diff.py` and `collect_git_delta` in
`server/work_artifact_registry.py`. The output schema, configuration,
permissions and attribution rules are unchanged.

## Change class

- [x] Routine bug fix and regression coverage
- [ ] Product-semantic or public-contract change
- [ ] Isolated experiment

## Evidence

- The original parser and real-repository regressions fail on unmodified
main and pass with the parser fix.
- Six additional end-to-end cases cover one/two trailing blank context
lines in working-only, committed-only and combined patches. All six fail
before the assembly fix and pass afterward.
- `python -X utf8 -m pytest tests/test_local_git_provider_diff.py
tests/test_work_artifact_registry.py tests/test_unified_diff_canvas.py
tests/test_work_export_integration.py -q`: **73 passed** on the final
combined tree, including #289 and #290. After updating to merged main,
head `2f0c9488dd0938820d0f4331daeafe4cee1f1dc5` was verified
byte-for-byte identical to that tested tree.
- Test-file conflicts with #289/#290 were resolved by retaining both
sides' test functions and manual runner calls. Existing test bodies were
checked unchanged.
- Ruff on the affected files and `git diff --check`: passed.
- Local validation used Windows, Python 3.12.10 and Git 2.56.0.
Linux/macOS and broader application validation rely on hosted CI; no
local Electron journey or live provider call was performed.

## Final check

- [x] One coherent blank-context preservation fix
- [x] No new dependency, schema, fallback or public configuration
- [x] No secrets, personal data or restricted assets
- [x] CPU/model-less baseline retained

---------

Co-authored-by: Lucas1479 <sli776@aucklanduni.ac.nz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Work diff canvas ignores copy headers when the user's Git config sets diff.renames=copies

2 participants