Repository navigation
shape-doctor.py: a repository whose only remote is not named origin is offered the adoption line plan takes, --project Thing with no --org, where it was --project WorkUpstream --org <your-org>, which plan refused adopt-not-the-source; the naming row and the Triad advisory read that remote too (#1292) - #1320
brettheap wants to merge 5 commits into
Conversation
… from its only remote (#1276) Not gated yet (reboot checkpoint). The rows pass on this change (14) and 12 of them fail on main; mutants M1-M5 run (M2 equivalent), M6-M20 not yet run; the adoption gates, test_shape_doctor, the leg-path collisions module and the guards not yet run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s `clone.defaultRemoteName` names it, is read from that remote, so `plan` reads its organisation, default branch and branches where it refused `adopt-no-org`, took HEAD's branch as the default and asked `--tracking-branch` of the source's own branches, and one with several remotes and no `origin` names them and guesses none (#1276) The final commit over the WIP commit. It changes the `_remotes_of` docstring to say `git switch` "would change" the remote the current branch names (only `git ls-remote --get-url`'s flip was measured), and records the gates. - Rows (tests/test_adopt_source_remote_name.py): 14 passed; on main 12 fail and 2 pass (the (c) row and the legacy-`origin` row). - Mutants M1-M20: 19 killed, M2 equivalent (`git remote get-url origin` exits 0 whenever `origin` is listed). - Gates: the adoption groups (1,620 passed, 4 skipped in ten runs, each module with the modules it imports fixtures from), test_shape_doctor (446 passed, 3 skipped), the leg-path collisions (104 passed), and the guards with test_update_add_codes_documented (349 passed, 3 skipped); every skip is the host's (git 2.43, a case-keeping disk, not Windows, GIT_CONFIG_GLOBAL set). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…remote has no longer runs `git fetch origin` in a source that has no `origin` (a source with several remotes is given the rename or a branch made from a remote's, one with no remote none), a remote whose name starts with `-` is refused by name as one that is not printable ASCII is, the `--org` help says what is read, the way past a default branch names the remote read, and the dead constants are gone (#1276) A source with no `origin` and two or more other remotes, and one with no remote, were told `git fetch origin` after `plan-tracking-branch-missing`, which fails there (`fatal: 'origin' does not appear to be a git repository`) one line below "it has no remote `origin`". Where remotes were passed over, the remediation now gives what `adopt-no-org` gives, the rename `git remote rename <name> origin` or `git branch <branch> refs/remotes/<name>/<branch>`; a source with no remote is given no fetch. `check`'s split-branch remediation leaves out its `git fetch --prune origin` sentence the same way. Run through in a copy of the several-remotes source, both ways make the same plan check `plan ok`. A remote named `-x` was refused for the wrong reason (git reads `get-url -x` as an option); it is refused by name, with `--` for its rename. `--org`'s help and docs/cli.md say exactly what is read. The push in `plan`'s way past a refused default branch names the remote read. The unread constants `TRACKING_BRANCH_REMEDIATION`, `REMOTE_LACKS_REMEDIATION` and `LOCAL_REMOTE_LACKS` are removed. Rows: the several-remotes and no-remote ways, an ESC-named and a `-x`-named remote, and `up$(id)` quoted in two remediations. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…org` print takes `--`, so it works for a remote named `-x`; one remote is worded as one, a remote whose name holds an ESC is given no `git branch`, the split-branch row expects the text for a source with no remote, and the dead `ORIGIN_BRANCHES` and four unused defaults are gone (#1276) RV1280b's review of 890976c. CI's `tests` failed on tests/test_adopt_split_branch.py::test_check_says_how_to_go_on_once_after_its_findings: `small_source` has no remote, so `check` leaves out the sentence that sends the person to fetch from an `origin` it has not, and the row counted the text for a source whose remote is `origin`. It expects `_split_branch_remediation(None)` once and no `git fetch` now. `git remote rename <name> origin` is `error: unknown switch` for a remote named `-x`. `check`'s remediation for a tracking branch only a remote has, and `adopt-no-org`'s, print `git remote rename -- <name> origin` now, which works for every name `_remotes_of` passes over. A lone remote is worded as one ("A branch the source's remote `-x` has"). A remote whose name holds an ESC is given no `git branch <branch> refs/remotes/<name>/<branch>`, which git refuses (`_holds_branches` asks `git check-ref-format`), and the sentence says why. Each way, run as printed, makes `check` pass: (d), `-x` alone, and the rename `adopt-no-org` gives for `-x` and for an ESC remote. An `up$(id)` case is added to the row for `_default_branch_way_past` and to a split-branch row, so dropping the `shlex.quote` in either fails a row. The unused `ORIGIN_BRANCHES` and the defaults on `_remote_lacks_remediation`, `_local_remote_lacks`, `_refuse_a_tracking_branch_the_remote_lacks` and `_checked_default_branch` (every caller passes the remote) are removed. The `--org` help says "the one that remote's url names"; docs/cli.md is regenerated. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… is offered the adoption line `plan` takes, `--project Thing` with no `--org`, where it was `--project WorkUpstream --org <your-org>`, which `plan` refused `adopt-not-the-source`; the `naming` row and the Triad advisory read that remote too (#1292) After #1280 (#1276), `adopt-project.py plan` reads a source's only remote where it has no `origin`. The doctor's `origin_org` and `origin_name`, and `shape_advisory.repository_names`, read `origin` alone, so for a clone of `testorg/Thing` whose only remote is `upstream` the way in offered `--project WorkUpstream --org <your-org>`, and that line, filled in as it asks, ended in `REFUSED adopt-not-the-source` (RV1280 S4 on #1280). The rule for which remote is read (`_remotes_of`, with `_remote_exists` and `_printable_ascii`) moves from `adopt-project.py` to `scripts/shape_advisory.py`, which both tools already import and no root copies, and `adopt-project.py` imports it under the names it always used. The doctor's two reads and `repository_names` ask `source_remote` from it. Not `scripts/repo_shape.py`: it is a shape copy every root pins, and a rule added there is an `upstream-changed` row for every project. Not a copy in the doctor held by a parity row: #521's copy of `plan`'s regex drifted. A source with an `origin`, several remotes and no `origin`, a remote named `-x` or not in printable ASCII, or no remote reads byte for byte as before in the doctor's `--json` and text, and `plan` is byte-identical on every probe source. No new command or flag. Rows in tests/test_shape_doctor.py: 14, 3 of which fail at #1280's head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUses of the APIMake the doctor's adoption line, naming row and repository-identity checks ask the shared rule which remote
ImplementationMove remote selection into the shared advisory module so
Carry the selected source remote through adoption planning, branch checks, remediations and source descriptions so a sole non-
DocumentationUpdate the
TestsAdd adoption-source regressions for sole, absent, multiple and unusual remotes, covering branch selection, remediations and source identity.
Update the split-branch remediation assertion for a source with no remote, which must not be told to fetch from
Other changesFiles not grouped.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
(lane openRepoShape-3, 2026-11-10T17:30:08Z) RV1292: READY AFTER FIXES at 9d71536 This is an independent review of #1320 (for #1292). It covers only the PR's own commit, The three choices1. Where the rule lives (
2. Which reads move (all three): right. The body describes the doctor's side plainly but not
3. What was left out: right to leave, and the body says so for both under "Out of scope".
Before and after (my own probe, not W1292's)The probe made 19 one-commit sources and ran at both heads. Setup:
For each source it recorded:
Of 169 outputs per head, 143 are byte-identical. Unchanged in every output (doctor
Changed: only sources with a single remote that is not
In each of these, three more things change, all as they do for
Every change is what #1292 asks for. Rows
My own mutants. Each was one exact replacement, then restored from a backup and checked against HEAD with
GatesAll at
Merges (
|
| with | #1320 9d71536d |
#1280 alone 5cb1abdb |
|---|---|---|
main dc9a0bcc |
conflict b64f1c6ac74f: adopt-project.py, 3 hunks |
conflict d4fb95c7b393: adopt-project.py, 3 hunks |
#1319 be62b704 |
conflict 52eefdfa37d1: adopt-project.py, 3 hunks |
conflict 3e2e3e97a2f7: adopt-project.py, 3 hunks |
- The hunks are adopt-project.py: a source whose only remote is not named
origin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's. I hashed the text from each<<<<<<<to its>>>>>>>with the marker labels removed. All four give the same sha256,80b431f1011322af. This method hashes differently from the body's, so it cannot be compared with itsec61064bd9f21ca8. - Positions: the hunks sit at lines 3555, 3650 and 5753 for shape-doctor.py: a repository whose only remote is not named
originis offered the adoption lineplantakes,--project Thingwith no--org, where it was--project WorkUpstream --org <your-org>, whichplanrefusedadopt-not-the-source; thenamingrow and the Triad advisory read that remote too (#1292) #1320 and at 3620, 3715 and 5818 for adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280 alone: 65 lines apart, as the body says. shape-doctor.py: a repository whose only remote is not namedoriginis offered the adoption lineplantakes,--project Thingwith no--org, where it was--project WorkUpstream --org <your-org>, whichplanrefusedadopt-not-the-source; thenamingrow and the Triad advisory read that remote too (#1292) #1320 adds no conflict of its own. - The PR's other three files merge as the PR changes them. Each file's diff between the two merge trees with main has the same
git patch-id --stableas the PR's own diff:shape-doctor.py:f7a039b43bd9scripts/shape_advisory.py:38aa00c1c071tests/test_shape_doctor.py:68af3a68cf92
- Main since the base: its 41 commits add no
get-url originread toshape-doctor.pyorscripts/shape_advisory.py.
Body
Closes #1292is the only closing keyword, including in the Sourcery section, andclosingIssuesReferencesreads[1292](checked again at the end).- It is ASCII except line 155, the robot line (U+1F916).
- The numbers I measured again match the body: 14 rows; 3 fail and 11 hold at the parent;
14 passed; the conflict hunks 65 lines apart; and every line number except one (N1). - It covers what a person sees change, the choices, and the restack plan, except for what S1 and S2 add.
Findings
MUST: none.
S1 SHOULD (body, and the squash message): plan and check change too.
repository_namesis also whatplanandcheckread for their Triad advisory (_print_triad_advisory,adopt-project.py:6227).- The body says
planis byte-identical "on every probe source". That is true for its 11 sources, but not in general. On the path-leg source above,plan --project Atlas --org testorgnow prints the leg's NOTE instead of thetriadlines (exit 0 at both heads), just as the same source withorigindoes. The change is right, but nothing in the body says it. - Replacement 1: replace the third bullet under "Which reads move" with:
shape_advisory.repository_names(scripts/shape_advisory.py:631): the Triad advisory and leg detection, in the doctor and inadopt-project.py planandcheck(_print_triad_advisory,adopt-project.py:6227). Moves. Measured with theAtlas-codeclone above: left onorigin, the doctor offers to adopt a leg, a lineplanrefuses. Oneplanoutput changes with it: for a source whose only remote is a path naming a leg (upstream=/srv/git/testorg/Atlas-code.git), whichplantakes with--project Atlas --org testorg,planprints the leg'sNOTEwhere it printed the Triad advisory, exit 0 both, as for the same source withorigin; and the doctor, which offered that source--project <Folder> --org <your-org>, a lineplantook, now gives the leg's row and no NEXT.
- Replacement 2: at the end of choice 1, replace "
planis byte-identical on every probe source (below)." with:planis byte-identical on every probe source (below); the oneplanchange isrepository_names's, under "Which reads move". - Replacement 3: after "All 33 runs are byte-identical, exit and output (
W1292-scratch/plan-same.txt).", add:The move itself changes no
planoutput;repository_nameschangesplan's andcheck's advisory line for a source whose only remote is a path naming a leg (above), which none of these 11 sources is. - Squash message: replace "and
planis byte-identical on every probe source." with:and
planis byte-identical on every probe source but one whose only remote is a path naming a leg, where its Triad advisory becomes the leg's NOTE, as fororigin.
S2 SHOULD (body): two more visible changes for the upstream clone. After the the way in bullet under "What changed", add:
the other way inofferssetup.sh --org <your-org> --project Thing, where it offered--project WorkUpstream, as fororigin.- In
--json, the way in'sorigin_organd thenamingrow'soriginkeep their names and, where there is noorigin, carry the only remote's organisation and repository name (testorg,Thing).
Merge line (body).
- Replace "with main
f2a6527e9and with each open PR" with:with main
dc9a0bccand with each open PR - Replace the main row with:
| main
dc9a0bcc| conflictb64f1c6ac:adopt-project.py, 3 hunks | conflictd4fb95c7b:adopt-project.py, 3 hunks | - Add:
| validate-pins.py and validate-family.py: a leg's or member's git-directory
HEADthat is a FIFO, a socket or a device, or a symlink to one, is refused by name before git is asked, where a FIFO left the gate with no answer at all (#1290) #1319be62b704| conflict52eefdfa3:adopt-project.py, 3 hunks | conflict3e2e3e97a:adopt-project.py, 3 hunks | - I did not re-measure the other PR rows.
N1 NIT: scripts/shape_advisory.py:628 should be :631, where def repository_names is at this head.
N2 NIT, a coverage gap that comes from #1280: no row holds the surrogateescape decode (MC survives). The input is reachable: git config remote.<byte 0xff>.url https://github.com/testorg/Thing.git gives a remote whose name is the byte 0xff.
- At this head,
plan --project Thingon that source exits 2:REFUSED adopt-no-org: ... its one remote, `\udcff`, is not read in that one's place, as its name is not printable ASCII. - Under MC it exits 1 with
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0. - A row in
tests/test_adopt_source_remote_name.pywould hold it, either here or in a follow-up. Separately,\udcffis a Python escape printed raw; that spelling is adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's.
N3 NIT (optional): the header of scripts/shape_advisory.py still describes only the advisory ("never a gate ... nothing here returns an exit code"). remotes_of now decides plan's adopt-no-org and adopt-not-the-source refusals. This does not break the doctrine: the ratified list names review lanes, checks, validators, floors, councils and merge gates, not plan. One sentence in the header pointing to the new section would stop the next reader from moving the rule out on that doctrine's account.
N4 NIT (a follow-up, not this PR): for a source whose only remote is upstream = /srv/git/testorg/Thing.git, the NEXT at this head is --project Thing --org <your-org>, with WHY "this repository's origin names none that plan can read". The doctor actually read upstream there. Before this PR it read origin, so the sentence was true. I agree with leaving it here, because changing it would alter the bytes for every source with no organisation. It is worth filing.
Not this PR's (an observation): for a remote naming testorg/sam-wip (the workspace form), the doctor offers --project SamWip with origin at both heads, and now with upstream too. plan refuses that line adopt-not-the-source, and refuses --project sam-wip with naming-role-mismatch. My test folders' names also ended in -wip, so the folder may be what classified it. Lane 3 may want to check whether this is filed.
Not measured, and commands for lane 3's main loop
- The doctor gate. W1292 reports
509 passed, 3 skipped in 959.09sat this head; I did not run it:
cd /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292 && /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292-scratch/envrun.sh /dev/null /usr/bin/python3 -m pytest -q -p no:cacheprovider -rs --basetemp=/tmp/claude-1000/l3bt/RV1292-doctor tests/test_shape_doctor.py tests/test_doctor_unsearchable_links.py > /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292-scratch/gate-doctor.log 2>&1 - Optional. Neither W1292 nor I ran these.
- The doctor's NEXT rows, with the modules they import from:
... pytest ... --basetemp=/tmp/claude-1000/l3bt/RV1292-next tests/test_doctor_next_is_a_command.py tests/test_commit_trailers.py tests/test_doctor_legs_members_next.py tests/test_doctor_validator_rows.py tests/test_shape_doctor.py tests/test_doctor_gitmodules_not_utf8.py - The origin-repository rows:
... pytest ... --basetemp=/tmp/claude-1000/l3bt/RV1292-originrepo tests/test_adopt_origin_repository.py tests/test_adopt_project_is_the_source.py
- The doctor's NEXT rows, with the modules they import from:
Paths
- Clone:
/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292, detached at9d71536dand clean. Its worktree/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292-scratch/parentis detached at5cb1abdb. - Scratch:
/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1292-scratch:- scripts in
tools/:probe.py,mutate.py,swaps.sh,gates.sh,mt.sh,mc_probe.py; - probe outputs in
out2/; - logs:
swaps.log,gates.log,gate-moved.log,gate-patched.log,gate-guards.log; - merge trees and hunks in
mt/.
- scripts in
- Basetemps:
/tmp/claude-1000/l3bt/RV1292-rows,RV1292-src,RV1292-moved,RV1292-patchedandRV1292-guards. - I read W1292's probe only to see which sources it used, never as evidence. I ran nothing in W1292's clone or scratch.
Lane 3: the gates RV1292 left run now from lane 3's main loop with RV1292's runner at 9d71536d, one pytest at a time:
tests/test_shape_doctor.pywith every module that imports its fixtures;- the origin-repository rows.
Lane 3 takes S1, S2, the merge line and N1 into the body, word for word, once they pass. Not taken, and why:
- N2: lane 3 adds the
surrogateescaperow to adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280, the PR whose decode it holds, at adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's merge round. - N3: it would move the reviewed head.
- N4 and the
sam-wipobservation: follow-up candidates for Brett Heap.
|
(lane openRepoShape-3, 2026-11-10T17:53:13Z) READY at
So RV1292's verdict holds: READY AFTER FIXES, with the fixes to the body only. Lane 3 took S1, S2, the merge line and N1 into the body word for word, and added an "Independent review" section. Not taken:
|
|
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 4 days and 19 hours by commenting |



What changed
A v1.2.2 fix for #1292, a follow-up of lane 3's independent review of #1280 (RV1280, finding S4), filed on Brett Heap's word "file follow-ups 5, 10 and 15" (2026-11-09). It lands after the v1.2.1 tag, under Brett Heap's merge word, once reviewed and green, after #1280.
After #1280 (#1276),
adopt-project.py planreads a source's only remote where it has noorigin. The doctor still readoriginalone, so for a clone oftestorg/Thingwhose only remote isupstream, in a folderwork-upstream,the way inofferedand that line, with
<your-org>filled in as itswhyasks, ended inREFUSED adopt-not-the-source: --org testorg and --project WorkUpstream name testorg/WorkUpstream, and the source is testorg/Thing, exit 2. Now the doctor reads the remoteplanreads, by the ruleplanreads it by:the way inoffers... plan --source .../work-upstream --project Thing, with no--org, the line the same clone withorigingets, andplantakes it (exit 0).the other way inofferssetup.sh --org <your-org> --project Thing, where it offered--project WorkUpstream, as fororigin.--json, the way in'sorigin_organd thenamingrow'soriginkeep their names and, where there is noorigin, carry the only remote's organisation and repository name (testorg,Thing).namingrow classifieswork-upstream, Thing, where it classifiedwork-upstreamalone, as it does fororigin.upstreamnamestestorg/Atlas-codeis the code leg's row (NAMED OR WRITTEN AS A LEG, no NEXT) and the Triad advisory is not given, as for the same clone withorigin. Before, the doctor called it a single repository and offered--project WorkUpstreamLeg --org <your-org>, whichplanrefusedadopt-not-the-source(the source istestorg/Atlas-code).origin(whatever other remotes it has, and one kept as a file in.git/remotes), several remotes and noorigin(the NEXT keeps--org <your-org>; nothing is guessed), a single remote named-xorcaf+ U+00E9 (passed over as adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280 passes it over), and no remote.No new command or flag.
The choices, and why
How the doctor and
plancome to ask one question: the rule moves toscripts/shape_advisory.py. The issue offered two places; I measured a third and took it.scripts/repo_shape.py, besideurl_repository: it is a SHAPE COPY that every assembly root and family holder pins by digest. A rule added there puts anupstream-changed scripts/repo_shape.pyrow in front of every project in the estate, until each runsupdate-shape.py apply, for code no root's own copy calls.scripts/shape_materialize.py:3131-3134says this of its own helpers, andscripts/shape_advisory.py:77-82of itself. In a patch, that is a re-pin for every project.plan's by a parity row: no shape copy changes, but the row holds only the layouts it lists.origin_org's docstring records adopt-project.py: read<org>/<repo>from everyorigingit accepts (a local path, a Windows backslash path, a trailing/or.git/) #521: the doctor's copy ofplan's regex, held by a test, would have sent a person to a lineplanrefuses whenplanchanged.scripts/shape_advisory.py: in no copy list, and both tools already import it (adopt-project.py:268,shape-doctor.py:500). One rule by construction, and no shape copy changes. The cost is a move of adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's helpers out ofadopt-project.py:_remotes_of,_remote_existsand_printable_asciiareshape_advisory'sremotes_of,remote_existsandprintable_asciinow, imported under the old names (adopt-project.py:269-280)._remote_namesand_readable_remote_namemoved with them, unchanged.planis byte-identical on every probe source (below); the oneplanchange isrepository_names's, under "Which reads move".The choice and its reasons are written where the next writer reads them: the section comment at
scripts/shape_advisory.py:508-529, the import's comment atadopt-project.py:269-276, and theorigin_organdorigin_namedocstrings.Which reads move: all three that read a repository's own identity. Measured on the parent's head; the line numbers are this head's:
origin_org(shape-doctor.py:11434): the way in's--org. Moves (S4).origin_name(:11301), throughrepo_local_origin_name(:11412): the way in's--projectand thenamingrow (check_not_a_root_naming,:11483). Moves. Thenamingrow waswork-upstreamalone; it now classifies the remote's name too, as fororigin.shape_advisory.repository_names(scripts/shape_advisory.py:631): the Triad advisory and leg detection, in the doctor and inadopt-project.py planandcheck(_print_triad_advisory,adopt-project.py:6227). Moves. Measured with theAtlas-codeclone above: left onorigin, the doctor offers to adopt a leg, a lineplanrefuses. Oneplanoutput changes with it: for a source whose only remote is a path naming a leg (upstream=/srv/git/testorg/Atlas-code.git), whichplantakes with--project Atlas --org testorg,planprints the leg'sNOTEwhere it printed the Triad advisory, exit 0 both, as for the same source withorigin; and the doctor, which offered that source--project <Folder> --org <your-org>, a lineplantook, now gives the leg's row and no NEXT.Not moved:
origin_url(shape-doctor.py:10419), which reads a sibling's and the holder'soriginasscripts/siblings.pyreads it ((no origin)). It is themake siblingsquestion, notplan's.Measured, before and after
Probe: a script in my scratch (
W1292-scratch/probe.py, not committed), on git 2.43.0 and Python 3.12, under a stubghandGIT_SSH_COMMAND=false. Each source is a one-commit repository whose remotes are written withgit config.testorg/Thingishttps://github.com/testorg/Thing.git. For each source and each head, the doctor ran--root <src> --jsonand the text report, andplanran the doctor's own NEXT with<your-org>astestorg. Digests are sha256 (12 hex) of the output with the checkout's path written<SHAPE>.5cb1abdb: json / text / planorigin)origin,fork)upstream, andoriginas a.git/remotesfile)origin=Atlas-code)upstream,fork)-x)caf+ U+00E9)upstream)adopt-not-the-sourceupstream=Atlas-code)adopt-not-the-sourceupstream=/srv/git/testorg/Thing.git)--project Thing --org <your-org>, namingwork-upstream-path, Thingplanitself, moved code:plan --source <src>with--project Thing, with--project Thing --org testorg, and with neither, on all 11 sources at both heads. All 33 runs are byte-identical, exit and output (W1292-scratch/plan-same.txt). The move itself changes noplanoutput;repository_nameschangesplan's andcheck's advisory line for a source whose only remote is a path naming a leg (above), which none of these 11 sources is.The
work-upstreamNEXT, before and after:Rows
14 rows in
tests/test_shape_doctor.py, the module that holds the doctor's way-in rows (#545's and #521'stest_the_adoption_line_names_an_org_only_where_plan_reads_none) and itsnamingrows. They sit right after the #545 rows.test_the_adoption_line_reads_the_remote_plan_reads(6 layouts:only-upstream,only-origin,origin-and-fork,two-and-no-origin,starts-with-dash,not-printable-ascii): the whole NEXT,origin_org,suggested_project, thenamingrow's names andrepository_names.test_plan_takes_the_adoption_line_as_the_doctor_fills_it(the same 6): the parity row. The NEXT's--project, with--org testorgwhere the NEXT asks for one, is run throughplan: noadopt-not-the-source, exit 0. Then the doctor's organisation isplan's_remote_repository's.test_a_clone_whose_only_remote_names_a_leg_is_offered_no_adoption(origin,upstream).With #1280's
shape-doctor.py,adopt-project.pyandscripts/shape_advisory.pyswapped in (restored and checked withcmp), 3 fail and 11 hold:The parity row fails there on S4's own line:
REFUSED adopt-not-the-source: --org testorg and --project WorkUpstream name testorg/WorkUpstream, and the source is testorg/Thing. On this branch:14 passed, 449 deselected in 11.09s.Mutants
Each was applied to the branch, run, then restored and checked with
cmp.originalone again (origin_organdorigin_name)reads_the_remote_plan_reads[only-upstream],plan_takes...[only-upstream]originguessed among (return names[0], [])[two-and-no-origin]and[not-printable-ascii]in both parametrized rowsrepository_namesreadsoriginalone againreads_the_remote_plan_reads[only-upstream],leg..._no_adoption[upstream][not-printable-ascii]in both parametrized rows-is readtests/test_adopt_source_remote_name.py:..._cannot_name_is_not_read[leading-dash],..._named_like_an_option_passes_check[branch],[rename](my rows read no organisation either way:git remote get-url -xis an unknown switch)remote_existsalways true (anoriginassumed)[only-upstream]twice,leg..._no_adoption[upstream]); 20 of #1280'sremote_existsalways false (a.git/remotesoriginmissed)test_a_legacy_origin_is_read_beside_another_remoteamong themM1 to M4 and M6 ran against the 14 rows, M5 to M7 against
tests/test_adopt_source_remote_name.py(28 rows), on this head9d71536d1.Gates
One pytest at a time, under a stub
gh(exit 97),GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null, git-filter-repo 2.47.0 on PATH, andLANES_LANEunset:tests/test_shape_doctor.py -kthe three):14 passed, 449 deselected in 11.09s.tests/test_shape_doctor.pywithtests/test_doctor_unsearchable_links.py, which imports its fixtures and holdsorigin_org's,repo_local_origin_name's andrepository_names's rows for a.gitthis user cannot reach, in ONE run, in the background with a log:509 passed, 3 skipped in 959.09s (0:15:59). Skipped:[3] tests/test_shape_doctor.py:10643: the PowerShell line is printed, and powershell.exe exists, only on a Windows host.origin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's two test modules and the advisory's, for the moved rule:tests/test_adopt_source_remote_name.py tests/test_adopt_split_branch.py tests/test_shape_advisory.py:122 passed in 266.85s (0:04:26).adopt-project.pychanges:tests/test_scaffold_leg_path_collisions.py:104 passed in 5.64s.tests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_agents_md_codes.py tests/test_git_output_decoding.py tests/test_stdout_encoding.py tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py tests/test_update_add_codes_documented.py:470 passed, 3 skipped in 99.26s (0:01:39). Skipped:tests/test_stdout_encoding.py:285: the default this is about is Windows' own;[2] tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one.tests/test_doctor_next_is_a_command.py. Its<your-org>rows are for repositories with no remote, which read byte for byte as before (work-noneabove), and it imports from four other test modules.All on this head,
9d71536d1, with /usr/bin/python3 3.12 and git 2.43.0.Merges
git merge-tree --write-treeof this head (9d71536d153ac429d3e24547e54349de5857816c), and of #1280's head alone, with maindc9a0bccand with each open PR that touches a file this PR changes:dc9a0bcc(RV1292's measure)b64f1c6ac:adopt-project.py, 3 hunksd4fb95c7b:adopt-project.py, 3 hunksbe62b704(RV1292's measure)52eefdfa3:adopt-project.py, 3 hunks3e2e3e97a:adopt-project.py, 3 hunks8bdc1efd4(shape-doctor.py,tests/test_shape_doctor.py)29f6d728b:adopt-project.py, 3 hunks4b0a66917:adopt-project.py, 3 hunkscf6585063e39ab2c64:adopt-project.py, 3 hunks02b6e997f:adopt-project.py, 3 hunkse5012593aa371825fa:adopt-project.py, 3 hunksb99122912:adopt-project.py, 3 hunks8649430ce2f435a5c9fcdc6a73f28f6f6694fa6ea2e26:adopt-project.py, 3 hunks7479d8cf3:adopt-project.py, 3 hunksdf942efee(tests/test_shape_doctor.pytoo)4459de12a2b64970fe2e020b458d730645c0:adopt-project.py, 3 hunkscb512f264:adopt-project.py, 3 hunks129f4dce8(scripts/shape_advisory.py)01f646441:adopt-project.py, 3 hunks50f0f1c19:adopt-project.py, 3 hunkscf07ba0d10d23ce03b:adopt-project.py, 3 hunks9e3f3e53e:adopt-project.py, 3 hunks86ab76db4(shape-doctor.py)b69513d8b:adopt-project.py, 3 hunks022383580:adopt-project.py, 3 hunks39b21c07e04ce85780:adopt-project.py, 5 hunks943c59e55:adopt-project.py, 5 hunks3e5d1fde172519158e:adopt-project.py, 3 hunks3bbc733ff:adopt-project.py, 3 hunksfa6d189ffa520055c6:adopt-project.py, 3 hunks36ba82aac:adopt-project.py, 3 hunkscc230f2f18942ac91d1359b45e3ddc21fb5397037615c:adopt-project.py, 3 hunks00f7bab94:adopt-project.py, 3 hunksce1dd661f14aeb13a0583342d5bEvery conflict is #1280's own: each names
adopt-project.pyalone, with the same hunks for this head as for #1280's head alone, 65 lines higher (the lines this PR moves out sit above them). Their text, markers' labels aside, is byte-identical both ways: sha256ec61064bd9f21ca8for the 3 hunks against main and every PR but #1265, and2a9de60bec963b69for #1265's 5.shape-doctor.py,scripts/shape_advisory.pyandtests/test_shape_doctor.pymerge clean against every PR, #1316, #1304, #1295 and #1284 included. The restack onto #1280's squash resolves these hunks once, for both.Out of scope
shape-doctor.py:12084-12085: the way in'swhystill says "this repository'soriginnames none thatplancan read" for every repository whose NEXT asks--org. For one whose only remote isupstreamand names no organisation (a path, aswork-upstream-path), the remote it means isupstream. The words are unchanged, byte for byte, and the issue does not ask for them.shape-doctor.py:10419origin_url, the sibling and holderoriginread asscripts/siblings.pyreads it (above).origin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's own reads inadopt-project.py. They moved here unchanged, andplanis byte-identical.clone.defaultRemoteNameitself, which neither tool reads (shape_advisory.remotes_of's docstring says why).Stacked
Stacked on #1280 (head
5cb1abdbc78f3dbe751bb91fa0e5d4f6ddc4cbd8). Its first commits are #1280's; this PR's own change is5cb1abdbc..9d71536d1. Review that range. It lands after #1280, by a restack onto its squash.Independent review
RV1292, lane 3's independent opus review (issuecomment-6774393309), reads READY AFTER FIXES at
9d71536d. The fixes are to the body only, and it found no MUST. It judged all three choices right:scripts/shape_advisory.py;why.Its own probe of 19 sources found every change to be what #1292 asks. Its gates passed:
adopter.git_out: 122 passed;Lane 3's main loop ran the gates RV1292 left, with RV1292's runner at this head, one pytest at a time:
tests/test_shape_doctor.pywith every module that imports its fixtures (tests/test_doctor_unsearchable_links.py,tests/test_doctor_next_is_a_command.py,tests/test_commit_trailers.py,tests/test_doctor_legs_members_next.py,tests/test_doctor_validator_rows.pyandtests/test_doctor_gitmodules_not_utf8.py), 1008 passed, 9 skipped in 1321.62s; andtests/test_adopt_origin_repository.pywithtests/test_adopt_project_is_the_source.py, 53 passed in 34.17s. The 9 skips are the host's: 3 Windows-only PowerShell rows, and 6 rows where git 2.43 crashes or dies and CI's git 2.55 answers.Taken here: S1 (the
planandcheckchange thatrepository_namesbrings), S2 (two more visible changes), the merge line and N1.Not taken:
surrogateescaperow to adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280, whose decode it holds, at adopt-project.py: a source whose only remote is not namedorigin, asclone.defaultRemoteNamenames it, is read from that remote, soplanreads its organisation, default branch and branches where it refusedadopt-no-org, took HEAD's branch as the default and asked--tracking-branchof the source's own branches, and one with several remotes and nooriginnames them and guesses none (#1276) #1280's merge round.sam-wipobservation: follow-up candidates for Brett Heap.Closes #1292
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code
Summary by Sourcery
Align repository remote selection across the adoption tools so repositories without an
originreceive valid adoption guidance and remote-aware diagnostics.Bug Fixes:
originis absent, so shape-doctor adoption commands match whatadopt-project.py planaccepts.originremotes from being incorrectly offered as standalone projects.Enhancements:
origin.Documentation:
originremote and the cases where no remote is selected.Tests:
originremotes, multiple remotes withoutorigin, unusual remote names, legacy remotes, leg detection, and parity between shape-doctor recommendations and adoption plans.