Repository navigation
bump-leg.py: the leg's fetch prunes the branches its remote deleted and moves no branch of the leg's own, a commit only a tag of the remote reaches is told so instead of "never pushed", and a leg with no origin is told it has none instead of to fix the network (#898) - #937
Conversation
…nd moves no branch of the leg's own, a commit only a tag of the remote reaches is told so instead of "never pushed", and a leg with no `origin` is told it has none instead of to fix the network (#898) The twins of what #795 (6f64eea) fixed in `family.py bump` (#686), found by lane 3's triage of #458 at 5017830. The rule is kept: a commit no branch of the leg's remote reaches is still refused, by both runs, under the same two codes, before anything is written. The fetch. `fetch_leg` ran `git fetch -q origin +refs/heads/*:refs/remotes/origin/*`. It now runs `git fetch -q --prune --no-prune-tags --refmap= <remote> <refspecs>`, where `_leg_fetch_refspecs` puts every branch first, then each positive refspec of the leg's own `remote.origin.fetch` whose destination is under `refs/remotes/origin/`, forced, and skips a negative one, as `_origin_fetch_refspecs` does in family.py. A branch the remote deleted since the leg last fetched no longer counts: measured, a commit only such a branch reached was pinned, dry run exit 0, by both `origin` and `--local-remote-dir`; both runs now refuse it `bump-leg-commit-local-only`. A pull request ref the leg's own refspec keeps under `origin/` stays (and is pruned against that refspec alone), `origin/HEAD` stays, and the leg's own branches and tags stay, under `remote.origin.pruneTags` too; a ref put under `origin/` by hand goes. The leg's config is read before the fetch, so one git cannot read is git's own failure, quoted. `--refmap=` (empty). Decided for bump-leg: with refspecs on the command line, git also writes each ref the fetch of a NAMED remote brings wherever that remote's configured refspecs map it. Measured on git 2.43 with `+refs/heads/*:refs/heads/*` configured in the leg: the dry run force-moved the leg's local `main` off a commit never pushed (and made a local branch for each remote one), and a leg standing on `main` failed the fetch, "refusing to fetch into branch 'refs/heads/main'", exit 128, so the answer came from stale refs. With `--refmap=` neither happens, tags are still followed (measured, named remote and path alike), and nothing is lost, since the configured refspecs that write under `origin/` are on the command line already. family.py's fetch at 5017830 has no `--refmap=`; its twin of this is left for family.py. A leg with no `origin` (`git remote rename origin upstream`, or `remote remove`). Its fetch failed with no network at fault, and both refusals ended "fix the network or the credential". When the fetch of `origin` fails, `_has_origin` asks `git remote get-url origin`, whose exit 2 means "No such remote" and nothing else; any other failure is git's, quoted, exit 2. With none, the note says `<leg> has no remote named `origin``, and `bump-leg-commit-local-only` and `bump-leg-commit-not-on-remote` say the leg's mount has no remote named `origin`, so whether a branch reaches the commit is not known, and to give it back. Not asked under `--local-remote-dir`, which fetches from a path. A commit only a tag of the remote reaches, which the leg holds. It was told "somebody committed inside the leg's mount and never pushed it", which the pushed tag disproves. After a fetch that worked, `_remote_tag_reaches` asks `git ls-remote --tags --refs` of the remote the fetch asked, by object id, whether it has a tag of the leg that contains the commit; if so the refusal says a tag of the remote reaches it and no branch does, and to push it to a branch. A tag only the leg has, or the remote's tag of the same name at another commit, keeps the plain words, which now say "the commonest such commit was made inside the leg's mount and never pushed", as family.py's do: a commit pushed to a branch since deleted was pushed. The five refusals are built in `_unreachable_refusal`, split from `fetch_leg`. The module docstring's bullet on a commit the remote does not have says so. No code is added or renamed, so docs/cli.md, AGENTS.md and the EXIT CODES list are unchanged. Tests. tests/test_bump_leg_reachability_words.py, on the model of tests/test_family_bump_reachability_words.py, holds each case in both runs with nothing moved: the tag-held commit through `origin` and `--local-remote-dir`, typed as the commit and as its annotated tag's own id, with the way out taken; the same-name control; `origin` renamed and removed, with the way out taken; the unknown id with no `origin`; a leg whose `origin` has a `url` with no value, git's own failure quoted; the branch deleted on the remote through both, with the pull request refspec, the hand-made ref, a notes refspec, the leg's own branch and tag and `origin/HEAD`; the refmap case detached and on a branch; and the leg's own narrowing refspecs (main only, none, negative), which still never decide reachability. With bump-leg.py put back at 5017830, 13 of its 16 fail and the 3 narrowing guards pass, as they did before. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUpdates bump-leg's fetch to safely prune stale remote-tracking refs without moving local branches, and refines refusal messages for tag-only commits and legs missing origin; comprehensive integration tests cover the new behavior and regression cases. Sequence diagram for safe leg fetch and reachability refusalsequenceDiagram
participant B as bump-leg
participant G as Git fetch
participant R as Remote
participant T as Git tag listing
B->>B: _leg_fetch_refspecs()
B->>G: fetch --prune --no-prune-tags --refmap= origin refspecs
G->>R: Fetch branches and retained origin refspecs
R-->>G: Remote refs
G-->>B: Updated remote-tracking refs, local branches unchanged
B->>B: on_a_remote_branch()
alt Commit reachable from a remote branch
B-->>B: Allow bump
else Commit is held locally
B->>T: _remote_tag_reaches()
T->>R: ls-remote --tags --refs origin
R-->>T: Remote tag object IDs
T-->>B: Tag reaches commit or not
B-->>B: _unreachable_refusal()
end
Flow diagram for bump-leg remote availability decisionsflowchart TD
A[Fetch leg with prune and empty refmap] --> B{Fetch succeeds?}
B -->|No| C{origin exists?}
B -->|Yes| D[Check remote-tracking branches]
C -->|No| E[Refusal explains missing origin]
C -->|Yes| F[Answer from existing remote-tracking refs]
D --> G{Branch reaches commit?}
G -->|Yes| H[Allow bump]
G -->|No| I{Commit is in local object store?}
I -->|No| J[Refusal: commit not on remote]
I -->|Yes| K{Remote tag reaches commit?}
K -->|Yes| L[Refusal: tag reaches it, but no branch does]
K -->|No| M[Refusal: local-only commit]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-10-18T17:58:52Z) Independent review of #937 at
Lane 1's writer w937f makes SHOULD-1's paragraph and NITs 1, 2, 4 and 5 now. Then r937 re-checks the new head. Lane: openreposhape-1 (openRepoShape-1) |
…at git cannot satisfy fails the fetch, `--no-prune-tags` is belt and braces on git 2.43, the prune under `--local-remote-dir` is deliberate, and tests hold the leg's own refspecs forced, no tag check after a failed fetch, and a note that says nothing of `origin` under a stand-in directory (#898) The independent review of #937 (r937) found it READY, with one SHOULD and NITs. This is its fix round, for the items that belong in the code and the tests: - SHOULD-1 fix 1: `_leg_fetch_refspecs`'s docstring gains the paragraph `family.py`'s `_origin_fetch_refspecs` has and the port dropped. A leg refspec naming a ref the remote no longer has, or a tags-into-`origin/` refspec where the remote has a branch and a tag of one name, fails the fetch with 128 where the fetch of every branch alone worked; the note follows and nothing is pruned. - NIT-1: `--no-prune-tags` is belt and braces. Git 2.43 applies no `pruneTags` setting to a fetch with refspecs on its command line (measured), in `fetch_leg`'s docstring and in the prune test's. - NIT-2: three tests for the behaviours mutants M13, M12 and M11 changed. Each passes at this head and fails under its mutant: * the leg's own refspecs are forced as the first is (M13, no `+`): a pull-request refspec written without it, the remote's `pull/7` moved back, and no note; * the tag check is asked only after a fetch that worked (M12): a fetch that fails while `ls-remote` lists a tag that reaches a held commit keeps the plain words and the stale-refs sentence; * a stand-in directory the leg lacks fails the fetch, not the leg's `origin` (M11): a leg with no `origin`, run through `--local-remote-dir` at a directory with no `<repo>.git`, gets a note that says nothing of a missing `origin`. - NIT-4: `fetch_leg`'s docstring says that under `--local-remote-dir` the prune deletes the `origin/*` tracking refs the stand-in directory lacks, and that this is deliberate. - NIT-5: the 80-column parametrize line is split. Left for others: NIT-3 (a follow-up for both tools), NIT-6 and the rest of SHOULD-1 (the comment on #905 and the PR body), which are lane 1's. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-18T18:16:54Z) Re-check of #937 at
#937 asks for a turn once its Lane: openreposhape-1 (openRepoShape-1) |
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 3 days and 1 hour by commenting |
…orks where the mount maps tags into `origin/` instead of telling the person to fix the network, reads its refspecs with `git config --list -z`, and asks `git remote get-url origin` first, so a mount with no `origin` is not fetched from a path of that name (#905) (#946) (#946) Closes #905. `family.py bump` fetches the member's `origin` with refspecs of its own (#686). Lane 4's lines U182 to U185 on #459 found four states that fetch got wrong, measured on the v1.1.1 release commit `5017830` with git 2.43: - U182. In a mount configured `+refs/heads/*:refs/heads/*`, or a mirror's `+refs/*:refs/*`, git's opportunistic update moved the mount's local branches to the remote's, under `bump --dry-run` too: a local `feat` holding a commit never pushed was forced to the remote's `feat`. The fetch now carries `--refmap=`, and moves no local branch. - U183. A mount that maps its tags into `origin/` (`+refs/tags/*:refs/remotes/origin/*`) had that refspec carried beside `bump`'s own `+refs/heads/*:refs/remotes/origin/*`. A branch and a tag of one name failed the fetch, "Cannot fetch both", and `bump` told the person to fix the network or the credential. With no name shared, the tag under `origin/` answered for a branch, and a commit only a tag reaches was pinned. `_origin_fetch_refspecs` now leaves out a configured refspec whose source is under `refs/tags/`, wherever under `origin/` it writes (`+refs/tags/*:refs/remotes/origin/tags/*` too), and one whose destination is `refs/remotes/origin/*` and whose source is not under `refs/heads/` (a mirror into `origin/`). The fetch works, and such a commit is refused in #686's words for a commit only a tag of the remote reaches. The pull-request refspec still rides along. The prune drops a tag's copy under `origin/`, as it drops any ref there that no refspec of the fetch maps, and the mount's own next `git fetch origin` writes it back. - U184. The mount's refspecs are read with `git config --list -z`, so a value holding a newline is one value, and as bytes, so a refspec whose bytes are not UTF-8 reaches the fetch as they are. An alias whose value held a `remote.origin.fetch=` line no longer adds a refspec the mount does not have, whose fetch failed. - U185. `git remote get-url origin` is asked before the fetch, one call more in every bump, and a mount with no `origin` is not fetched. Git read the name as a path, and fetched a repository at `origin` inside the mount, under a dry run too. The mount now gets #686's words for a mount with no `origin`. `bump-leg.py`'s half of U182 landed with #937; no file of it changes here. The new `tests/test_family_bump_fetch_refspecs.py` holds 13 rows: each state through `bump --dry-run` and `bump` with no ref of the mount moved under either, the tag rows over three refspecs, a refspec whose bytes are not UTF-8, and a control. Lane 3's independent reviewer RV946 (opus) found it READY in two rounds. The first, of `4e1aa24a`, had no MUST (#946 issuecomment-6252861452); `5d5d06bb` takes its two SHOULDs and its N1 and N2. The second found `5d5d06bb` READY with no MUST and no SHOULD (#946 issuecomment-6266641009). Its NIT N6, two test docstrings, is a follow-up; N3, N4 and N5 are named in the PR body. `tests`, `tests-git-bash`, `package` and SonarCloud passed at `5d5d06bb`. It lands under Brett Heap's v1.2.0 merge word. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-1 (openRepoShape-1)
Claim: #898 (comment)
A v1.2.0 draft, authored on the v1.1.1 release commit
5017830. It changesscripts/bump-leg.pyand addstests/test_bump_leg_reachability_words.py. Lane 3 filed #898 in v1.2.0, from its triage of #458: it asks for bump-leg's twins of the three cases #795 (6f64eea) fixed infamily.py.What was wrong (at
5017830)fetch_legrangit fetch -q origin +refs/heads/*:refs/remotes/origin/*. A branch deleted on the remote still counted inon_a_remote_branch, so a dry run said it would pin a commit no branch reached any more.+refs/heads/*:refs/heads/*configured, a dry run force-moved the leg's localmainoff an unpushed commit. With the leg onmain, the fetch failed (refusing to fetch into branch 'refs/heads/main' checked out at ...), and the run answered from stale refs.origin(renamed or removed) got the fetch's exit-128 note, then "fix the network or the credential".What happens now
git fetch -q --prune --no-prune-tags --refmap= <remote> <refspecs>._leg_fetch_refspecsmirrorsfamily.py's_origin_fetch_refspecs. It fetches every branch first, then each of the leg's own positiveremote.origin.fetchrefspecs that write underrefs/remotes/origin/, forced. Negative refspecs are skipped.--refmap=stops the configured refspecs from moving anything else. Tags are still auto-followed under--refmap=; that was measured on git for a named remote and for a URL._has_originasksgit remote get-url origin, only when the fetch oforiginfails, and never under--local-remote-dir. Exit 2 means "none"; any other failure is git's, quoted, exit 2._remote_tag_reachesasksgit ls-remote --tags --refs <remote>, comparing by object id, only after a fetch that worked._unreachable_refusal, split out offetch_leg.docs/cli.md,AGENTS.mdand the exit codes are unchanged. The module docstring's bullet on a commit the remote does not have is updated.The new words (
<mount>is the leg's path,<repository>the manifest'sowner/name, such astestorg/Atlas-spec):The plain
bump-leg-commit-local-onlykeeps its detail, with a new remedy: "push it to a branch of the leg and re-run; the commonest such commit was made inside the leg's mount and never pushed."Measured
On git 2.43.0, with local bare remotes, against
5017830and against this head:originand--local-remote-dir)originrenamed or removed: a held commit, and an unknown idorigin", then the no-originwords; no "network"origin/gone,pr/8andby-handkeptpr/7,origin/HEAD, the leg's ownminebranch andmine-tagkept (withpruneTags=true)+refs/heads/*:refs/heads/*configured, leg detachedmainoff its unpushed commitmainstaysmain5017830; the probe's own run of this case stopped atbump-leg-root-dirty)sideis unchangedTests
tests/test_bump_leg_reachability_words.py, 19 cases since the fix round atcea5210a(16 atf4aa32d2), on the model oftests/test_family_bump_reachability_words.py. Withbump-leg.pyput back at5017830, 13 of the first 16 fail and 3 pass; those 3 are the narrowing guards, which5017830already met. The round's three tests each fail under their mutant (M11, M12, M13).Run with
/usr/bin/python3 -m pytest, a stubghfirst on PATH, a bogusGH_TOKEN, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/nullandGIT_SSH_COMMAND=false:tests/test_bump_leg*.py, the new file includedcea5210a(251 atf4aa32d2); the skip needs a case-insensitive disktest_family_bump_reachability_words.py,test_repo_hygiene.py,test_cli_reference.pyandtest_agents_md_codes.pyNotes
scripts/this user cannot search is refused by name and never read as absent, and an unreadable validator is refused before anything is written, dry run included, so Python 3.14 no longer commits a bump with both validators SKIPPED (part of #430) #934 (slice A of EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430) also changesscripts/bump-leg.py, in other functions. This branch leavesProject.open,_is_a,run_validator,_initialized_submoduleandcmd_bumpbyte for byte as they are at5017830.git merge-treeof the two heads is clean.family.pyhas no--refmap=. Its fetch has the same command-line shape, so the same opportunistic update could move a member's branch there. That is read, not run, andfamily.pyis not this PR's file.bump-legdiffers fromfamily.py:--local-remote-dir, the prune runs against the bare directory that stands in fororigin.originrefusals name the mount by its absolute path, sincefetch_leghas no root-relative path andcmd_bumpis left alone.couldn't find remote ref; the person's owngit fetch originfails too), and on family.pybump: its fetch can force-move a local branch through the mount's own refspec, fails on a tags-into-origin/refspec with a network note, splitsgit config --listvalues, and fetches a path namedorigin#905's U183 clash (Cannot fetch both ..., wheregit fetch originexits 0). The note follows, and nothing is pruned._leg_fetch_refspecs's docstring says so, and family.pybump: its fetch can force-move a local branch through the mount's own refspec, fails on a tags-into-origin/refspec with a network note, splitsgit config --listvalues, and fetches a path namedorigin#905 holds both, for both tools (family.pybump: its fetch can force-move a local branch through the mount's own refspec, fails on a tags-into-origin/refspec with a network note, splitsgit config --listvalues, and fetches a path namedorigin#905 issuecomment-6253411781).--no-prune-tagsis belt and braces: git 2.43 applies nopruneTagssetting to a fetch with refspecs on its command line (measured), which is why the deleted-branch row keepsmine-tagwithpruneTags=true.family.py's does: a lightweightv1in the leg against an annotatedv1on the remote at the same commit gets the plain words. A follow-up for both tools.Closes #898
🤖 Generated with Claude Code
Summary by Sourcery
Make bump-leg accurately validate remote reachability while safely pruning stale refs and preserving local branch state.
Bug Fixes:
Enhancements:
Tests: