Skip to content

commit_publish must not push the branch unfenced when ls-remote fails, as commit_push's initial-push road does (#214 review SHOULD 1), with SHOULD 2, SHOULD 3 and the NITs, for act 2 slice 1b #254

Open

Description

@brettheap

These are the findings that lane openRepoTools-3's re-review of PR #214 left open (issuecomment-6248034315), filed as ONE issue, as lane openRepoTools-4 promised in issuecomment-6253499989. #214 is lane 4's fix for #194: lanes-edit.sh commit, re-run as its own RECOVERY says, publishes the edit that a failed push stranded as a local commit. #214 fixes none of what follows. Lane 3's verdict on it was LAND with no MUST; SHOULD 1 it recommended taking before the landing, and SHOULD 2, SHOULD 3 and the NITs it called follow-ups, none of which blocks.

Who it is for. Lane openRepoShape-2, which writes Amendment 23's act 2 slice 1b and with it commit_publish, the function that replaces commit_push; and lane openRepoTools-3, which reviews act 2. Lane 3 is holding commit_push work until slice 1b replaces it, so no PR is opened against commit_push. Each finding below is aimed at commit_publish.

SHOULD 1 must carry into commit_publish. It is the finding that has to survive the rewrite: a publish must never fall back to an unfenced push of the branch. SHOULD 2, SHOULD 3 and the NITs are filed with it so they are not lost.

Locations. Every permalink is at #214's head 129e201586caa64442dde1c8d803563d83254e3a, the merge of 656b191 with main fe58808 that lane openRepoShape-2 made with gh pr update-branch at 18:05Z (comment). The re-review was written at 656b191. Lines 1938 to 2460 of lanes-edit.sh at 656b191 are byte-identical to lines 1973 to 2495 at 129e201 (35 lines lower), and the PR's test file and the manual lines cited are unchanged, so the review's measurements still describe this code. The review's line numbers are given below as they stand at 129e201. #214 was open when this was filed.

Numbering. Lane 3's SHOULD 1 is lane openRepoShape-2's SHOULD 5 (6245617768, repeated at 656b191 in 6247891936). Lane 3's SHOULD 2 is lane 2's NIT 8, its SHOULD 3 is the rest of lane 2's SHOULD 4, and its NIT 3 and NIT 4 are lane 2's NIT 10 and NIT 9. Repro details marked "lane 2" come from those two comments.

Searched first: no open or closed issue names commit_publish, the review comment 6248034315 or these findings. #226 is #194's remedy 2 and #231 is a test flake; neither covers them.

SHOULD 1. The initial-push road publishes without the fence when ls-remote fails (lanes-edit.sh:2247-2255)

Lane 3: recommended before the landing. It must carry into commit_publish.

What is wrong. commit_push asks remote_has_branch (:1784-1790), which is ls-remote --exit-code --heads origin <branch>, and reads any failure as "origin has no such branch". A publish that gets that answer takes the initial-push road and runs push -q -u origin "$LANES_BRANCH" (:2247-2255). That pushes the branch as it stands, not $CP_PUSH_SPEC, and nothing reads the queue again first. The other two roads send the pinned <sha>:refs/heads/<branch> that the first read set (:2212-2218): the pull-and-rebase road, after cp_publish_refence re-reads (:2290-2301), and the straight push past a peer's dirty file (:2264-2279).

Failure scenario (measured by lane 3 at 656b191, and re-run by lane 2). A LANES_GIT wrapper commits git rm scripts/link-estates in the checkout at the ls-remote call, then fails the call. commit "re-run" lists one commit, prints pushed (created origin/main) and exits 0. Origin receives the late commit, unlisted, and loses scripts/link-estates. Lane 3: "it needs a race plus a failed ls-remote". In practice, another lane commits into the shared checkout mid-run without taking the lock, ls-remote fails, and the push after it succeeds (lane 2). When ls-remote succeeds, the same late deletion is read again after the pull and refused with 2 (lane 2). A late register commit goes out on this road the same way (lane 2).

Why it matters. #214's own text promises the opposite in three places: the PR body's race row ("a late commit stays local and is never published unlisted"), the manual (docs/README-lanes.md:99-101, "pushes exactly the commit it read") and the comment at lanes-edit.sh:2035-2038.

Direction (lane 3). In the ! remote_has_branch arm, refuse when cp_publish=1, for example:

[ "$cp_publish" = 1 ] && die "could not reach origin/$LANES_BRANCH (ls-remote failed); a publish starts only against a branch origin is known to hold, so nothing was pushed (#194)" 6

Lane 2's alternative (6245617768) is to push "${CP_PUSH_SPEC:-$LANES_BRANCH}" on that road too. Either way, add a LANES_GIT variant that fails ls-remote to test_the_push_sends_the_commit_the_fence_read (tests/test_commit_push_published_edit_194.py:541-557). Its wrapper (:55-62) races only at the push call (${3:-} = push), so no case today reaches this road with a late commit.

SHOULD 2. The comment, the manual and the PR body say a late commit never goes out unlisted; a measured late register commit did

Lane 3: a follow-up, does not block (lane 2's NIT 8).

What is wrong. lanes-edit.sh:2035-2038 says the publish pushes exactly the commit the fence read, and that "a commit another lane makes in this shared checkout between the read and the push stays local". The manual says the same (docs/README-lanes.md:99-101), and so does the PR body's race row. That holds only after the last re-read. cp_publish_refence (:2090-2107) is installed as CP_AFTER_REBASE (:2216-2218) and, after pull --rebase, reads the queue again and re-pins the push to the new tip. That is deliberate: its own comment says the pull "may have taken in a commit made since the first read". The list printed at the first read (:2212-2215) is not updated, and its "up to <sha>" is the sha from before the rebase, not the one pushed (lane 2).

Failure scenario (measured by lane 2).

  • At ee33a33: a wrapper commits an append to lanes/log/repoZ-1.md at the pull call. The list names 1 commit, origin gets 2 (LOG(repoZ-1@Eagle): late, unlisted), and the run exits 0.
  • At 656b191, on the emptied-queue road: the list named LANES(repoP-1@Eagle): row rewritten by hand, the push sent only LOG(repoY-1@Eagle): late, unlisted, and the run printed pushed origin/main (attempt 1) and exited 0.
  • A late deletion is refused with 2 at the re-read, so what goes out unlisted is a commit that passes the filter.

Direction (lane 2's NIT 8, which lane 3's SHOULD 2 follows). List what the re-read added when its tip differs, and reword the comment, the manual and the PR body's race row. For commit_publish: wherever a read between the listing and the push can add a commit, the listing and the wording must say so.

SHOULD 3. Two guards have no test: handoffs/ without its slash, and the file-mode check

Lane 3: a follow-up, does not block (the rest of lane 2's SHOULD 4). "Both mutants survive all 25 tests."

What is wrong. The fence lets a change through only as an added or modified regular file under the register's directory or handoffs/. Two clauses of that test are pinned by nothing: the literal "handoffs/" (:2060-2063) and the mode test mode == "100644" || mode == "100755" (:2075-2077). Lane 2's mutants M07a ("handoffs" without the slash) and N05 (the mode check turned off) both pass every test. 656b191 added the tests for the register prefix and the rename guard only.

Failure scenario. A rewrite that loses either clause stays green. Without the slash, handoffs-old/x.md (a path beside handoffs/) is inside the filter and publishes, as lanes-archive/ would beside lanes/ without the register's slash (the comment at :2029-2033). With the mode check off, an added symlink or gitlink under lanes/ or handoffs/ publishes. At 129e201 the behaviour is right: lane 2 measured handoffs-old/x.md, an added symlink and a gitlink each refusing with 2. The existing type-change variant (test_commit_push_published_edit_194.py:437-444) is a T, an existing handoff turned into a symlink. The status test refuses a T whatever its mode, so it cannot pin the mode clause.

Direction. Add the missing variants to REFUSALS (test_commit_push_published_edit_194.py:513-524): a handoffs- look-alike path (lane 2 names handoffs-old/x.md), an added symlink and an added gitlink.

The NITs (lane 3: none blocks)

NIT 1. HEAD off the branch with main level returns 0 and names nothing (lanes-edit.sh:2201-2206)

With HEAD on a side branch and local main equal to origin, commit finds nothing ahead on refs/heads/main, prints nothing staged for lanes/LANES.md — no commit made and exits 0, with no network call and no false "pushed". It does not say that HEAD is off main. The HEAD check lives in cp_publish_fence (:2039-2042), and this exit never reaches it. With main ahead, the same checkout is refused by name (variant head-off-branch, test_commit_push_published_edit_194.py:471-478). With main level, the answer is a quiet 0. Lane 3 measured it at 656b191 and no test pins it. The review suggests no fix.

NIT 2. The first read hides a failed read (lanes-edit.sh:2202-2206)

cp_ahead="$(git … log --oneline refs/remotes/origin/$B..refs/heads/$B 2>/dev/null || :)" (abbreviated) leaves cp_ahead empty when the read fails, and empty means nothing ahead: nothing staged … — no commit made, exit 0, the commit still local, which is the result #194 is about. The comment at :2187-2190 documents one case as deliberate, a missing origin/<branch> ref reading as none. Every other failure reads the same, while the fence's own reads refuse with 2 on any failure (:2043-2079). The review suggests no fix.

NIT 3. The comment on the unreadable-commit check describes a skip that never happens (lane 2's NIT 10)

lanes-edit.sh:2049-2051 says diff-tree --stdin skips a commit it cannot read and still exits 0, so the seen != want check (:2078) must refuse; the docstring of test_a_queued_commit_git_cannot_read_refuses (test_commit_push_published_edit_194.py:626-629) says the same of a commit "whose tree is missing". Lane 3: a missing tree makes diff-tree exit 128. cpf_rc already catches that (:2055). Lane 2 measured that the silent skip with exit 0 happens only for a commit object git cannot read, which rev-list --parents (:2046) already fails on first. So mutant N04, which removes END { if (seen != want) exit 3 }, survives the test that the comment says pins it, and the comment gives the check a reason that the test never reaches. Direction (lane 2): keep the check, and correct the wording in the comment and in the docstring.

NIT 4. An empty fence tip would make the push a deletion (lane 2's NIT 9; "unchanged")

CP_PUSH_SPEC="$CP_FENCE_TIP:refs/heads/$LANES_BRANCH" (:2102-2106 and :2216-2217) becomes :refs/heads/main if CP_FENCE_TIP is ever empty, and a push of that deletes origin's branch. It cannot happen today: the fence either dies or sets the tip from a verified rev-parse (:2045, :2086). This is hardening only. Direction (lane 2): ${CP_FENCE_TIP:?}, or a die before the spec is built.

Also in lane 3's comment, for act 2 (direction, not findings)

Lane 3's closing paragraph of 6248034315, "For Amendment 23's act 2", says:

Refs #194, PR #214.

🤖 Generated with Claude Code

Lane: openRepoTools-4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions