Skip to content

adopt-project.py: the mount removes a path a case twin is at or under with git rm -f and writes the twins the root keeps back from the index, so on a disk that ignores case execute keeps a set the plan splits across repositories or drops (#1183) - #1237

Draft
brettheap wants to merge 4 commits into
fix/1161-check-names-case-twinsfrom
fix/1183-case-twins-across-repositories

Conversation

@brettheap

@brettheap brettheap commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1189 (fix/1161-check-names-case-twins, head 58aa9d2b); it lands after #1189. Its base is #1189's branch, and its diff against 58aa9d2b is adopt-project.py and tests/test_adopt_check_throwaway_repos.py alone.

Outside v1.2.0, by Brett Heap's ruling of 2026-10-21T10:32:04Z ("leave the follow-ups out of v1.2.0").

What

On a disk that ignores case, execute lost a case twin the plan keeps in the root when its twin goes to a leg or is dropped, or stopped mounting the legs, in each case after both legs were pushed. check printed plan ok for every such plan: plan-case-twins (#1161, #1189) asks only about twins the plan keeps in ONE repository.

Measured with git 2.43 on a FUSE disk that ignores case and keeps it, in a private namespace. The split's clone checks out one file per twin set, holding the bytes of the twin git wrote last in the index's order (a after A, d/x after D/x), and git says the following paths have collided. Then:

  • where the twin that goes sorts last (A root, a code), git rm a deleted the one file both names share, and git add -A staged the kept twin's deletion: adopt-lost, exit 1;
  • where it sorts first (A code, a root), git rm A refused with the following file has local modifications: A: REFUSED mounting the legs, exit 2. That also happened with BOTH twins dropped, and with one twin sent to each leg.

Each leg was always right, since git filter-repo works on objects. The loss was only in the assembly's work tree, between _mount_the_legs's git rm loop and _stage_the_split's git add -A -- ..

Now _mount_the_legs does three things:

  • it lists the assembly's index once (git ls-files -z, read-only), and keeps each _case_twin_sets set with a path at or under a path it removes (_split_case_twins);
  • it removes each such path with git --literal-pathspecs rm -r -q -f -- (GIT_RM_FORCED), and every other path with today's GIT_RM;
  • after the last git rm, it writes the twins of those sets that the root keeps back from the index with git checkout-index -f -z --stdin (_write_from_the_index). A refusal there is CommandFailed, one of LATE_FAILURES, naming the command as _git_rm names its own.

A source with no such set, as nearly every source is, gets the same git rm commands as before and nothing written back. The one command added for it is the read-only listing.

FUSE scenarios measured, plan, check then execute --local-remote-dir

Each twin holds a blob of its own unless noted. Base is #1189's head as it was then, 4d40ce9f. Head is ef18f059's adopt-project.py. The merge of 58aa9d2b changed that file only in the root NOTE's rule, which none of these plans reaches, since each keeps no set in one repository.

scenario the plan's answers base 4d40ce9f head
across A, D/ root; a, d/ code exit 1, adopt-lost A, D/x verified
acrossrev A, D/ code; a, d/ root exit 2, REFUSED mounting the legs at git rm verified
acrossspec A, D/ root; a, d/ spec exit 1, adopt-lost A, D/x verified
dropped A root; a drop exit 1, adopt-lost A verified
droppedrev A drop; a root exit 2, REFUSED mounting the legs verified
dropboth A, a drop exit 2, REFUSED mounting the legs verified
specode A spec; a code exit 2, REFUSED mounting the legs verified
ffacross F/ root; f code exit 1, adopt-lost F/x verified
ffacrossrev F/ code; f root verified verified
ffdropped F/ root; f drop exit 1, adopt-lost F/x verified
samebytesacross A root; a code; one blob not measured verified
samebytesdropped A root; a drop; one blob not measured verified
dirmergeacross D/x root; d/y code not measured verified
deepacross D/E/x root; d/e/x, d/e/y code not measured verified
none no twins not measured verified
root A, a root not run check exit 1 and execute exit 2, plan-case-twins before any leg (#1189's refusal, unchanged)

"verified" means check printed plan ok, and execute exited 0 with adoption verified: every source path is in exactly one place. Where base was measured, its check printed plan ok too.

On the bench disk, which keeps case, base verified all 15 scenarios above but root. At head, only across and acrossrev were run there, and both verified (see Known limits).

Why

  • -f on the paths a twin is at or under, and on no other. -f skips only git's question whether the file on the disk still holds what the index holds. In a fresh clone, that question can fail only through the collision, since check refuses re-encoded files before any leg. Every other path keeps the command it had, and a row pins that it does.
  • Written back after the loop, from the index. Writing each kept twin before its sibling's git rm was rejected: one git rm -r can remove two twins at once (a dropped folder holding d/x and d/X, which plan-case-twins does not ask about, because drop is no repository), and on a disk that ignores case the write and the removal meet the same file. A row pins the order.
  • --cached was rejected. On a disk that keeps case it leaves the file, and git add -A adds it again.
  • The index is asked, not the disk. No core.ignorecase probe is made. The same commands run on every disk, and on a disk that keeps case the write-back rewrites the bytes each kept twin already holds. This is why the rows can make the one shared file themselves, with hard links, on CI's Linux disks.
  • Names as bytes. The listing is read as _git_path reads a path (UTF-8, a byte that is not UTF-8 kept as a surrogate). The names checkout-index reads are handed back as the bytes the tree holds (surrogateescape), on its input, so no locale and no length of a command line is in the way.
  • plan-case-twins's remediation text is unchanged. Its comment and the comment above the finding now say what execute does since this change.

Tests

In tests/test_adopt_check_throwaway_repos.py, section "#1183", before "L1-92". Base is 58aa9d2b (this PR's base: its adopt-project.py swapped into this head's tree). Head is c9f0cc18.

row base 58aa9d2b head
test_the_mount_keeps_each_twin_the_root_keeps_with_its_own_blob (6 shapes: across, across-the-other-way, dropped, both-dropped, one-to-each-leg, a-folder-kept-beside-a-file) 6 failed: a wrong blob for the kept twin (across, dropped, a-folder-kept-beside-a-file); CommandFailed git --literal-pathspecs rm -r -q -- A (across-the-other-way, both-dropped, one-to-each-leg) 6 passed
test_a_source_with_no_twin_set_split_is_mounted_as_before (guard) passed passed
test_only_a_path_a_twin_is_at_or_under_is_removed_with_f failed: no -f and no checkout-index among the commands passed
test_a_path_git_cannot_write_back_is_refused_naming_the_command failed: no _write_from_the_index passed
test_a_forced_git_rm_git_refuses_is_refused_naming_it_with_f failed: _git_rm takes no forced passed
test_twins_whose_names_are_not_utf_8_keep_their_own_blobs failed: the kept twin staged with its sibling's blob passed
test_on_a_disk_that_ignores_case_execute_keeps_twins_it_splits (4 shapes, FUSE only) skipped skipped
test_execute_adopts_twins_it_sends_to_different_repositories (the control, any disk) passed passed

Totals: base 10 failed, 2 passed, 4 skipped; head 12 passed, 4 skipped. The 4 skips are the rows that need a disk that ignores case, and every CI disk keeps case. They are skipped where git init writes no core.ignorecase=true. The rows that drive the mount make each set one file themselves, with hard links, as the trace measured.

Mutants of ef18f059's new lines (GIT_RM_FORCED, _git_rm's forced, CHECKOUT_INDEX, _at_or_under, _split_case_twins, _write_from_the_index, _mount_the_legs' use of them), run against the section's rows in a clone, each restored in a finally with its blob checked equal to HEAD's:

  • at the first rows: 34/40 killed;
  • with the two rows this PR adds for M05, M21 and M23: 37/40 killed.

The three survivors are equivalent:

  • forced: bool = True as _git_rm's default: its one caller always passes forced;
  • an empty ls-files record kept: the name "" is a set of one, and no set of one is kept;
  • check_program dropped from _write_from_the_index: CHECKOUT_INDEX is a constant git command, all text, which check_program always allows.

Gate at c9f0cc18, one pytest call per line, under a stub gh, GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null and GIT_SSH_COMMAND=false (CPython 3.12.3 on the bench; CI runs 3.14):

  • tests/test_adopt_check_throwaway_repos.py: 103 passed, 6 skipped (the 4 FUSE-only rows, and 2 outside this section);
  • tests/test_adopt_make_reads_first.py: 33 passed;
  • tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py: 107 passed, 2 skipped;
  • tests/test_scaffold_leg_path_collisions.py: 104 passed;
  • tests/test_adopt_clone_template.py tests/test_adopt_git_rm_bytes.py tests/test_adopt_late_failure.py tests/test_adopt_mount_ignored.py tests/test_adopt_submodules.py: 316 passed.

tests/test_shape_doctor.py (4 shards) is lane 1's to run.

Known limits

  • The bench disk at head was not run in full. Only across and acrossrev were run there, and both verified. At base, all 15 verified there. At base on FUSE, samebytesacross, samebytesdropped, dirmergeacross, deepacross and none were not measured, and root was not run.

  • three gave no result. That is a defect in the bench script, not in the tool: the plan quotes the name Å, and the bench's path: match does not. The scenario (A root, a code, Å drop, å root) was not measured on either side.

  • The FUSE-only rows have not been run on a FUSE disk. They skip on every CI disk. The scenarios they hold were measured on FUSE through the bench, as in the table above.

  • plan-case-twins's sentence is now partly stale (lane 1 rules on adopt-project.py: check names the case twins the plan keeps in one repository as plan-case-twins on every disk, a one-blob set as a NOTE in a leg and, in the root, two paths in one folder, and execute refuses the finding before any leg (#1161) #1189's text; it is unchanged here). It says execute "could stop ... after both legs are pushed, as adopt-lost or at git rm mounting the legs" for twins the plan keeps in one repository. Measured on the FUSE disk with _case_twin_findings patched, in a scratch copy, to return no finding, for twins kept in one leg (A and a to code; A and a to spec; D/ and d/ to code; F/ and f to code; src/A and src/a under src/ to code):

    • at 58aa9d2b: check printed plan ok, and execute exited 2 with REFUSED mounting the legs, after both legs were pushed, in four of the five: at git --literal-pathspecs rm -r -q -- <path>, <path> being A (to code), A (to spec), D and src, git saying "the following file has local modifications". F/ and f verified.
    • at this head: all five exited 0 with adoption verified. The git rm stop is gone for twins in one leg.
    • at this head, unpatched: all five are still plan-case-twins in check (exit 1), and execute refuses them before any leg (exit 2).

    The finding still stands: such a leg holds twins that a clone of it on such a disk cannot check out apart. Only the clause naming git rm no longer describes what execute would meet for a leg.

Not in this PR

  • The remediation's option "or answer the twins of each set into different repositories, or drop all but one, and run check again". It is now safe on every disk measured, but it is left for a follow-up, with TWINS_REMEDIATION and the doc trio.
  • Any change to plan-case-twins's sentence (see Known limits).
  • A check that asks the work directory's disk for core.ignorecase. This change makes execute keep these sets on every disk, so check needs no such probe for them.

Closes #1183

Lane: openreposhape-1 (openRepoShape-1)

🤖 Generated with Claude Code

Summary by Sourcery

Keep case-distinct paths intact when adoption moves or drops their twins on case-insensitive filesystems.

Bug Fixes:

  • Preserve case-distinct paths during adoption when they are split across repositories or partially dropped on case-insensitive filesystems.
  • Prevent mounting failures and lost root files by force-removing affected paths and restoring retained twins from the Git index.

Enhancements:

  • Maintain byte-accurate handling for non-UTF-8 path names and retain existing removal behavior for sources without affected case-twin sets.

Tests:

  • Add coverage for split and dropped case-twin layouts, command selection and failure reporting, non-UTF-8 names, and end-to-end adoption behavior.

Brett Heap and others added 4 commits October 23, 2026 12:16
… with `git rm -f`, and writes each twin the root keeps back from the index, so a disk that ignores case no longer loses a set the plan splits across repositories or drops a path of (#1183)

Measured with git 2.43 at 4d40ce9, on a FUSE disk that ignores case: the
split's clone checks out one file per twin set, holding the bytes of the
twin git wrote last. Where the twin that goes sorts last (`A` root, `a`
code), `git rm a` deleted the one shared file and `execute` stopped with
`adopt-lost` after both legs and the split were pushed; where it sorts
first (`A` code, `a` root), `git rm A` refused with "local modifications"
and `execute` stopped mounting the legs.

`_split_case_twins` lists the assembly's index once and keeps each
`_case_twin_sets` set with a path at or under a removed path. Each removed
path with such a twin is removed with `GIT_RM_FORCED`; every other path
keeps `GIT_RM`. After the last `git rm`, the set's paths the root keeps are
written back with `git checkout-index -f -z --stdin`. A source with no
split set gets the same `git rm` commands and nothing written back.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eps each case twin the root keeps with its own blob where the plan sends its twin to a leg or drops it, removes only such a path with `-f`, writes back only the kept twins, and that `execute` adopts such a split, whole, on a disk that ignores case (#1183)

The mount rows make each set one file on any disk (hard links to the twin
git writes last), as #1183's trace measured on a disk that ignores case.
At head 10 passed, 4 skipped (the FUSE-only rows, on a disk that keeps
case); at base 4d40ce9 8 failed: the six mount rows (a wrong blob, or
`CommandFailed` at `git rm -- A`), the `-f` command row and the write-back
row. The no-split command row and the control pass at both.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1189 (fix/1161-check-names-case-twins) was restacked onto main by merges
and gained two commits of its own since 4d40ce9: `plan-case-twins` in the
root leaves to the NOTE only two paths of one blob whose names differ only
in their last part, and its rows. No conflict: git merged the comment above
`plan-case-twins`, where both sides changed a paragraph, by itself.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t rm` git refuses is named with `-f`, and that the mount keeps case twins whose names are not UTF-8 with their own blobs, reading the index and handing the kept twin back as bytes (#1183)

Mutants of ef18f05's new lines left three that change what the tool does
alive: `_git_rm` naming `GIT_RM` in its `CommandFailed` where it ran
`GIT_RM_FORCED`, and `_split_case_twins` decoding the listing, or
`_write_from_the_index` encoding the names, as strict UTF-8, which raises a
`UnicodeError` mounting the legs, after both are pushed.

`z_rows` reads git's NUL-ended rows as `_git_path` reads a path, so
`an_assembly` and `staged` hold a name that is not UTF-8; `an_assembly`
takes the function that adds the paths, and `with_names_not_utf8` adds them
through the index, as `with_paths` hands `git mktree` text. The row skips
where the disk takes no such name.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.2.1 milestone Oct 23, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates _mount_the_legs to safely assemble plans that split or drop case twins on case-insensitive disks: affected paths are force-removed and root-kept twins are restored from the index, with byte-safe handling and comprehensive regression tests while preserving behavior for unaffected sources.

Sequence diagram for preserving split case twins during leg mounting

sequenceDiagram
    participant Mount as _mount_the_legs
    participant Index as Git index
    participant Git as git
    participant Disk as Case-insensitive disk

    Mount->>Index: git ls-files -z
    Index-->>Mount: source paths
    Mount->>Mount: _split_case_twins
    loop removed paths
        Mount->>Git: _git_rm(path, forced)
        Git->>Disk: git rm -r -q -f -- path
    end
    opt root-kept twins
        Mount->>Git: _write_from_the_index(paths)
        Git->>Index: checkout-index -f -z --stdin
        Index-->>Disk: write kept twin bytes
    end
    Mount-->>Mount: continue staging and verification
Loading

Flow diagram for case-twin handling during assembly

flowchart TD
    A[_mount_the_legs] --> B[git ls-files -z]
    B --> C[_split_case_twins]
    C --> D{Path at or under a case-twin set?}
    D -->|Yes| E[_git_rm with -f]
    D -->|No| F[_git_rm without -f]
    E --> G{Twin kept in root?}
    F --> H[Continue mounting]
    G -->|Yes| I[_write_from_the_index]
    G -->|No| H
    I --> H
    H --> J[Stage split and verify adoption]
Loading

File-Level Changes

Change Details Files
Make mounting case-twin splits safe on case-insensitive filesystems by identifying affected twin sets from the Git index and restoring root-kept entries after removals.
  • List index paths once and select twin sets containing paths at or below removed paths.
  • Use forced literal git rm only for removals intersecting those twin sets, preserving existing removal behavior elsewhere.
  • Rewrite root-kept twins from the index with NUL-delimited git checkout-index after all removals.
  • Preserve byte-oriented path handling, including non-UTF-8 names, and report write/removal failures as CommandFailed.
adopt-project.py
Add focused regression coverage for command selection, index restoration, path encoding, and complete adoption scenarios.
  • Verify split twins retain their individual blobs across repository moves, drops, folder/file overlaps, and both-drop cases.
  • Verify unaffected sources use the original git rm sequence with no checkout step.
  • Verify forced-removal and checkout-index failures include the executed commands and Git output.
  • Exercise non-UTF-8 twin names and full execute controls; skip filesystem-dependent cases when the disk preserves case.
tests/test_adopt_check_throwaway_repos.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-24T14:47:26Z) r1237, lane 1's independent reviewer (not the writer), reviewed #1237 at c9f0cc18: NOT READY, with one MUST, one SHOULD and one NIT.

The full review

r1237: independent review of PR #1237 at c9f0cc18

Verdict: NOT READY. One finding is a MUST: on a disk that ignores case, the new forced git rm deletes files outside the work directory through a symlink, and execute still exits 0 with "adoption verified". At base (58aa9d2b) the same plan is refused at git rm and nothing is deleted.

This is a partial report. Lane 1 sent LAST STEP at 14:42Z. Three read-only runs are still going; they are listed in checkpoint.md.

Findings

MUST 1: git rm -f deletes through a symlink left by the clone, outside the work directory, and execute exits 0

Where: adopt-project.py:14338 (GIT_RM_FORCED), :14352 (removal = GIT_RM_FORCED if forced else GIT_RM) and :14455-14457 (the forced loop). The body's "Why" bullet "-f skips only git's question whether the file on the disk still holds what the index holds" is also wrong.

The shape that triggers it:

  • The source has a folder D/ and a symlink d that leads to a directory, so the two are a set to _case_twin_sets.
  • The plan sends D/ to a leg (or drops it) and keeps d in the root (or sends it elsewhere).
  • check prints plan ok.
  • The clone writes d last, so D is the symlink on such a disk.
  • git rm -r -f -- D then unlinks D/x through the symlink, in the folder the symlink points to.
  • Without -f, git refuses it as "local modifications".

Evidence 1: git 2.43 on the bench disk. The repo has D/x tracked and D replaced by a symlink to victim/:

--- not forced:  error: the following file has local modifications: D/x   exit 1   victim: x kept
--- forced:      exit 0   victim: x gone

Evidence 2: execute whole on the FUSE disk that ignores case. My bench is r1237/fuse/bench.py. The victim/ folder is outside --work-dir.

scenario head c9f0cc18 base 58aa9d2b
D/ code, d→abs victim root check plan ok; execute exit 0, adoption verified; victim after: keep only (x deleted) execute exit 2, REFUSED mounting the legs at git --literal-pathspecs rm -r -q -- D, "local modifications: D/x"; victim intact
D/ drop, d→abs victim root exit 0, verified, x deleted not run yet (batch2 b3)
D/ code, d→../../victim root exit 0, verified, x deleted not run yet

Logs: runs/fuse-h2-symvictim.txt, runs/fuse-b2-symvictim.txt and runs/fuse-h3.txt.

Evidence 3: the same on CI's kind of disk. The probe is probes/probe_symlink_row.py, which calls _mount_the_legs with D made a symlink by hand:

  • head: mount: returned, victim after: ['keep'];
  • base: mount raised CommandFailed - ['git', '--literal-pathspecs', 'rm', '-r', '-q', '--', 'D'], victim after: ['keep', 'x'].

Fix:

  • Before a forced removal, check with os.lstat every leading folder of each index path at or under it. Use the module's _is_a-style helper, not a bare guard.
  • Where a leading folder is a symlink, remove that path from the index only (git rm --cached -r). git counts a path beyond a symlink as absent from the work tree, and git add -A does not follow it. The other option is to refuse there.
  • Add a row on the bench disk in the shape of the probe: D/x code, d a symlink to a folder outside the assembly, D replaced by that symlink. It should assert that the outside file survives and that D/x leaves the index. That row fails at head today.

SHOULD 1: plan-case-twins' sentence is now stale, and the PR leaves it so

I measured the claim in the brief and it holds. On FUSE, with _case_twin_findings patched to name no finding:

  • At base (bn): legcode, legspec, legcodedir and legsrc gave execute exit 2, REFUSED mounting the legs at git rm of A, A, D and src. legff verified.
  • At head (hn): legcode, legspec, legcodedir and legff gave execute exit 0, adoption verified. legsrc is still running.

So no set kept in one repository can now stop at git rm. Root sets never reach git rm at all.

These places must change, and the PR changes none of them (its body says so under "Not in this PR"):

  1. adopt-project.py:9418-9422, the finding as printed: "...execute could stop there before any leg exists, at git's will not add file alias, or after both legs are pushed, as adopt-lost or at git rm mounting the legs, so execute refuses them on every disk." Drop the bolded clause. I have not yet measured whether "after both legs are pushed, as adopt-lost" still holds, and for which repository; it may only hold for root sets. That check is the queued hr run (root, rootff and rootdir with no finding, at head).
  2. tests/test_adopt_check_throwaway_repos.py:537-543 (twins_kept) pins the same words, ending with "git rm mounting the legs" at :542. It must change in step with item 1.
  3. adopt-project.py:9266-9270, a comment: "...in 3 others, for adopt-project.py: check names the case twins the plan keeps in one repository as plan-case-twins on every disk, a one-blob set as a NOTE in a leg and, in the root, two paths in one folder, and execute refuses the finding before any leg (#1161) #1189's review, execute stopped after both legs were pushed, mounting them, at git rm -r (...)". This is history; add "until adopt-project.py: on a disk that ignores case, execute loses a case twin the plan keeps in the root when its twin goes to a leg #1183".

No doc file names plan-case-twins. A git grep finds it only in adopt-project.py, the throwaway test file and test_adopt_make_reads_first.py, and the last of these does not quote the clause. #1189's review (r1189c) expected #1237 to update the sentence.

NIT 1: _split_case_twins' docstring (adopt-project.py:14374) is too narrow

It says the function returns "a set the plan sends into different repositories, or drops a path of". It also returns sets kept whole in one leg, such as the one-blob NOTE sets in a leg, and those are removed with -f too. The hn runs above take that path.

NOTE (landing): closingIssuesReferences is []

The PR's base is fix/1161-check-names-case-twins, and GitHub does not link closing issues for a non-default base. Once #1189 lands and this PR is retargeted to main, check that Closes #1183 links. Also note that #1189 is still a DRAFT, at 58aa9d2b.

Commands and summary lines (probe env: stub gh, GH_TOKEN=bogus-r1237, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, EMAIL=r1237@openreposhape.invalid, env -u LANES_LANE)

  • Head, tests/test_adopt_check_throwaway_repos.py (whole file): 103 passed, 6 skipped in 258.05s. This matches the body.
  • Base tool (58aa9d2b's adopt-project.py, blob 5fac9479) with head's tests, the 16 rows in adopt-project.py: on a disk that ignores case, execute loses a case twin the plan keeps in the root when its twin goes to a leg #1183's section: 10 failed, 2 passed, 4 skipped. This matches the body's table, failure reason for failure reason:
    • wrong blob: across, dropped, a-folder-kept-beside-a-file and the non-UTF-8 row;
    • CommandFailed: git --literal-pathspecs rm -r -q -- A: across-the-other-way, both-dropped and one-to-each-leg;
    • the command list without -f;
    • no _write_from_the_index;
    • _git_rm() takes 2 positional arguments.
  • tests/test_scaffold_leg_path_collisions.py: 104 passed.
  • tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py: 107 passed, 2 skipped.
  • Doc trio: 171 passed.

Mutants (my own; clone r1237/mut; #1183's 16 rows; each restored in a finally, blob equal to HEAD's 13579470)

id mutant result killed by
r01 GIT_RM_FORCED without -f KILLED across-the-other-way, both-dropped, one-to-each-leg, cmd-order row, forced-refusal row
r02 _at_or_under with no / boundary KILLED test_a_source_with_no_twin_set_split_is_mounted_as_before
r03 _at_or_under matches the path itself only KILLED across, across-the-other-way
r04 forced flag with its arguments swapped KILLED across-the-other-way
r05 kept = every twin of a split set KILLED 9 rows
r06 no write-back KILLED across, dropped, folder-beside-file, cmd-order, non-UTF-8
r07 write-back before the git rm loop KILLED test_only_a_path_a_twin_is_at_or_under_is_removed_with_f only
r08 write-back names encoded as strict UTF-8 KILLED non-UTF-8 row
r09-r16 the listing decoded with replace, a set split only by its first twin, checkout-index without -f, the unforced command named, a write-back failure ignored, every path forced, every set taken as split, the unforced command run still running (PID 2834213) n/a

So far: 8/8 killed, with 8 restores True. On r07: the hard-link simulation alone does not catch the order. Only the row that checks the command order kills it, so that row is load-bearing.

The writer's tally reconciles with its own logs. w1183/runs/mutants-ef18f059.out shows 34/40, with M05, M07, M16, M21, M22 and M23 surviving. w1183/runs/mutants-ef18f059-r2.out shows "killed 37/40; survivors: M07 ..., M16 ..., M22 ...", and every restore line shows blob 13579470. I agree that the three survivors are equivalent:

  • _git_rm's only caller (:14456) always passes forced;
  • a set of one name "" is never kept;
  • CHECKOUT_INDEX is a constant command made only of text.

Body checks

  • Title: 249 characters (cap 250).
  • Closing form: exactly one, Closes #1183 (body line 111). My regex scan for close/fix/resolve/cover followed by #N finds nothing else, and the Sourcery block holds none.
  • Stacking: the statement is right. c49de697 has parents a455c759 and 58aa9d2b, and the diff against 58aa9d2b is the two named files (438+/20−).
  • Gate counts: the four I re-ran match. I did not re-run test_adopt_make_reads_first.py (33) or the group of five files (316).
  • The "Why" bullet on -f is false (MUST 1).

Not covered

  • Mutants r09 to r16 are running (PID 2834213; log r1237/runs/mutants.txt).
  • FUSE batches are still running:
    • PID 2828083 (batch1): hn legsrc, then head unpatched with legcode and root;
    • PID 2862988 (batch2): syminside, rootnote3 (two one-blob twins in the root plus a third sent to code), base runs of the symlink variants, a re-check of the body's FUSE table at head, symboth, and root, rootff and rootdir with no finding at head and base.
    • Logs are under r1237/runs/fuse-*.txt.
  • The 3.14-like shim run was not made. The shim is written in r1237/shim/, and the PR adds no pathlib checks.
  • Not run: test_adopt_make_reads_first.py, test_adopt_git_rm_bytes.py (the mount in the C locale), the other mount files and the four test_shape_doctor.py shards.

State

  • The review checkout /workspace/projects/openRepoShape-worktrees/l1-r1237 is clean (git status --short is empty).
  • The checkpoint is /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1237/checkpoint.md.
  • The scratch clones are mut, base and fz, in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1237/. base carries base's tool on purpose, and fz holds untracked copies of the tool.
  • I pushed nothing and posted nothing.

Lane: openreposhape-1 (openRepoShape-1)

This branch has not been deployed

No deployments
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.

1 participant