Skip to content

adopt-project.py: each clone execute makes names its remote origin, whatever clone.defaultRemoteName says, so execute no longer ends in REFUSED mounting the legs ("No such remote 'origin'") after both legs are pushed (#1261) - #1268

Draft
brettheap wants to merge 1 commit into
mainfrom
l3-1261-clone-default-remote-name

Conversation

@brettheap

@brettheap brettheap commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What changed

A v1.2.1 follow-up from lane 4's review of #1251 (#1261). It lands under Brett Heap's v1.2.1 merge word (2026-10-23T13:10:26Z) once reviewed and green.

git clone names the remote it makes after clone.defaultRemoteName, and execute addressed its clones as origin. With upstream in the person's global git config, execute --local-remote-dir ended in REFUSED mounting the legs, exit 2, after both legs were made and pushed. Each clone execute makes now names its remote origin, whatever the person's config holds. No flag or command is added.

  • adopt-project.py gains REMOTE_NAMED_ORIGIN = ["-c", "clone.defaultRemoteName=origin"] beside FILE_PROTOCOL, and four git calls carry it:
  • Source.clone, the cloned: true path, runs gh repo clone <org/repo> <dir> -- --quiet --origin origin. gh runs git itself, so no -c of ours reaches its clone. --origin is a git clone flag, which gh passes on after its -- as it passes --quiet. With it, the clone that _default_branch (refs/remotes/origin/HEAD) and _branches_of (refs/remotes/origin/) read names its remote origin. This was not measured against a real gh: the argv is pinned with a stub gh that logs and refuses.
  • Why -c on the git calls and not --origin: git submodule add takes no --origin, so the mount needs the -c in any case. The -c then gives the three git calls one spelling. On git 2.43.0, under a global upstream, it outranks every other scope measured (below).

Measured: plan, check, execute --local-remote-dir

The standard source is tests/conftest.py's make_source_repo (directory Thing, project Northwind), with test_adopt_e2e.py's three answers. It was made once, and each run adopts a copy of it. git 2.43.0, Python 3.12.3, git-filter-repo 2.47.0 (CI's pin, in a venv), with a stub gh (exit 97) first on PATH. GIT_CONFIG_GLOBAL names a scratch file holding the setting, or an empty file for the control.

global config main be21751c3 this PR faa29987a
defaultRemoteName = upstream plan 0, check 0 (plan ok), execute 2: REFUSED mounting the legs plan 0, check 0, execute 0, adoption verified: every source path is in exactly one place
empty (control) plan 0, check 0, execute 0, adoption verified: ... plan 0, check 0, execute 0, adoption verified: ...
defaultRemoteName = a..b plan 0, check 1 adopt-git-unasked, execute 2 REFUSED adopt-git-unasked before any leg plan 0, check 0, execute 0, adoption verified: ...

The end of main's execute under upstream (paths shortened):

  spec    5 path(s) -> 536e214ac536 (2 commits kept) -> .../remotes/Northwind-spec.git
  code    7 path(s) -> 48c2220e5939 (2 commits kept) -> .../remotes/Northwind-code.git
REFUSED mounting the legs. THE EXACT COMMAND WAS:
    git remote set-url origin .../remotes/Northwind-spec.git
    (in .../work/Northwind/spec)
    exit 2
--- output ---
error: No such remote 'origin'
--- end output ---
NOTHING has been rolled back: ... both legs are pushed. ...

And main's check under a..b: FINDING adopt-git-unasked: ... the split's git clone of the source exited 128 there: fatal: 'a..b' is not a valid remote name.

  • The legs. For each pushed leg, a dump of git symbolic-ref HEAD, git for-each-ref and the sorted git cat-file --batch-all-objects --batch-check (52 lines). The dumps are cmp-identical across main's control, main under upstream (pushed before the refusal), and this PR under upstream, the control and a..b.
  • The clones' remotes (git config --get-regexp '^(remote|branch)\.' for each repository in --work-dir). On main under upstream, the assembly, the spec mount and both work clones held remote.upstream.* and branch.*.remote upstream. With this PR they match the control byte for byte once each run's base path is taken out: the assembly and both mounts origin, and the work clones none, since filter-repo removes origin.
  • After the mount. Nothing after the mount names origin. The only origin the post-mount code names is _mount_the_legs' set-url. git submodule sync reads each mount's remote from its branch. _push_the_leg and the split's push go to explicit urls, and gh pr create and gh repo edit name their repository. The run with this PR went through the split's commit, its push and the verification under upstream, to adoption verified.

Measured: which spelling reaches which clone (git 2.43.0, global upstream)

command remote it made
git clone upstream
git clone --origin origin origin
git -c clone.defaultRemoteName=origin clone origin
git submodule add --force -q <repo> <path> (with FILE_PROTOCOL only) upstream; then git remote set-url origin exits 2
git -c clone.defaultRemoteName=origin submodule add ... origin; then set-url origin exits 0
a template config holding defaultRemoteName = fromtemplate fromtemplate; origin with the -c, and with --origin origin
GIT_CONFIG_COUNT naming fromcount, plus the -c origin
GIT_CONFIG_PARAMETERS naming fromparams, plus the -c origin
a..b: plain, with the -c, with --origin origin fatal: 'a..b' is not a valid remote name; origin; origin

Rows

tests/test_adopt_clone_default_remote_name.py is new. test_adopt_e2e.py's module adoption makes a source of its own, and a source made twice is two histories, so the byte comparison needs both runs on copies of one source. Each row gives the setting to its own child only, in a GIT_CONFIG_GLOBAL of its own: the session's file, then the setting.

  • test_the_premise_a_plain_clone_under_the_setting_names_upstream
  • test_execute_under_the_setting_exits_0_and_says_adoption_verified: fails on main with REFUSED mounting the legs.
  • test_the_legs_are_the_bytes_a_run_without_the_setting_pushes: passes on main as well, since both legs were pushed before the mount refused.
  • test_each_clone_execute_makes_has_the_remotes_it_has_without_it
  • test_check_passes_under_a_default_remote_name_git_refuses
  • test_gh_repo_clone_is_handed_origin_for_the_remotes_name: a stub gh, first on PATH, logs one argument per line and exits 97.

On main (adopt-project.py at main's bytes): 4 failed, 2 passed (the premise and the legs rows pass). With this PR: 6 passed.

tests/test_adopt_source_clone_record.py: its exact pin of the gh repo clone argv gains "--origin", "origin".

Mutants

Each part was dropped in turn from this PR's adopt-project.py, the new module was run, and the file was restored, each restore checked with cmp and git diff --quiet HEAD.

mutant result failing row(s)
M1 _extract_leg's clone 1 failed, 5 passed test_each_clone_execute_makes_has_the_remotes_it_has_without_it
M2 the mount's git submodule add 2 failed, 4 passed test_execute_under_the_setting_exits_0_and_says_adoption_verified (error: No such remote 'origin'), and the remotes row
M3 the assembly's clone 1 failed, 5 passed the remotes row
M4 check's rehearsal 1 failed, 5 passed test_check_passes_under_a_default_remote_name_git_refuses
M5 gh repo clone's --origin origin 1 failed, 5 passed; test_adopt_source_clone_record.py -k each_spelling_of_a_name: 12 failed test_gh_repo_clone_is_handed_origin_for_the_remotes_name

Tests run

Each pytest was run one at a time as /usr/bin/python3 -m pytest -q -p no:cacheprovider -rs (-rfs for the chain), with a stub gh (exit 97) first on PATH, then a folder holding only a link to the venv's git-filter-repo 2.47.0, and with GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL an empty scratch file and EMAIL=tests@openreposhape.invalid. Head faa29987a. The new rows and the mutants first ran at 391886b01, whose adopt-project.py differs from this head's in comments only, and the new rows ran again at f741b30f8, which has this head's tree.

  • tests/test_adopt_clone_default_remote_name.py: 6 passed in 14.75s (and 4 failed, 2 passed with adopt-project.py at main's bytes).
  • The docs and hygiene guards, with three more, in one run: tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_git_output_decoding.py tests/test_stdout_encoding.py tests/test_readme_sections.py tests/test_path_guard_hygiene.py, plus tests/test_cli_reference.py (no help text moved), tests/test_no_auto_maintenance.py (it scans every test file for GIT_CONFIG_COUNT) and tests/test_clone_just_pushed.py (the one test that already named clone.defaultRemoteName): 559 passed, 3 skipped in 128.74s.
  • The adoption modules whose tests run execute (git grep -l of "execute", 'execute' or EXECUTE, kept where the module also names ADOPT or adopt-project: 61 modules), tests/test_shape_doctor.py and tests/test_scaffold_leg_path_collisions.py. Each was run as one pytest, the modules that exercise the changed calls first: runs 1 to 3 together with every test module the module imports from, and runs 4 on with each it imports a fixture from (the pairing in which a module-scoped fixture can run twice):
    • tests/test_adopt_source_clone_record.py tests/test_adopt_plan_leg_path_help.py tests/test_adopt_temp_work_roots.py: 154 passed in 61.75s (0:01:01) EXIT=0
    • tests/test_adopt_check_throwaway_repos.py tests/test_adopt_nested_attributes.py tests/test_adopt_relative_submodule_url.py tests/test_adopt_root_symlinks.py: 466 passed, 4 skipped in 838.78s (0:13:58) EXIT=0
    • tests/test_adopt_e2e.py: 27 passed in 29.84s EXIT=0
    • tests/test_adopt_clone_template.py: 31 passed in 39.94s EXIT=0
    • tests/test_adopt_mount_ignored.py: 26 passed in 28.43s EXIT=0
    • tests/test_adopt_clone_claimed.py tests/test_adopt_source_clone_record.py tests/test_adopt_temp_work_roots.py: 151 passed in 90.43s (0:01:30) EXIT=0
    • tests/test_adopt_late_failure.py: 88 passed in 47.19s EXIT=0
    • tests/test_adopt_history_gitlinks.py: 45 passed in 66.50s (0:01:06) EXIT=0
    • tests/test_adopt_verify.py: 31 passed in 32.85s EXIT=0
    • tests/test_adopt_split_branch.py: 47 passed in 123.27s (0:02:03) EXIT=0
    • tests/test_adopt_partial_clone_source.py: 38 passed in 30.68s EXIT=0
    • tests/test_adopt_filter_repo_failure.py: 11 passed in 10.13s EXIT=0
    • tests/test_adopt_relative_submodule_url.py: 34 passed in 140.84s (0:02:20) EXIT=0
    • tests/test_adopt_execute_refusals.py: 181 passed in 279.10s (0:04:39) EXIT=0
    • tests/test_adopt_submodules.py: 167 passed in 227.35s (0:03:47) EXIT=0
    • tests/test_scaffold_leg_path_collisions.py: 104 passed in 6.32s EXIT=0
    • tests/test_shape_doctor.py: 446 passed, 3 skipped in 780.19s (0:13:00) EXIT=0
  • The rest of the chain, runs 18 to 61, finished after this PR opened (CHAIN-DONE at 18:50Z on 2026-11-08). Each run is one pytest, and every one exits 0; four were not run again because each of their files had already run inside an earlier run:
    • run 18, tests/test_adopt_branch_names.py: 29 passed in 41.13s
    • run 19, tests/test_adopt_c_locale_bytes.py: 17 passed in 42.81s
    • run 20, tests/test_adopt_contracts_dir.py: 34 passed in 23.07s
    • run 21, tests/test_adopt_empty_symlinks.py: 53 passed in 85.43s (0:01:25)
    • run 22, tests/test_adopt_entry_paths.py: 79 passed in 74.69s (0:01:14)
    • run 23, tests/test_adopt_eol_block_through_links.py: 13 passed, 2 skipped in 24.79s
    • run 24, tests/test_adopt_git_reads.py: 37 passed in 46.77s
    • run 25, tests/test_adopt_git_rm_bytes.py: 4 passed in 8.78s
    • run 26, tests/test_adopt_github_exits.py: 23 passed in 56.35s
    • run 27, tests/test_adopt_gitmodules_values.py: 187 passed in 145.84s (0:02:25)
    • run 28, tests/test_adopt_ignored_shape_files.py: 37 passed in 39.49s
    • run 29, tests/test_adopt_leg_keys.py: 44 passed in 41.24s
    • run 30, tests/test_adopt_leg_path_collisions.py: 95 passed in 61.55s (0:01:01)
    • run 31, tests/test_adopt_leg_paths.py: 351 passed in 86.79s (0:01:26)
    • run 32, tests/test_adopt_manifest_values.py: 82 passed in 104.78s (0:01:44)
    • run 33, tests/test_adopt_nested_attributes.py: 75 passed in 84.13s (0:01:24)
    • run 34, tests/test_adopt_next_make_reads_first.py: 40 passed in 42.07s
    • run 35, tests/test_adopt_pins.py: 152 passed in 179.49s (0:02:59)
    • run 36, tests/test_adopt_plan_value_order.py: 20 passed in 22.91s
    • run 37, tests/test_adopt_plan_yaml_quoting.py: 540 passed in 194.48s (0:03:14)
    • run 38, tests/test_adopt_printed_sentences.py: 54 passed in 38.78s
    • run 39, tests/test_adopt_root_manifest.py: 27 passed in 15.86s
    • run 40, tests/test_adopt_root_shape_paths.py: 121 passed in 87.24s (0:01:27)
    • run 41, tests/test_adopt_root_symlinks.py: not run again (every file already ran in an earlier run)
    • run 42, tests/test_adopt_shape_pin_and_work_dir.py (with 1 module it imports): 226 passed, 3 skipped in 344.55s (0:05:44)
    • run 43, tests/test_adopt_single_repository_record.py: 39 passed in 19.17s
    • run 44, tests/test_adopt_spec_only.py: 14 passed in 17.11s
    • run 45, tests/test_adopt_spelled_names.py: 27 passed in 8.25s
    • run 46, tests/test_adopt_temp_work_roots.py: not run again (every file already ran in an earlier run)
    • run 47, tests/test_adopt_template_values.py: 78 passed in 64.28s (0:01:04)
    • run 48, tests/test_adopt_tracking_branch.py: 17 passed in 19.74s
    • run 49, tests/test_adopt_unenterable_source_and_locale.py (with 1 module it imports): 40 passed in 16.90s
    • run 50, tests/test_adopt_unsearchable_source.py: not run again (every file already ran in an earlier run)
    • run 51, tests/test_adopt_values_as_typed.py: 40 passed in 30.52s
    • run 52, tests/test_adopt_work_dir_refusal.py (with 1 module it imports): 259 passed, 3 skipped in 347.04s (0:05:47)
    • run 53, tests/test_answered_paths_per_leg.py (with 1 module it imports): 465 passed, 3 skipped in 720.53s (0:12:00)
    • run 54, tests/test_doctor_recorded_answers.py (with 1 module it imports): 475 passed, 3 skipped in 752.61s (0:12:32)
    • run 55, tests/test_github_limits_init_check_org.py (with 2 modules it imports): 264 passed in 65.52s (0:01:05)
    • run 56, tests/test_github_name_limits.py: not run again (every file already ran in an earlier run)
    • run 57, tests/test_leg_path_yaml_scalars.py: 199 passed, 8 skipped in 30.66s
    • run 58, tests/test_ruleset_hint_tracking_branch.py: 23 passed in 34.77s
    • run 59, tests/test_template_text_placeholders.py: 142 passed in 71.49s (0:01:11)
    • run 60, tests/test_template_values_single_pass.py: 192 passed in 14.34s
    • run 61, tests/test_update_shape_merged_away.py (with 2 modules it imports): 494 passed, 3 skipped in 706.68s (0:11:46)

Merges

git merge-tree --write-tree (git 2.43.0) of this PR's head faa29987a with main, and with each of the 27 open PRs that touch any of its files. That set came from one gh pr list --state open --limit 200 --json number,headRefOid,files call, which listed 50 open PRs, and each PR touches adopt-project.py.

Out of scope

Read, not probed: these are clones other tools make and then address as origin. Each is a possible follow-up, and none is changed here.

  • scaffold-project.py:2638-2644 (_build_assembly_root) runs git -c protocol.file.allow=always submodule add --force -q <work clone> <path>, then git remote set-url origin <url> in assembly/<path>. This is the shape of the failing mount here, so scaffold-project.py (and setup-project.py / setup.sh, which drive it) can likely meet the same refusal.
  • setup-project.py:3345-3350 (clone_and_bootstrap) runs git clone -q [--no-local] --recurse-submodules <assembly url> <clone>. origin_recorded_in (setup-project.py:2417, used at :2491) then reads remote.origin.url from <clone>/.git/config, and reads none under the setting. bootstrap.py then runs in that clone; its own reads were not surveyed.
  • scripts/family.py:7288 mounts a member with git submodule add -q --force -- <url> <path>. The member's origin is then addressed by _has_origin (:8879, git remote get-url origin, whose exit 2 is read as "no origin"), _on_a_remote_branch (:8780, refs/remotes/origin/), _origin_tag_reaches (:8930, ls-remote --tags origin) and cmd_bump (:9458, a fetch from origin).
  • scripts/bump-leg.py makes no clone. It reads a leg's refs/remotes/origin/ (:1094-1101) and fetches origin (:1503, used at :1520) in a leg that the person's or bootstrap's clone made, which under the setting has no origin.
  • Found and not a follow-up:
    • setup-project.py:962 clones the shape into a temporary checkout, and no origin use follows.
    • scripts/family.py:6635 and :7389 and update-shape.py:711 make --bare clones, and no origin use of those follows.
    • update-shape.py's refs/remotes/origin/ reads (:5497, :5604, :5910) are of the person's own project clone.
    • scaffold-project.py:2147, :2518, :2653 and scripts/family.py:3324 name origin themselves with git remote add origin in repositories made by git init.

Closes #1261

Lane: openreposhape-3 (openRepoShape-3)

🤖 Generated with Claude Code

…`, whatever `clone.defaultRemoteName` says, so `execute` no longer ends in `REFUSED mounting the legs` ("No such remote 'origin'") after both legs are pushed (#1261)

`git clone` names its remote after `clone.defaultRemoteName`, and
`execute` addressed its clones as `origin`: with `upstream` in the global
config, `_mount_the_legs`' `git remote set-url origin` exited 2 in the
clone `git submodule add` made, after both legs were pushed.

`REMOTE_NAMED_ORIGIN`, `-c clone.defaultRemoteName=origin`, now goes on
`_extract_leg`'s clone, the assembly's clone, `git submodule add` (a `-c`
reaches the clone it makes) and `check`'s rehearsal of `_extract_leg`'s
clone; `gh repo clone` is handed `--origin origin` after its `--`.
tests/test_adopt_clone_default_remote_name.py holds the rows, and
tests/test_adopt_source_clone_record.py's pin of the `gh repo clone` argv
names the new flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Force every Git clone used by execute, its mount creation, and the corresponding check rehearsal to name its remote origin, keeping the rehearsal aligned with execution and preventing later origin operations from failing under clone.defaultRemoteName.

  • adopt-project.py L1123-1135, L11900-11910 (+4 small edits)

Pass --origin origin through gh repo clone so clones created by the GitHub CLI also expose the remote name expected by downstream reads.

  • adopt-project.py L2779

Tests

Add regression coverage for adoption under custom and invalid default remote names, compare resulting legs and remotes against the control run, and pin the GitHub CLI clone arguments.

  • tests/test_adopt_clone_default_remote_name.py L1-249
  • tests/test_adopt_source_clone_record.py L262-263

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

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-11-08T19:27:08Z) Lane 3's independent review of #1268 (for #1261), by RV1268 (opus), which wrote none of it, at faa29987: READY. The report follows as the reviewer wrote it.

I'm RV1268, the independent reviewer for PR #1268 (issue #1261), head faa29987aad260bb1a0f28f1acb9de316684383a.

Verdict: READY

There is no MUST finding. The fix works, every "Done when" item is pinned, and every gate I ran passes. One SHOULD is for the refresh after #1251 lands: it is about #1251's text, which is not on main yet, so it cannot be done on this head. Everything else is a NIT.

All probes and pytest runs used uid 1000, /usr/bin/git 2.43.0, the stub gh (exit 97) first on PATH, then a folder holding only a link to the venv's git-filter-repo 2.47.0. They also used GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false and EMAIL=tests@openreposhape.invalid. The wrapper is RV1268-scratch/envrun.sh. One standard source (make_source_repo, directory Thing, project Northwind) was made once, and each run adopted a copy of it.

1. Before and after (RV1268-scratch/probe.py)

global config main be21751c3 head faa29987a
defaultRemoteName = upstream plan 0, check 0, execute 2 REFUSED mounting the legs / error: No such remote 'origin' plan 0, check 0, execute 0, adoption verified: every source path is in exactly one place
empty (control) 0 / 0 / 0, verified 0 / 0 / 0, verified
a..b plan 0, check 1 FINDING adopt-git-unasked (exited 128 there: fatal: 'a..b' ...), execute 2 REFUSED adopt-git-unasked 0 / 0 / 0, verified
  • The legs. For each run I dumped symbolic-ref HEAD, for-each-ref and the sorted cat-file --batch-all-objects --batch-check: 52 lines each. These five are cmp-identical to main's control: main-upstream, head-empty, head-upstream, head-aab. I also hashed every object's full bytes (cat-file --batch-all-objects --batch | md5sum): spec c6a4782e... and code 3f12d592... in all five.
  • The clones' remotes. On main under upstream, the assembly, the spec mount and both work clones hold remote.upstream.*, and the code mount is (absent). On the head under upstream and under a..b, the remotes dump is cmp-equal to main's control.
  • The split commit. The trees differ only in .gitmodules and shape/README.md, and both are equal once each run's own path is taken out.

2. Does the -c reach the clone git submodule add makes?

Yes. RV1268-scratch/sm.sh under a global upstream:

FILE_PROTOCOL only                                 add rc=0 remotes=[upstream ] set-url origin: error: No such remote 'origin' rc=2
FILE_PROTOCOL + REMOTE_NAMED_ORIGIN (before verb)  add rc=0 remotes=[origin ] set-url origin: rc=0
GIT_CONFIG_PARAMETERS=fromparams, + -c             remotes=[origin ]
GIT_CONFIG_COUNT=fromcount, + -c                   remotes=[origin ]
PARAMETERS + COUNT, + -c                           remotes=[origin ]
  • With a global a..b, the plain submodule add exits 128 ('a..b' is not a valid remote name), and with the -c it names the remote origin.
  • Nothing after the mount names origin. grep of the head's adopt-project.py finds one origin after the mount: set-url at :14976. Every push, fetch and ls-remote goes to an explicit url (:5029, :14118, :14735, :16076).
  • The head-upstream run went on through split aa4da57ba8fe on adopt/three-repo-shape, the verification, and adoption verified.

3. The behaviour change beyond the issue

Passing under a..b is right. check's rehearsal exists to predict execute's clone, and that clone no longer reads the setting. execute under a..b really does succeed: I measured it, and the legs match the control. The person's own later clone (the NEXT line) still fails under a..b, as every clone of theirs does.

Interactions I measured:

  • GIT_CONFIG_PARAMETERS, with an empty global config:
    • clone.defaultRemoteName=upstream: main execute exit=2 REFUSED mounting the legs; head execute exit=0, legs and remotes equal to the control.
    • a..b: main check exit=1 (adopt-git-unasked); head 0 / 0 / 0.
  • A global remote.origin.fetch (+refs/pull/*/head:refs/remotes/origin/pr/*), plus upstream:
    • main execute 2; head execute 0.
    • Head's remotes dump is identical to main's under that line alone (head upstream+originfetch remotes == base originfetch-only remotes). The setting becomes a no-op, and the global remote.origin.* does what it does without it.
  • A global remote.origin.url = /nonexistent/x.git, plus upstream. This is the one real change.
    • A plain git -c clone.defaultRemoteName=origin clone fetches from the global url and exits 128.
    • main: check 0, then execute 2 at the mount, with both legs pushed (legs.dump 52 lines).
    • head: check exit=1 FINDING adopt-git-unasked, and execute refuses adopt-git-unasked with Remediation: no leg repository was created and nothing was pushed (legs (absent)).
    • That is an earlier refusal that names its cause, and it is exactly what that person gets without the setting. It is not a regression.

4. The gh repo clone argv

  • gh's manual gives gh repo clone <repository> [<directory>] [-- <gitflags>...]: "Pass additional git clone flags by listing them after --."
  • gh's source does the same. clone.go sets opts.GitArgs = args[1:] (pflag drops the --). git/client.go's parseCloneArgs takes the first argument that does not start with - as the target. Clone runs git clone <flags> <url> <target>. Nothing in gh reads clone.defaultRemoteName.
  • So -- --quiet --origin origin becomes git clone --quiet --origin origin <url> <dir>, which is correct.
  • It also keeps gh's fork step working under the setting: gh adds the fork's parent as a remote named upstream. Without --origin, the clone's own remote would already be upstream. This part is read, not measured.
  • The pinned argv row is a real regression pin. D5 kills it, and the record pin fails 12. But it proves only our argv. See N4 for the behaviour it leaves untested.

5. The rows

  • New module on the head: 6 passed in 9.17s.
  • With main's adopt-project.py swapped in: 4 failed, 2 passed. The failures are the execute, remotes, check and gh rows, and the output shows REFUSED mounting the legs / error: No such remote 'origin'. The file was restored, checked with cmp against the head's, and git diff --quiet HEAD was clean.
  • The setting reaches only each row's own child. I ran a probe module after the new one in the same pytest (RV1268-scratch/test_zz_rv1268_leak.py; I put it in the mutant tree and moved it to quarantine/ afterwards): 8 passed. It checks that the session's global file holds no defaultRemoteName, that git config --get clone.defaultRemoteName exits 1, and that a plain clone after the rows names origin.
  • Done when:
    • Item 1 is pinned by the execute row.
    • Item 2 is pinned by the legs row, which passes on main too, by design.
    • Item 3 is covered by the leak probe and the gates.

Mutants (RV1268-scratch/mutants.log). Each was restored with cmp and diff --quiet, and each restore was clean.

mutant result killed by
D1 drop _extract_leg's -c 1 failed remotes row
D2 drop the mount's -c 2 failed execute and remotes rows
D3 drop the assembly's -c 1 failed remotes row
D4 drop the rehearsal's -c 1 failed check row
D5 drop gh's --origin origin 1 failed; record -k each_spelling_of_a_name: 12 failed gh row
V1 value =orign 2 failed execute and remotes rows
V2 key defaultRemoteNam 3 failed
V3 constant [] 3 failed
A2 -c after submodule add 2 failed
G1 --origin origin before gh's -- 1 failed; record: 12 failed
A1, A3, A4 -c after the clone verb 6 passed equivalent

A1, A3 and A4 are equivalent mutants. git clone -c re-reads its config after writing it, and I measured that it names the remote origin, but it leaves clone.defaultRemoteName=origin in the clone's own .git/config. That is not a gap in the rows.

6. Gates

The gates ran one pytest at a time, with -rfs, GIT_CONFIG_GLOBAL=/dev/null, and basetemps /tmp/claude-1000/l3bt/RV1268-<group>:

  • g1, the new module plus test_adopt_source_clone_record.py and the two modules it imports from (test_adopt_temp_work_roots.py, test_adopt_plan_leg_path_help.py), plus test_adopt_e2e.py: 187 passed in 75.45s, 0 skipped.
  • g2, test_adopt_split_branch.py, test_adopt_submodules.py and test_scaffold_leg_path_collisions.py: 318 passed in 275.18s. Its import from test_adopt_pins is asked_what, a plain function, not a fixture.
  • g3, the hygiene guards plus test_cli_reference.py, test_no_auto_maintenance.py and test_clone_just_pushed.py: 559 passed, 3 skipped in 84.57s. The skips are a Windows-only test and 2 for GIT_CONFIG_GLOBAL being set at session start.
  • Also, not run by me:
    • CI on pull/1268/merge e56915cf: 20586 passed, 404 skipped, with filter-repo 2.47.0 installed. All four checks pass.
    • The SonarCloud gate is OK, with 0 open issues.
    • The writer's chain log matches the body run for run, through CHAIN-DONE. I read it and did not re-run it.

7. Merges

merge result
head x main 81eaf757c clean, tree 08fe007f977a; new rows on it: 6 passed
head x main 9252ef208 (newest; it touches only family.py, README, the handbook and tests) clean, tree 41bb694bbb60; the PR's files and conftest are byte-equal to the 08fe007f tree
head x #1251 abeac66d clean, tree 4474c9f5c0dd
head x (main + predicted #1251 squash 64b5b255) clean, tree c6f9bc13cff4; new rows 6 passed; probe under upstream: execute 0, legs equal to main's control, remotes equal to the merge's control
the same on 9252ef208 tree d8cc6004178f
#1251 alone under upstream execute exit=2 REFUSED mounting the legs
#1154 8a7828332 clean, tree 118ca7046350, as the body says

What the refresh must keep: *REMOTE_NAMED_ORIGIN on _extract_leg's clone, ahead of #1251's git config --file <work>/.git/config --unset remote.origin.url. merge-tree keeps it with no conflict.

8. The body's claims

Findings

SHOULD

NIT

Not covered

  • A real gh (stub and emulator only), the GitHub path of execute, Windows, another git version, and setting values other than upstream, a..b, fromparams and fromcount.
  • setup-project.py, family.py and bump-leg.py under the setting.
  • The writer's 61-run chain (read, not re-run).
  • Merges with the other 25 open PRs.

Housekeeping: while comparing the split trees, one command wrote a stray file by mistake, /tmp/claude-1000/rv1268-a.txt (2285 bytes of ls-tree output). It was not deleted, under the no-rm rule. Nothing was posted, pushed or committed. The only objects I made are prediction objects inside my own clone: commit-trees 64b5b255, 9773a634, b6699c88 and 4eb1ee16, with no refs.

Paths:

  • Clone: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1268, at faa29987a, clean.
  • Scratch: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1268-scratch/. It holds mut/ (mutant worktree, at the head, clean), base/ (main be21751c3), probes/, gates.log, gate-g1.log to gate-g3.log and mutants.log.
  • Basetemps: /tmp/claude-1000/l3bt/RV1268-{new,mut,g1,g2,g3}.

Lane: openreposhape-3 (openRepoShape-3)

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

1 participant