Repository navigation
shape-doctor.py: a shape copy renamed on both sides of a merge, DD at its old name, is named gone on both sides with each new name a side's own commit holds the base's blob of it at, never as a merge of hunks git did not merge (#783) - #789
Conversation
…in reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) `pin_restore` took `--ours` wherever ours held a pin, read or not, and `--theirs` only where ours held none. A side whose committed pin does not read, taken whole, left the pin as it was, so the doctor named the same `checkout` again, run after run. - `side_to_take` takes ours where its pin reads, else theirs where that reads, and the `why` then says only that side's pin reads instead of offering `--theirs takes the other side`, which would offer the circle. - Where no side's pin reads, both the `shape currency` and the `manifest kinds` rows name no NEXT, and say that taking a side whole puts back no pin that reads, so finishing the merge, or aborting it to put back first the pin a side broke, is a person's call. `unreadable_pin_row` keeps its `update-shape.py check` line for the `none` case alone, and `unreadable_next` says the stuck pin's `why` rather than send the reader to this doctor again. - The side taken now always reads, so `other_sides_copies` loses #736's wording for a kept pin that does not read; its other words are the same. Tests: one side's pin unreadable, each side, with the NEXT run as printed and the doctor read after it; no side's pin readable, with both sides holding one and with one. Each fails on the doctor before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted under a name no pin has a row for, is named in a conflicted shape pin's `why` and `left_for_a_person` as renamed from that copy, for a person to settle, and never taken (#753) Where one side renamed the Makefile to build.mk and the other edited it, git carries the conflict to build.mk, which no pin's row lists, so the `why` and `left_for_a_person` said nothing of it and only the `merge` row listed the path. - `pin_restore` keeps each conflicted path's stage 1, the merge base's blob, from the `ls-files -u` it already reads. - `copies_renamed` calls a path no pin lists a rename of a copy where the merge base (`merge-base HEAD MERGE_HEAD`) held nothing at that path and its stage 1 is, object for object, the blob the base holds at that copy's path, and one copy alone matches. git gives a path the base did not hold a stage 1 only where it paired it with one the base did. A path the base held is no rename, though its bytes be a copy's, as the source's own file beside a collision copy can be (the collision-layout test in test_shape_doctor.py holds that). No similarity index is guessed at, and any other kind of conflict names nothing, as before. - `renamed_copies` gives the sentence: "build.mk, renamed from the copy Makefile, is conflicted too, and no pin has a row for it under its new name, so neither side is taken whole: a person merges it by hand, keeping the hunks git merged", or, where one side alone holds it (a rename the other side deleted), that a person keeps or deletes it. - `by_conflict` is the split `other_sides_copies` made inline, shared. Tests: the rename on the side `--ours` takes, on the other side, and on one side with the copy deleted on the other. Each fails on the doctor before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…path in the whole merge base holds its stage-1 blob and that path is a copy, so a file of the project's own with a copy's bytes is never named; the `why` says the copy is reported missing once the merge is finished, where the pin taken here still lists it (review of #781) RV-b's review of #781 at 23f3879: - S1. `copies_renamed` listed only the copies' paths in the base, so a file of the project's own holding a copy's bytes (tools/bootstrap.py, byte for byte scripts/bootstrap.py), renamed on one side and edited on the other, was named "tools/boot.py, renamed from the copy scripts/bootstrap.py" and given to a person, though a file of the project's own is never named. It now reads the base's whole tree, one `ls-tree -r -z`, and calls a path a rename only where exactly one path holds its stage-1 blob and that path is a copy. Not git's `diff -M` record: a similarity guess of its own, made apart from the merge's. What that costs: a copy renamed beside a file with its exact bytes is not named, as before #753. - N1. Where the pin taken here still lists the old name, the `why` adds "The pin taken here still lists Makefile, so this doctor reports it missing once the merge is finished, unless it is renamed back". Tests: the own file renamed, and the copy renamed beside its twin, name nothing (both were named before; dropping the uniqueness guard now fails them); each rename case finishes the merge with build.mk kept and reads `DRIFTED (1 copy-missing)`. The rename setup is shared (`merged_after`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes no copy holds, renamed, is named no copy's rename, and a rename the kept pin has no row for says nothing is missing, so both guards of #753's rename rule are held (review of #781, round 3) RV-b's re-verification of #781 at 06e1de8 found two guards of `copies_renamed` and `renamed_copies` that no test held: - "the one path holding the blob is a copy": dropped, a project's own docs/notes.md, renamed to docs/n.md on one side and edited on the other, was named "renamed from the copy docs/notes.md". Now `test_a_rename_of_no_copy_names_nothing[the-projects-own-unique-file- renamed]` holds it, beside round 2's two twin cases (the table and the test renamed to cover all three). - "the pin taken here lists the old name": dropped, a merge that ends COMPLIANT was told the Makefile would be reported missing. Now `test_a_rename_the_kept_pin_has_no_row_for_says_nothing_is_missing` restages the kept pin without the Makefile's row, finds no "still lists" sentence, and finishes the merge to a verdict that is COMPLIANT and not copy-missing. `merged_after` takes an own file's bytes as well as a copy's, and `finished_keeping` finishes a rename's merge for both tests. In shape-doctor.py, `copies_renamed`'s docstring calls "the base held nothing at the new name" belt and braces since round 2: alone it acts only on a rename onto a path the base held (rename/add). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at its old name, is named gone on both sides with each new name a side's own commit holds the base's blob of it at, never as a merge of hunks git did not merge, and the pin taken here's row for it is said to go missing (#783) Ours renames the copy Makefile to a.mk and theirs to b.mk. git leaves `DD Makefile` (stage 1 alone), `AU a.mk` and `UA b.mk`, neither with a stage 1, so #781's rename rule saw neither new name, and the Makefile, a conflicted copy, got the copies' sentence: "a person merges it by hand, keeping the hunks git merged", where git merged none. - `pin_restore` takes a copy at stage 1 alone out of `copies` and `other_side`: never taken, and never the hunks sentence. - `copies_renamed_twice` names a path one side alone added as that side's new name for the copy where the commit that side records (`HEAD` for stage 2, `MERGE_HEAD` for stage 3) holds the merge base's blob of the copy there, and no other path of the base holds that blob. git writes its merge of the copy's three versions to both new names, so where a side edited the copy no index stage holds the base's blob; that side's own commit does where it renamed without an edit. A side whose commit holds an edit is not proved: no similarity index, and git's merged content, the same at both names, is not taken for a pairing. - `twice_renamed_copies` says: "Makefile is gone on both sides of the merge, renamed to a.mk on the side taken here and to b.mk on the other side, and no pin lists the new names, so nothing is taken whole: a person keeps one name, or both, by hand"; where one side's name is not proved, that it is not; where neither is, that a person settles where it goes. - `still_listed` is #781's "The pin taken here still lists ..." sentence, moved out of `renamed_copies`, said of a copy renamed on one side or both that the pin taken here still lists. - `base_holders` is the base's whole tree by blob, read once for both rename rules; `copies_renamed` takes it. Tests: a rename on both sides; with an edit on ours; beside an own file holding the Makefile's bytes. Each finishes the merge keeping a.mk and reads `DRIFTED (1 copy-missing)`. Each fails on #781's doctor; four mutants (the `DD` test, the blob test, the one-path test, the stage's commit) each fail at least one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR extends shape-doctor’s conflicted-pin analysis to recognize copies renamed on both sides of a merge using conservative, exact blob evidence, while improving readable-pin side selection and adding focused behavioral and mutation-tested coverage. Sequence diagram for conservative two-sided copy rename detectionsequenceDiagram
participant Doctor as shape-doctor.py
participant Index as Merge index
participant Base as Merge base tree
participant Commit as Side commit
Doctor->>Index: read ls-files -u
Index-->>Doctor: DD old name; AU and UA new names
Doctor->>Base: base_holders()
Base-->>Doctor: blob to base paths
Doctor->>Commit: rev-parse HEAD:path or MERGE_HEAD:path
Commit-->>Doctor: committed blob
alt committed blob equals base blob and base holds it once
Doctor->>Doctor: copies_renamed_twice()
Doctor-->>Doctor: name the side's new path
else edited or ambiguous evidence
Doctor-->>Doctor: leave destination unproved
end
Doctor-->>Doctor: twice_renamed_copies() reports hand resolution
Flow diagram for detecting copies renamed on both merge sidesflowchart TD
A[Conflicted merge index] --> B{Copy has stage 1 only DD?}
B -- No --> C[Existing copy conflict handling]
B -- Yes --> D[Read merge-base tree by blob]
D --> E[Find AU and UA paths]
E --> F{Side commit holds base blob exactly?}
F -- No --> G[Leave destination unproved]
F -- Yes --> H{Blob occurs at exactly one base path?}
H -- No --> G
H -- Yes --> I[Prove side-specific new name]
I --> J[Report old copy gone on both sides]
J --> K[Leave names for person to resolve]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-17T09:26:11Z) Independent review of #789 by RV-b (opus), top commit of head The writer's claims hold:
One small error in the body: it says every claim in the issue held, but the issue swapped the S1: the dropped guard is reachable (
S2: two guards are untested, and both mutants survive and print false sentences. The head is right in both states; only the tests are missing.
S3: when the standard moved the copy, neither new name is proved, though exact evidence exists (
NITs:
Cases measured on the head:
Mutants:
Tests:
Merges: clean with Lane: openreposhape-3 (openRepoShape-3) |
…ew name by the bytes that side's own pin row records too, so a move of the standard no longer hides both names; a path a pin has a row for is never also named a new name, and the sentences say "neither name is taken" (review of #789) - S1: `pin_restore` passes `copies_renamed_twice` only the conflicted paths no pin has a row for. Theirs renaming the Makefile to b.mk and moving its row to `- path: b.mk` in the same commit named b.mk twice, as the other side's copy and as a new name "no pin lists", and `left_for_a_person` held it twice. `left_for_a_person` is built from a set. - S3: `side_moved_it` also proves a new name where the side's own commit (`HEAD`, `MERGE_HEAD`) holds there the bytes whose sha256 that commit's own shape pin records in its row for the copy (`pinned_sha256`). Where the standard moved the copy between the two pins, neither commit holds the merge base's blob, and neither name was proved. Exact, no similarity. Those bytes must be held at no path of the merge base, as the base's blob must be held at the copy alone, and with no merge base nothing is proved. - NIT: "so nothing is taken whole" is "so neither name is taken" in the three sentences. Tests: `TWICE` is a table of `Twice` cases, eight new: theirs moving its row (S1); `--theirs` taken, plain and edited on ours, where ours' pin does not read; the kept pin without the Makefile's row, finished COMPLIANT (S2); the standard moving the Makefile, beside a twin of its old bytes, with the base holding ours' new bytes, and with no MERGE_HEAD (S3). Each case also checks every other name is said once, and "hunks" only for a copy left to a person. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T10:03:32Z) Re-verification of #789 round 2 by RV-b (opus), on head S4: the new pin-row proof can name a project's own file as the copy's new name (
NITs:
Rulings on W687's open points:
Checked:
Runs:
Merges: clean with main and with every open lane-3 PR, #630 included. Lane: openreposhape-3 (openRepoShape-3) |
…ew name only where that side's own commit holds the proving bytes at that one path, so a project's own twin kept in step with the copy and renamed too is never named the copy's new name (review of #789, round 3) RV-b's S4: the base holds the Makefile and the project's own twin of it, `tools/Makefile.orig`. Ours keeps the twin in step with a.mk, and each side renames the twin too. Ours' commit then holds the bytes its pin row records for the Makefile at a.mk and at tools/x.mk, which git paired with the twin, and the `why` said "renamed to a.mk and tools/x.mk on the side taken here": a person following it could delete their own file. - `side_moved_it` proves a name only where the side's own commit holds its bytes at that path alone (`tree_holders`, `ls-tree -r -z` of HEAD or MERGE_HEAD): #781's one-path rule, applied to the side's tree. It closes the same gap for the base's blob written at two paths. - `base_holders` reads the base through `tree_holders`. Tests: two `TWICE` cases, the twin kept in step with the standard moving (pin-row proof) and without (base-blob proof). Each names only b.mk and leaves `[Makefile, b.mk, project.yaml]`; both fail without the one-path check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T10:18:12Z) Re-verification of #789 round 3 by RV-b (opus), on head 1.
2. The
The rest are unchanged: every no- 3. The two new cases build real states: real commits and a real NITs:
Runs:
CI: SonarCloud, package and tests-git-bash pass; Lane: openreposhape-3 (openRepoShape-3) |
…t 2.46 and later refuse `git update-ref -d` on a pseudoref (#783) CI's git 2.55.0 answered `refusing to update pseudoref 'MERGE_HEAD'` to the helper's `update-ref -d MERGE_HEAD`, which the container's git 2.43.0 allows. The helper now unlinks the file at the path `git rev-parse --git-path MERGE_HEAD` gives, a linked worktree's own included, and asserts it is gone; MERGE_HEAD stays a file under both ref backends, so it holds on every git. A missing file is an error: the helper follows a real merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T20:23:42Z) RV789d's re-check of #789's fix round ( Verdict: READY. I found no MUST or SHOULD items. Commit 3cfd34b fixes the CI failure, and the test still checks the state it names. Everything I ran was read-only; I made no GitHub write. The PR head was still 3cfd34b at my last read. 1. The diff. 2. The test still tests its state. I copied one conflicted rename/rename merge (under
3. Path handling.
4. git 2.46+ emulation. I put a shim
5. Mutants, each run on the case
The surviving mutant is equivalent in this fixture, because the root is never a linked worktree. Only my probe in check 3 proves the linked-worktree behaviour. 6. Other callers.
7. Merges with
8. Tests (each run with the stub
CI on 3cfd34b (I did not wait for it): SonarCloud pass, package pass, tests-git-bash pass, and NITs (optional, not blocking):
Lane: openreposhape-3 (openRepoShape-3) |
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
#781's final head a61f4e1 and its squash ed052f2 hold the same tree. The squash conflicted only in shape-doctor.py and tests/test_doctor_pin_restore_merges.py, the two files #781 and this branch both change; both are taken ours, which changes neither file from the merge of a61f4e1 above. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 3 days and 12 hours by commenting |



Lane: openreposhape-3 (openRepoShape-3)
Claim: #783 (comment)
Closes #783.
Stacked on #781; lands after it. #781 is itself stacked on #779, and all three change
pin_restore. This branch is #781's head0a9afe3aplus three commits:3dd7ffb8(#783),37da4e52(round 2) andabcad3997cfb82c297f5a62ddba6244106b214b9(round 3). Against main the diff therefore shows #779's and #781's changes too. The file:line references below are toabcad399, except in the Round 2 section and its mutant table, which keep37da4e52's.Lands after the v1.1.0 tag.
What happened
Ours renames the copy
Makefiletoa.mk, and theirs renames it tob.mk.Measured on #781's head
0a9afe3a, git 2.43ort, with the standard moving onlybootstrap.py:DD Makefilewith stage 1 alone (b9fac472, the base's blob),AU a.mkwith stage 2 alone, andUA b.mkwith stage 3 alone.why: "Makefile is a copy conflicted too. … a person merges it by hand, keeping the hunks git merged", followed by the--accept-localsentence. git merged no hunks.a.mkandb.mkwere not named, andleft_for_a_personwas['Makefile', 'project.yaml'].a.mkstage 2 andb.mkstage 3 are06cfdf63, so no index stage holds the base's blob. Only theirs' own commit (MERGE_HEAD:b.mk) still does.The issue's claims held except one: its
AU/UAlabels are swapped. It saysAU b.mkandUA a.mk; measured, ours' new name isAU a.mk(stage 2) and theirs' isUA b.mk(stage 3), as RV-b confirmed. (Round 1 of this body said every claim held; that was wrong.)What changed (
shape-doctor.py)A copy at stage 1 alone (
DD) leavescopiesandother_side(pin_restore,:4005-4022).--accept-localsentence.:4017-4022, round 2, S1).left_for_a_person, built from a set (:4044-4047), and its sentences thewhy(:4078-4081).copies_renamed_twice(:4263-4330) names a new name only on exact evidence (side_moved_it,:4341-4360). A path one side alone added (stage 2 or 3 alone), and no pin has a row for, is that side's new name for the copy where the commit that side records (HEADfor stage 2,MERGE_HEADfor stage 3) holds at that path, and at no other path of that commit (tree_holders,:4201,:4352, round 3, S4), either:whyandleft_for_a_personas renamed from that copy, for a person to settle, and never taken (#753) #781 weighs one; orpinned_sha256,:4333-4338, round 2, S3), and no path of the base holds those bytes.More on the rule:
applyrewrote it, so neither commit holds the base's blob, and neither name was proved. Each side's commit holds exactly what its own pin row records.whyandleft_for_a_personas renamed from that copy, for a person to settle, and never taken (#753) #781's one-path rule, applied to the side's tree. A project that keeps its own twin of the copy in step with it, and renames the twin too, holds the proving bytes at two new names, and git pairs one of them with the twin. Neither is proved, as either may be the project's own file. The same holds for the base's blob written at two paths.MERGE_HEAD, as a cherry-pick, a rebase ormerge --squashleaves), nothing is proved.twice_renamed_copies(:4363-4395) writes the sentence:still_listedis shape-doctor.py: a shape copy renamed on one side of a merge, conflicted under a name no pin has a row for, is named in a conflicted shape pin'swhyandleft_for_a_personas renamed from that copy, for a person to settle, and never taken (#753) #781's N1 sentence, moved out ofrenamed_copies. It now covers a copy renamed on one side or both: "The pin taken here still lists Makefile, so this doctor reports it missing once the merge is finished, unless it is renamed back". It is said only where the pin taken here has the row.base_holdersreads the base's whole tree, by blob, once for both rename rules, throughtree_holderssince round 3.copies_renamednow takes it instead of reading it.Docstrings: a paragraph in
pin_restore'sunmergedentry (:3856-3868), the "never named" rule, andcopies_renamed_twice's.Tests (
tests/test_doctor_pin_restore_merges.py)test_a_copy_renamed_on_both_sides_is_named_for_a_person[...](:1389) runs the tableTWICE(:1341) ofTwicecases (:1314):left_for_a_persona.mkrenamed-on-both-sides--oursMakefile, a.mk, b.mk, project.yamlDRIFTED (1 copy-missing)…-edited-on-ours--oursMakefile, b.mk, project.yamlDRIFTED (1 copy-missing)…-beside-a-twin--oursMakefile, project.yamlDRIFTED (1 copy-missing)…-a-twin-kept-in-step(round 3)a.mkandtools/x.mk)--oursMakefile, b.mk, project.yaml…-theirs-moving-its-row(S1)b.mkis theirs' pinned copy--oursMakefile, a.mk, b.mk, project.yamlDRIFTED (1 copy-missing)…-theirs-taken(S2)b.mkis the side taken here's--theirsMakefile, a.mk, b.mk, project.yaml, scripts/bootstrap.py…-theirs-taken-edited-on-ours(S2)--theirsMakefile, b.mk, project.yaml, scripts/bootstrap.py…-the-kept-pin-without-its-row(S2)--oursMakefile, a.mk, b.mk, project.yamlCOMPLIANT, SHAPE BEHIND (0 upstream-changed, 1 upstream-added)…-the-standard-moved(S3)--oursMakefile, a.mk, b.mk, project.yamlDRIFTED (1 copy-missing)…-the-standard-moved-beside-a-twin(S3)--oursMakefile, a.mk, b.mk, project.yamlDRIFTED (1 copy-missing)…-the-standard-moved-a-twin-kept-in-step(S4, RV-b's state)a.mkandtools/x.mk)--oursMakefile, b.mk, project.yaml…-the-standard-moved-the-base-holding-ours(S3)--oursMakefile, b.mk, project.yamlDRIFTED (1 copy-missing)…-the-standard-moved-no-merge-head(S3)--oursMakefile, project.yamlEach case checks:
DD/AU/UA(stages[1],[2],[3]);checkout <side> -- contracts/shape-pin.yaml, withscripts/bootstrap.pywhere both pins read and it is taken;whyholds the Makefile's sentence, then the "still lists" sentence where the pin taken here lists it (and "still lists" nowhere else), then "Then finish";left_for_a_personbut the Makefile is said exactly once;scripts/bootstrap.pywhere it is left to a person, never for the Makefile;merged_aftertakessources, andownmay add lines to the copy's bytes.renamedtakesrow=Trueto move the pin's row in the same commit.in_stepandin_turn(round 3) build the twin kept in step, with real commits on each side.Before the fix: the first three cases fail on #781's doctor. On
3dd7ffb8's doctor with only the wording NIT applied, 4 of the 11 fail:…-theirs-moving-its-row(S1) and the three standard-moved cases that prove a name (S3); the S2 cases pass there, as RV-b said (only the tests were missing).Round 3 mutant: dropping the one-path check (
:4352,tree.get(…) != [path]) fails 2 of the 13 both-sides cases,…-a-twin-kept-in-stepand…-the-standard-moved-a-twin-kept-in-step: killed.37da4e52's doctor fails the same two, withleft_for_a_person['Makefile', 'a.mk', 'b.mk', 'project.yaml', 'tools/x.mk']. The file was restored byte for byte (sha256 checked).Round 2 mutants (lines at
37da4e52), each applied to a backed-up working copy, the both-sides cases run, then restored byte for byte (sha256 checked):sides-label(:4346)number == stage→number == b"2"…-theirs-taken,…-theirs-taken-edited-on-ours), killedgone-copies-only(:4007)+ other_sidedropped…-the-kept-pin-without-its-row), killed:4018-4021)stagespassed…-theirs-moving-its-row), killedleft_for_a_personfrom a set (:4043-4046):4332):4332)…-the-base-holding-ours), killed:4304)held anddropped…-no-merge-head), killed:4331)return True…-beside-a-twin), killed:4250)HEADandMERGE_HEADswappedDDonly (:4008):4328)…-the-standard-moved-beside-a-twin), killed:4306):4293)The mutants ran on the working copy before a docstring-only edit (
copies_renamed_twice's "NO OTHER PATH" paragraph). The restored file's sha256 matched the backup each time.Commands, at the head, each with a stub
ghfirst on PATH that exits 97,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR:Pinned shape copies
None.
shape-doctor.pyis in no copy list.Docs
None changed. No doc describes the conflicted-pin restore.
Round 2 (review by RV-b; lines at
37da4e52)pin_restorepassescopies_renamed_twiceonly the conflicted paths no pin has a row for (:4016-4021), as RV-b measured it, andleft_for_a_personis built from a set (:4043-4046). Theirs renaming the Makefile tob.mkand moving its row to- path: b.mknow namesb.mkonce, as the other side's copy, and the Makefile's sentence says the other side's name is not proved. Test:…-theirs-moving-its-row.…-theirs-takenand…-theirs-taken-edited-on-ours(ours' pin restaged unreadable,--theirstaken,b.mknamed as the side taken here's), and…-the-kept-pin-without-its-row(both names, no "still lists", finishedCOMPLIANT, SHAPE BEHIND (…)).sides-labelandgone-copies-onlyare killed (table above).side_moved_it,pinned_sha256). The base-tree check carries over: the bytes must be held at no path of the merge base. So an own file holding the copy's OLD bytes does not block a name the standard moved it to, while one holding a side's NEW bytes does. With no merge base, nothing is proved. Tests: the four…-the-standard-moved…cases, RV-b's scenario among them.:4353,:4363,:4369). The body now says the issue'sAU/UAlabels were swapped.c.mkis a clean add, neverAU/UA. The[paths]list stays, so nothing breaks should git ever give more.Round 3 (re-verification by RV-b)
tools/Makefile.origwith the same bytes, the standard moves the Makefile, ours keeps the twin in step witha.mkand each side renames the twin too. Ours' commit then held the bytes of its pin row ata.mkand attools/x.mk, which git paired with the twin, and thewhysaid "renamed to a.mk and tools/x.mk on the side taken here". Fixed my own way, after reading RV-b's trial (rvb789-r2-fix-unique, a separatetree_holdersbesidebase_holders):tree_holders(:4201) is the one tree reader,base_holders(:4190) now calls it, andside_moved_itrefuses any path whose bytes the side's own commit holds at another path too (:4352, the bytes must be held at[path]exactly), for both proofs. Each side's tree is read once perpin_restore(:4317-4319).…-the-standard-moved-a-twin-kept-in-step(S4, pin-row proof) and…-a-twin-kept-in-step(the base-blob proof's gap: the twin's base bytes differ, ours writes the Makefile's base blob into it). Both are built with real commits andgit merge. Each names onlyb.mkand leaves['Makefile', 'b.mk', 'project.yaml'].…-the-standard-movedstill proves both names, and…-the-standard-moved-beside-a-twinis unchanged.files:row for is named in itswhyandleft_for_a_personfor a person to merge, and never taken (#687) #736'sother_sides_copiessentence (:4164, landed on main asbf1f099b), and the same sentence covers a real delete/modify conflict (by_conflict). It is not local to this PR, so I left it. For a path only one side added and only the other side's pin lists (…-theirs-moving-its-row), "one side alone holds it", asrenamed_copiessays, would be the truer words.Not covered
Where no rename conflict happens (both sides rename to the same name, or
-s recursive -X no-renames), nothing says the kept pin still lists the old name, and the verdict endsDRIFTED (1 copy-missing). Main and #781 behave the same.For the reviewer
HEAD:contracts/shape-pin.yaml,MERGE_HEAD:…), not from its index stage, so it matches the bytes read from that same commit. In the--theirscases, ours' index stage is restaged unreadable whileHEAD's pin still reads; both evidences agree there.AU/UApaths whose blob matches the base's copy"): in a real merge such a path is named whenever the side's own commit holds that blob at that one path. Nothing is named where the base holds that blob at two paths (shape-doctor.py: a shape copy renamed on one side of a merge, conflicted under a name no pin has a row for, is named in a conflicted shape pin'swhyandleft_for_a_personas renamed from that copy, for a person to settle, and never taken (#753) #781's rule that a blob two paths hold names nothing), or where the side's own commit holds it at two paths (round 3). Nothing is named either where there is noMERGE_HEAD: a real cherry-pick (CHERRY_PICK_HEAD), a rebase (REBASE_HEAD) orgit merge --squash. There, git names no merge base, thougha.mk:2andb.mk:3can both equalMakefile:1. Thewhythen says neither side's name is "proved here", which is true. RV-b measured all three;…-the-standard-moved-no-merge-headstands in for them. No follow-up issue is filed for it.🤖 Generated with Claude Code
Summary by Sourcery
Teach shape-doctor.py to safely diagnose copies renamed on both sides of a merge without claiming Git merged hunks or guessing unproven destinations.
New Features:
Bug Fixes:
Enhancements:
Tests: