Skip to content

copies: keep renames detected against multiple merge parents#9757

Open
hexbinoct wants to merge 1 commit into
jj-vcs:mainfrom
hexbinoct:fix/merge-commit-rename-diff
Open

copies: keep renames detected against multiple merge parents#9757
hexbinoct wants to merge 1 commit into
jj-vcs:mainfrom
hexbinoct:fix/merge-commit-rename-diff

Conversation

@hexbinoct

@hexbinoct hexbinoct commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #9752.

jj diff (and jj status) on a merge commit could silently drop a rename or copy. In the issue's reproduction, a file renamed in a merge whose parents all hold the source shows up as a plain A b.txt with no D a.txt, instead of R {a.txt => b.txt}.

Cause: for a merge commit the diff command detects copies once per parent and feeds each parent's records into CopyRecords::add_records. When the same rename exists against more than one parent, the identical (source, target) record arrives multiple times. add_records interpreted the repeated target as a target with several different sources and set both the source and target map entries to an out of range index, which is how it marks a path as conflicting. After that, for_target(b.txt) returns None so the target renders as a plain addition, while has_source(a.txt) still returns true so the deletion entry is suppressed. The rename is lost from both sides.

Fix: skip a record whose (source, target) pair was already recorded, so a duplicate coming from another parent is no longer mistaken for a conflict. A genuine conflict, where one target has two different sources across parents, is still discarded exactly as before. Since the fix is in the shared add_records, jj status on a merge commit benefits too.

Regression test: test_diff_rename_in_merge_commit builds a merge of two branches that both contain the file and then renames it. jj diff -s prints R {a.txt => b.txt} with the fix and A b.txt without it. The full test_diff_command suite and the jj-lib copy tests pass, and clippy and fmt are clean.

Thanks @yuja for pointing at the relevant code in the issue thread.

Update: the existing test_diffedit_merge was asserting this same bug from the other direction. It makes a copy inside a merge commit and expected it to render as a plain A file3. With the fix it now shows C {file1 => file3}, which is exactly what the same edit produces against a single parent (verified with jj debug copy-detection). Full jj-cli and jj-lib suites pass (0 failures).

@hexbinoct
hexbinoct requested a review from a team as a code owner July 4, 2026 06:41
@hexbinoct
hexbinoct force-pushed the fix/merge-commit-rename-diff branch from 7c3f494 to 16545ce Compare July 4, 2026 07:33
When diffing a merge commit, copy and rename detection runs once per
parent, so a file renamed on a path present in several parents yields the
same (source, target) copy record for each of them. `add_records` treated
those duplicates as a target with multiple sources and poisoned both
lookup maps. That dropped the deletion of the source and showed the target
as a plain addition instead of a rename.

Skip a record whose (source, target) pair was already recorded so the
duplicate no longer looks like a conflict. Genuine conflicts, where a
target has two different sources, are still discarded as before.

The existing `test_diffedit_merge` was asserting the buggy output, where a
copy made in a merge commit showed as a plain addition. Its snapshot now
reports the copy, matching what the same edit produces against a single
parent.

Fixes jj-vcs#9752
@hexbinoct
hexbinoct force-pushed the fix/merge-commit-rename-diff branch from 16545ce to fa890de Compare July 6, 2026 04:23

@yuja yuja left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, thanks.

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.

jj diff omits renamed files in a merge commit

2 participants