Repository navigation
park's not-carried report counts a leg's unpushed commits on the branch its own row declares, else the root's tracking_branch (#242) - #249
Conversation
…ch its own row declares, else the root's tracking_branch (#242) report_repos gave every leg's row the ROOT's tracking_branch, but the root's bootstrap.py places a leg on str(leg.get("branch") or branch). A leg whose row in project.yaml declares its own branch: keeps its unpushed work on THAT branch, so not_parked_report asked about the wrong one, found nothing ahead and ended without naming the leg: a silent miss in the report that exists to say what park did not carry. Both loops of report_repos (the standalone root and a family member's) now take each leg's branch from leg_branch_of, the shared helper #238 gave status and resume, and fall back to the root's branch on an empty answer, as status's estate_repos does. The false comment says the leg row's own branch:, else the root's tracking_branch, and points at #235 and #238. The shared resolver block is untouched. Four tests beside the not-carried report's: a standalone root's spec leg on branch: develop, the same inside a family member's clone, a leg with no branch: unchanged, and one root with a leg of each kind. The first, second and fourth fail on main's park and pass with this change. Fixes #242 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 1 day and 10 hours by commenting |
Reviewer's GuideFix File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
brettheap
left a comment
There was a problem hiding this comment.
Independent review of #249 at e46254a: LAND after fixes
The fix itself is right: both loops of report_repos now ask leg_branch_of with the right root and the right leg spelling, park's actions are unchanged, and the shared block is byte-identical. Three small things need a change before it lands (1 to 3), and two are follow-ups that need no change here (4 and 5). I ran everything in a private clone at e46254a with the submodule at its pin, under the shared pytest lock, with every LANES_*/TMUX* variable unset and a private HOME, TMPDIR, basetemp and pycache prefix. Linux CI's tests and tests-no-submodule jobs were still running when I read the checks, so this verdict also needs them green.
1. LOW: status now carries a false comment this PR made false.
estate_repos' header in status says: "A LEG'S BRANCH COLUMN DIFFERS from park's: here it is the branch the leg is placed on (leg_branch_of), where park's report gives every leg the root's (#242)." (status:734-736). After this PR park gives each leg the same column, so the sentence says the opposite of what the code does. #238's own review found the same kind of stale text and fixed it in that PR. The block is below --- END shared estate resolver (status:685), so editing it does not touch the shared block. Evidence: command grep -rn '#242' . outside park and the tests returns only status:736.
Fix, in status only (it adds a third file to the PR):
# recorded, which is what this command is for. A LEG'S BRANCH COLUMN IS THE
# SAME as `park`'s: the `branch:` the leg's own row declares, else the root's
# `tracking_branch:` (`leg_branch_of`; #235, #242).
I applied exactly this in a throwaway copy: bash -n status is clean and tests/test_repo_hygiene.py is 189 passed.
2. LOW: the new legbranch="$(leg_branch_of ...)" can truncate the report silently under set -e.
park runs under set -euo pipefail (park:43), and report_repos runs in a process substitution (done < <(report_repos), park:688), which keeps -e. leg_branch_of returns 1 when its done <"$root/project.yaml" redirect fails (the file exists but cannot be read). The bare assignment then ends report_repos' subshell, so that leg's row and every row after it are dropped, make park's exit code is untouched, and park prints no failure of its own. Main's park read the same file here too, through tracking_branch_of, but inside an if, so a failed read was tolerated; the new read is unguarded.
Reproduced on a family member with its project.yaml at mode 000 and 2 commits on the leg's main (the leg declares no branch):
- main's
park: printsunpushed main leg <P>/TestFam/Delta/spec/2 commit(s) on main that origin has not seen, and "2 thing(s) above are not park's to carry". e46254a'spark: stderr gets a secondPermission denied(fromleg_branch_of'sdone <), theunpushedrow is gone, rc is 0, and the summary says "1 thing(s)". With no dirty root the run would end "nothing else was left behind".
A standalone root cannot reach this (discovery reads project.yaml first and refuses), so the exposure is a family member's manifest, which is narrow. But the report exists to never miss silently.
Fix, in both loops:
legbranch="$(leg_branch_of "$ESTATE_DIR/$member" "$leg" || true)"
...
legbranch="$(leg_branch_of "$ESTATE_ROOT" "$leg" || true)"With that edit on a throwaway copy the same probe prints the unpushed main leg row again, and the four new tests plus the probe pass (9 passed).
3. LOW: no test pins the family loop's fallback to the member's tracking_branch.
Test (b) has only the leg that declares develop, so the family loop's ${legbranch:-$branch} is never exercised. Mutation check: changing only the family printf to "$legbranch" (empty branch for a leg with no branch:) leaves all four new tests green (4 passed). The other mutations are caught: passing $ESTATE_ROOT as the family root fails (b), passing an absolute leg path fails (b) and, in the standalone loop, (a) and (d), and dropping the fallback in the standalone loop fails (c) and (d).
Fix, in test_park_reports_a_family_members_leg_on_its_own_declared_branch: build the member with code_leg=True and add the second leg's row to the assertions.
member = clone_leg_member(folder, tmp_path / "remotes", "Delta",
leg_branch="develop", code_leg=True)
commits_on(member["spec"], "develop", 2)
commits_on(member["code"], "main", 1)
...
assert f"unpushed main leg {member['code']}" in result.stdout, (
result.stdout)
assert "1 commit(s) on main that origin has not seen" in result.stdoutRun on a throwaway copy: passes on e46254a's park, fails on main's park, and fails on the family-only no-fallback mutation.
4. NIT, follow-up only: the shared block's comment "park never asks where a leg belongs" is now false.
# shellcheck disable=SC2329 # parknever asks where a leg belongs;resumeandstatus do. sits above leg_branch_of in all three copies (park:527, resume:511, status:648). park now calls it. The PR is right not to edit one copy, because the byte-identity test pins all three. Fix: one follow-up that rewrites that comment identically in the three files. shellcheck -s bash park is clean at e46254a and on main.
5. INFO, no change needed: a leg that declares its own branch is no longer asked about the root's.
Probe: the leg declares develop, its local main is 2 ahead of origin/main, and develop equals origin/develop. Main's park printed unpushed main leg ... 2 commit(s) on main. e46254a prints "nothing else was left behind". That follows from #242's own suggested fix (bootstrap places that leg on develop, so develop is the branch the report asks about), and the report never covered other local branches. It is worth a sentence in the PR body, because "asks each leg about its own branch" hides that the root-branch question is dropped for such a leg.
Checklist results, with no defect found
- 1. Both loops. The family loop's root is
"$ESTATE_DIR/$member", the same path the existingsubmodule_paths "$ESTATE_DIR/$member"reads (park:615,park:617). The standalone loop's root is$ESTATE_ROOT(park:625,park:627). The leg argument is the relative.gitmodulespathsubmodule_pathsprints, andleg_branch_ofstrips a leading./and a trailing/from both sides. Probes with the manifest spelling the path"./spec",spec/,'spec'and./specall printunpushed develop leg ... 2 commit(s) on developone46254a; main'sparkprinted nounpushedrow for any of them. - 2.
unpushed_on_tracking/not_parked_report. Same outcomes as before for: local branch with noorigin/<branch>(silent, as for the root's branch;unpushed_on_trackingstill needs both refs), local branch gone with origin having it (silent), and a leg declaringmain(reported, identical on main ande46254a). A detached-HEAD leg with a localdevelop2 ahead is now reported (main'sparkwas silent); a commit made on a detached HEAD, on no branch, is reported by neither (pre-existing). The only thing newly silent is item 5. - 3. The rest of
park.report_reposhas exactly one consumer,not_parked_report(park:688), which runs aftermake parkand itsone_status.leg_branch_ofonly reads. Nothingparkdoes changes, and the exit code is stillmake's (rc 0 in every probe above, including the one in item 2). - 4. Tests. (a), (b) and (d) fail on main's
parkand pass one46254a; (c) passes on both:3 failed, 1 passedon main'sparkcopied into a clone,4 passedon the head. Each assertion names the leg's path, the branch and the exact count, and each test assertsreturncode == 0. The count lines (3 commit(s) on develop ...) are separate substrings, not tied to the leg's row, but the row assertions beside them and the negativeunpushed main legassertions make them unambiguous in these fixtures. The module-levelNEEDS_UPSTREAMmark makes the new tests skip, not fail, intests-no-submodule. - 5. Hygiene. No host-absolute path on any added line (a grep of
git diff 87a5e9d e46254a's added lines for home, workspace, tmp and Users path prefixes finds nothing). The shared block hashes identically inpark,statusandresume(545c655df59a5c15798c935f5442c1931322ce53bbd03d6bf3ed5320a4cb52e6three times); every hunk inparkstarts at line 602 or later and the block ends at line 564.tests/test_repo_hygiene.pyandtests/test_park_resume_commands.pytogether:374 passed.shellcheck -s bash park: clean. - 6. PR body. The table and the counts match what I measured (3 failed, 1 passed on main's
park; 185 + 189 = 374 passed). The only closing keyword isFixes #242; "fixed by #238" and "Bug Fixes:" are not followed by an issue reference. It does not mention item 1'sstatuscomment or item 5.
🤖 Generated with Claude Code
…nifest, pin the family loop's fallback, and fix status's comment about park (#242) Review 5563423499 of #249: 1. status's estate_repos header said park's report gives every leg the root's branch. It now gives every leg the same column as status does. 2. report_repos runs under set -e in a process substitution, and leg_branch_of fails when a project.yaml that exists cannot be read, so the bare assignment ended the subshell and dropped that leg's row and every row after it, with no word from park. Both loops now say || true and fall back to the root's branch; a test makes a family member's project.yaml mode 000 (skipped as root, which mode 000 does not bind) and fails without the guard. 3. The family loop's fallback to the member's tracking_branch was reached by no test: the member of test (b) now has a code leg declaring no branch:, and the report must name it on main. That fails with the family printf using "$legbranch" with no fallback. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
brettheap
left a comment
There was a problem hiding this comment.
Delta re-check of #249 at 33f3be1: LAND
No findings. Linux CI's tests and tests-no-submodule jobs were still running when I read the checks, so the verdict also needs them green. I checked only e46254a..33f3be1, in a private clone with the submodule at its pin, the shared pytest lock, every LANES_*/TMUX* variable unset and private HOME, TMPDIR and basetemp.
- The merge changed nothing of the PR's.
git diff 61883f1 33f3be1 -- park status resume tests/test_park_resume_commands.pyis empty. The merge brings in #247's files only (AGENTS.md,README.md,openRepoToolsand four install tests), and main moved no verb between87a5e9dand586dea2. - Finding 1,
statuscomment:status:734-736now says the branch column is the same aspark's, as I suggested. The diff instatusis those three comment lines. - Finding 2,
|| truein both loops: present in both loops atpark:619andpark:630. The new testtest_park_reports_a_leg_when_its_members_manifest_cannot_be_read(family member'sproject.yamlat mode 000, 2 commits on the leg'smain) is the mode-000 probe as a test. Withparktaken from each version: main586dea2passes,e46254afails,33f3be1passes. Removing both|| trueguards from the head makes it fail again. The skip as root is correct (mode 000 does not bind root). It ran here as an ordinary user, not skipped. - Finding 3, second
codeleg in test (b): with only the family printf changed to"$legbranch", test (b) now fails (1 failed) and the unreadable-manifest test fails too, as the PR body says. The head'sparkpasses both. Test (b) still fails on main'sparkand passes one46254a's. - Finding 5, PR body: the "A behaviour change worth stating" paragraph says a leg that declares its own
branch:is no longer asked about the root's branch, that unpushed commits on the root's branch inside such a leg are not counted, and that a leg with nobranch:is unchanged. That is what my probe showed. TheFiles:line names the third file as "a comment only". The only closing keyword is stillFixes #242. family_folder_naming_delta: harmless and correct. Its body is the removed inline setup moved verbatim (holderfamily.yamlnamingDelta, probe Makefile, returns the family folder). It has exactly two callers, test (b) and the new test, each with a freshhome, so its non-idempotentmkdiris not a problem.osis imported for the newskipif.- Shared block: its hash is the same in
park,statusandresume(545c655df59a5c15798c935f5442c1931322ce53bbd03d6bf3ed5320a4cb52e6). The delta's hunks inparkare at lines 615-619 and 628-630, after the block's end at line 564, and thestatushunk is at 734. - Tests run: the five tests that matter are
5 passed.tests/test_repo_hygiene.pyplustests/test_park_resume_commands.pygive375 passed(189 + 186). I did not runtests/test_status_command.py, which the PR body counts in its 584, because thestatuschange is a comment.
One thing needing no change: the standalone loop's || true has no test. A standalone root cannot reach it, because discovery reads its project.yaml first and refuses; the guard is for parity with the family loop.
🤖 Generated with Claude Code
…status names one family laid out twice, and resume finds a holder by its id when its record names another folder (#243) (#251) * family.yaml values are read the way openRepoShape v1.1.0 reads them, status names one family laid out twice, and resume finds a holder by its id when its record names another folder (#243) 1. Value shapes. The shared `trim_unquote` reads a scalar as v1.1.0's `parse_yaml` does: a quoted value is the text up to its closing quote (a `\` or a `''` does not close it) and nothing after the close is the value; an unquoted one is cut at a `#` that opens it or follows a space or tab. `yaml_scalar` sets `YAML_QUOTED`, and the repository arm of `is_family_folder`, now `named_for_repository`, reads an UNQUOTED `null`, `Null`, `NULL` or `~` as no repository. 2. Two layouts of one family are not de-duplicated. AGENTS.md says that parking both writes one record key, the family id, and to keep the folder named for the repository; `status` reports a finding on the holder's row when two family folders under the projects directory carry the same `family.yaml` `id:`, naming both and the one to keep. 3. `resume` finds the holder by `family.yaml` `id:` against the record's `family:` when the name typed and the record's folder both miss: one match is the estate (no clone), two or more are refused by name and nothing is cloned, none is the clone it always was. 4. Tests for the cwd walk-up of a repository-named family, and `named_apart` files only the record park writes, `quill.yaml`. The shared block gains `named_for_repository` and `family_folders_with_id` and stays byte-identical in park, resume and status. `leg_branch_of`'s comment names everyone who asks it, park's not-carried report (#242, PR #249) included; its SC2329 disable stays until #249 lands. AGENTS.md's cap moves 323 -> 330 with a dated entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * family_folders_with_id lists one holder once however many spellings of the projects directory reach it, and the second-spelling test skips where ~/Projects is ~/projects (#243, review of #251) The review of #251, finding 1: `PROJECTS_DIRS` is deduped by inode, but the folders inside two distinct projects directories were not compared, so a real `~/Projects` holding a link `Quill -> ~/projects/Quill` listed one holder twice. `resume quill` refused "2 family folders" and `status Quill` reported the family "laid out 2 times", with a remedy that would have taken the link away. A candidate that is `-ef` one already listed is now skipped (the reviewer's verified edit, with `local -a found=()` as the block's neighbours spell it), in the shared estate resolver, byte-identical in park, resume and status. Two real holders in two directories still refuse. The review's NIT: `second_projects_spelling` makes `~/Projects` beside the fixture's `~/projects`, and SKIPS, saying why, where the two names are one directory (a case-insensitive filesystem, a stock Mac) instead of failing on the `mkdir`. The two-holder refusal's `under-the-other-spelling` row and the new symlink test both use it; Linux runs both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brett Heap <service@opensoft.dev> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>



The defect
park's not-carried report counts the commits a leg has on its branch that origin has never seen.report_reposemitted every leg's row with the ROOT'stracking_branch, but the root'sscripts/bootstrap.pyplaces a leg onstr(leg.get("branch") or branch). A leg whose row inproject.yamldeclares its ownbranch:keeps its unpushed work on THAT branch, sonot_parked_report/unpushed_on_trackingasked about the wrong one, found nothing ahead and ended without naming the leg. The report is what tells a person which workparkdid NOT carry, so the miss is silent.resumeandstatushad the same assumption and were fixed by #238 (for #235);parkwas left out because #235's finding did not name it (found by review 5561239715 of #238).The fix
report_repos, in both loops (the standalone root and a family member's root,$ESTATE_DIR/$member), gives each leg's rowleg_branch_of "<root>" "<leg>" || true, falling back to the root's branch on an empty answer, exactly asstatus'sestate_reposdoes since resume and status hold each leg to the branch bootstrap puts it on: the leg row's own branch:, else the root's tracking_branch (#235) #238.leg_branch_ofwas already inpark's copy of the shared resolver block, unused; the shared block is not edited.|| truebecausereport_reposruns underset -ein a process substitution andleg_branch_offails when aproject.yamlthat exists cannot be read: a bare assignment would drop that leg's row and every row after it, silently (review 5563423499, finding 2).tracking_branch:") now says the leg row's ownbranch:, else the root'stracking_branch, and points at resume's leg guard checks each leg against the root's tracking_branch, not the leg's own branch:, so it lets bootstrap move a leg (exit 0) and refuses a leg on its own branch #235 and resume and status hold each leg to the branch bootstrap puts it on: the leg row's own branch:, else the root's tracking_branch (#235) #238. The comment aboveunpushed_on_trackingsays so for a leg too, andstatus'sestate_reposheader, which saidpark's report gives every leg the root's branch, now says the two are the same column.A behaviour change worth stating: a leg that declares its own
branch:is no longer asked about the ROOT's branch. Bootstrap places that leg on its own branch, so that is the branch the report asks about, as #242's suggested fix says; unpushed commits on the root's branch inside such a leg are not counted, and the report never covered a leg's other local branches. A leg with nobranch:is held to the root's, unchanged.Files:
park,status(a comment only),tests/test_park_resume_commands.py. The shared resolver block is byte-identical in all three commands; its stale "parknever asks where a leg belongs" comment is for #243's PR.Tests
Five tests beside the existing not-carried-report tests, built with
seed_leg_member(leg_branch=andcode_leg=since #238):parktest_park_reports_a_leg_on_the_branch_its_own_row_declares(standalone root, spec legbranch: develop, 3 local commits origin lacks; the report names the leg,developand3 commit(s))test_park_reports_a_family_members_leg_on_its_own_declared_branch(the same inside a family member's clone, 2 commits; and a secondcodeleg with nobranch:, 1 commit, reported onmain, which pins the family loop's fallback to the member'stracking_branch)test_park_reports_a_leg_when_its_members_manifest_cannot_be_read(a family member'sproject.yamlat mode 000 still reports the leg's 2 unpushed commits on the fallbackmain; skipped as root, which mode 000 does not bind)test_park_still_reports_a_leg_with_no_branch_of_its_own_on_the_roots(a leg with nobranch:is unchanged)test_park_asks_each_leg_of_one_root_about_its_own_branch(one root,specondevelopwith 3 commits andcodewith none declared onmainwith 1)Mutations, run against this head's tests:
|| trueremoved from both loops (the leg's row is dropped), and passes with it.printfusing"$legbranch"and no fallback, and pass with"${legbranch:-$branch}".First round, the four original tests against unmodified
park:3 failed, 1 passed, 181 deselected; with the fix4 passed.Modules in full at this head, after merging
origin/main(no rebase, no force), under the shared pytest lock with everyLANES_*andTMUX*variable unset and a private HOME, TMPDIR and basetemp:tests/test_park_resume_commands.py(186 tests),tests/test_status_command.py(209) andtests/test_repo_hygiene.py(189), together584 passed. Not the whole suite.Fixes #242
🤖 Generated with Claude Code