Skip to content

update-shape.py: apply's rollback line says to make a shut directory writable first, committed on <branch>: names what the commit holds, a branch named like a tag is pushed and read by its full name, and a quoted files: or shape: header is found as the reader finds it (#674) - #810

Merged
brettheap merged 5 commits into
mainfrom
fix/674-update-shape-apply
Oct 17, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-4 (openRepoShape-4)
Claim: #674 (comment)

What changed and why

Four cases from #674, each measured on main 4ce9045 (git 2.43.0, a scaffolded project with local bare remotes) before the change and again after it.

1. The rollback line over a read-only directory (#458's 6192496098, L4-65). A validator deleted contracts/shape-pin.yaml and left contracts/ at 0555. The tool printed ...its bytes before this run are kept as blob 738777eca8e6 in this repository, then: git -C <root> cat-file blob 738777ec... > <root>/contracts/shape-pin.yaml, and pasted, that failed with cannot create ...: Permission denied (exit 2 under dash; the issue says exit 1).

  • Now: Rollback._restore asks the directory the command writes into, which is _nearest_directory's answer. When the file is absent and that directory will not take it, the line reads ..., so make <root>/contracts writable, then: git -C <root> cat-file blob <id> > <root>/contracts/shape-pin.yaml.
  • Unchanged: a present file still gets "so make it writable", and an absent file under a writable directory still gets the bare command.
  • Why the full path: the directory is the root itself when contracts/ was deleted too, and it need not be inside the root at all.

2. committed on <branch>: (AT-scaffold N9, 6172776355). _land printed paths, and cmd_apply always puts the pin and the manifest in it. So an --accept-local run at the pinned commit printed committed on shape/update-x: contracts/shape-pin.yaml, project.yaml, while git show --name-only HEAD listed only the pin. The same happens with the issue's merged-away row.

  • Now: the line names what git diff-tree --root --no-commit-id -r --name-only --no-renames -z HEAD lists. It goes through the tool's own _git_answer, and falls back to paths only if git does not answer or lists nothing.

3. A branch named like a tag (6214576643, update-shape.py half). In a root tagged v1.0.0, apply --yes --branch v1.0.0 --push made its commit, then failed with REFUSED pushing v1.0.0, exit 2: git push -q -u -- origin v1.0.0, error: src refspec v1.0.0 matches more than one. The NEXT line it printed failed the same way when pasted, and so did the plain --branch NEXT line.

4. A quoted header (6175499929, L4-57). The reader and both validators read "files":, 'files': and files : as the key files. check passed such a pin and sent the person to apply, which then refused with update-pin-unrecognised ("has 0 files line(s)"). "shape": was refused with update-manifest-no-shape ("has no shape: block"), which is false.

Left as the issue directs:

  • run_validator's environment (a rider; no verdict changes).
  • AGENTS.md:283's placeholder git -C <path> push -u origin shape/update-<sha>. It is still true of the branch it names, and AGENTS.md is at its 492-line cap.
  • landing_failed's -- before origin, which its line always carried. The plain --branch NEXT line keeps having none.
  • README, docs/handbook.html and docs/cli.md: unchanged. No --help text moved, so docs/cli.md needs no regeneration.

Tests

New file: tests/test_update_shape_apply_lines_and_headers.py, 38 tests. It uses the neighbours' harnesses: the partial-rollback upstream and ledger, the scaffold-rendered pins of test_pin_rewrite_layout.py, the gh stub of test_update_shape_landing.py, and the merged-away adoption.

  • Headers: every spelling, for both pin kinds and both manifest kinds; the doubled key in both orders; four lines that are still no header, two of which _split_key refuses; and check then apply end to end, with the validators green.
  • Committed line: the --accept-local case, and the issue's own merged-away row.
  • Tag-named branch: --push; both printed push lines, run as printed (without a shell); the current-branch posture; current_branch in process; and the --pr re-run, through the gh stub.
  • Rollback: contracts/ at 0555 over a deleted pin, and a deleted contracts/ under a 0555 root. Each line is followed: its command fails while the directory is shut and restores the pin once it is opened. Both are WINDOWS_SKIP, as their neighbours are.

Tests that held the bare push form move with it: tests/test_update_shape_landing.py (3 places) and tests/test_update_shape_next_quoting.py (3 places and 2 docstrings). tests/test_update_shape_header_comments.py's docstring no longer says a quoted header is not found.

Targeted run, on the merged tree (tree b89da034: main bf1f099b merged into the pushed WIP; the run started on the unpushed merge commit f99feada, whose message was then amended with the trailers, tree unchanged): 33 test files, 2026 passed, 6 skipped, 1 xfailed in 27m41s, exit 0, no GIT_ALLOW_PROTOCOL exported.

Fix round 1 (head 088a2267, on lane 1's review of 31c2ca02, which found the four fixes correct and two SonarCloud smells): _branch_of is now the one place that takes refs/heads/ off a full ref, for current_branch and Rollback.new_branch (S1192, the literal written four times), and the header test builds its Row list before pytest.raises (S5778). Nothing else in update-shape.py moved but the wording of one comment in _land ("or list nothing"). Rerun on those bytes: the new file, test_update_shape_partial_rollback.py, test_update_shape_landing.py, test_update_shape.py, test_update_shape_next_quoting.py, test_update_shape_pr_base.py, test_repo_hygiene.py, test_cli_reference.py and test_agents_md_codes.py: 446 passed in 6m31s, no skips, exit 0.

Mutants, each reverting one part of the fix and run against the new file. All seven were killed:

mutant what it reverts result
M1 no "make writable" words killed, 2 failed
M2 path.parent instead of _nearest_directory killed, 2 failed (one is #560's own deleted-directory test)
M3 current_branch back to --short killed, 3 failed
M4 _full_refspec returns the bare name killed, 4 failed
M5 _is_block_header back to the exact text killed, 24 failed
M6 no YamlError catch killed, 2 failed
M7 committed on prints paths killed, 2 failed

The seven mutants ran before main was merged. The fix code they mutated differs from this head's only in one comment of _land (checked with diff), and the merge changed none of the functions they revert: main's only change to update-shape.py in that span is #742's Upstream and _classify_row.

What it cannot tell

  • The directory in so make <dir> writable is printed raw, as the tool's other prose prints a path (make {README} writable). The command half of the line is quoted; a person who copies the directory into chmod from a root whose path holds a space or a $ quotes it. Left for Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458's checklist.
  • os.access on Windows ignores ACLs and answers by the read-only attribute alone. So on Windows the "make writable" words appear only where that attribute is set, as with the existing checkout-form check. The chmod tests skip there.
  • The doubled-key refusal now covers a quoted twin of shape:. The files: block is still found at its first header and written to the end of the file, as before.

Shared files

Main bf1f099b was merged into the branch (git merge --no-ff, no conflicts; the WIP commit on the branch is untouched). git merge-tree --write-tree, each head read with ls-remote first, on 2026-10-17:

against head result
main bf1f099b clean, tree b89da034 (this head's own tree: the head already holds main)
main, after fix round 1 bb439a29 (its one commit since touches tests/test_pins_child_kind_and_siblings_root_spelling.py only) clean, tree c774b9b3
#770 (l3-755-merged-away-tests, tests/test_update_shape_merged_away.py), the only open PR in the update-shape tests 837920c2 clean, tree 78b508ac
#746 (fix/672-bump-leg-branch-and-push-line), the bump-leg.py twin of the push-line case a5d473ee clean, tree 2c36e404

None of the 36 open pull requests (read at 10:28Z) touches update-shape.py or the four test files this PR changes, so there is nothing else to compare. update-shape.py is not a pinned copy: no project re-pins.

Closes #674

🤖 Generated with Claude Code

Summary by Sourcery

Fix update-shape.py apply reporting, branch handling, rollback guidance, and header detection so its actions and printed commands remain accurate and executable.

New Features:

  • Make update-shape.py apply recognize quoted and spaced files: and shape: headers consistently with the YAML reader.
  • Support pushing branches whose names also exist as tags by using unambiguous full branch refspecs.

Bug Fixes:

  • Correct rollback guidance when restoring a deleted file beneath a non-writable directory.
  • Report only the paths actually included in the resulting commit.
  • Correct current-branch detection and pull-request reruns for branches sharing names with tags.

Documentation:

  • Update header-related test documentation to reflect reader-compatible quoted and spaced headers.

Tests:

  • Add comprehensive coverage for rollback guidance, committed-path reporting, tag-like branch names, branch detection, push commands, quoted headers, duplicate headers, and check/apply agreement.
  • Update existing push-line tests for full branch refspecs.

Brett Heap and others added 2 commits October 16, 2026 14:22
…py: `apply`'s rollback line says to make a shut directory writable first, `committed on <branch>:` names what the commit holds, a branch named like a tag is pushed and read by its full name, and a quoted `files:` or `shape:` header is found as the reader finds it (#674)

Four cases, each measured on main 4ce9045 with a scaffolded project and
local bare remotes, git 2.43.0.

The rollback line. A validator that deleted `contracts/shape-pin.yaml` and
left `contracts/` at 0555 got `... kept as blob <id> in this repository,
then: git -C <root> cat-file blob <id> > <root>/contracts/shape-pin.yaml`,
and pasted, that failed `Permission denied`. `Rollback._restore` now asks
the directory the command writes into, `_nearest_directory`'s answer, and
where the file is absent and that directory will not take it, the line says
`so make <that directory> writable, then:` first. The directory is named by
its full path, since it is the root itself where `contracts/` went too, and
need not be inside the root at all. A present file keeps "so make it
writable", and an absent file under a writable directory keeps the bare
command.

`committed on <branch>:`. `_land` printed `paths`, and `cmd_apply` always
puts the pin and the manifest in it, so a run that only drops a merged-away
row, or keeps a copy with `--accept-local` at the pinned commit, said
`contracts/shape-pin.yaml, project.yaml` of a commit holding the pin alone:
the manifest was rewritten with the bytes it had. The line now names what
`git diff-tree --root --no-commit-id -r --name-only --no-renames -z HEAD`
lists, through the tool's own `_git_answer`, and keeps `paths` only should
git not answer.

A branch named like a tag. In a root tagged `v1.0.0`, `apply --yes --branch
v1.0.0 --push` committed and then refused `pushing v1.0.0`, exit 2, `error:
src refspec v1.0.0 matches more than one`, and the NEXT line it printed failed
the same way pasted, as did `--branch`'s own NEXT line. `push_branch` and both
NEXT lines now push `refs/heads/<b>:refs/heads/<b>` (`_full_refspec`), the
form #689 and #672 ask of `family.py`'s and `bump-leg.py`'s push lines; it
pushes the branch and not the tag, and `-u` still sets the upstream.
`current_branch` read `git symbolic-ref --quiet --short HEAD`, which prints
`heads/v1.0.0` there, so a root ON `v1.0.0` was refused `update-branch-exists`
"('heads/v1.0.0' is checked out)" by `commit_on_branch`, and a `--pr` re-run
there was not recognised by `_awaits_its_pull_request`, said "nothing to do"
and opened nothing. It reads the ref whole now and takes `refs/heads/` off, as
`Rollback.new_branch` does; a HEAD attached anywhere else is on no branch.

A quoted header. `_is_block_header` asked for the exact text `<key>:` once a
comment was stripped, so `"files":`, `'files':` and `files :`, which the
reader and both validators read as the key `files`, passed `check`, which
sent the person to `apply`, and `apply` refused `update-pin-unrecognised`
("0 `files` line(s)"); `"shape":` likewise got `update-manifest-no-shape`,
"has no `shape:` block", which is false. It now asks the reader's own
question, `_split_key(_strip_comment(line.strip())) == (key, "")`, at the
reader's column (no space in front, and no tab among the blanks the reader
refuses), and a line `_split_key` refuses is no header rather than a
traceback. The header is written back as the line was, quotes included. So
`"shape":` beside `shape:` is one key written twice, as the reader reads it,
and is refused by name as #558 refuses a doubled bare `shape:`. The two
docstrings that said such a header "is not found here" say it is.

Left as they were, by the issue's own direction: AGENTS.md:283's placeholder
`git -C <path> push -u origin shape/update-<sha>` (true of the branch it
names; AGENTS.md is at its 492-line cap), `run_validator`'s environment
(a rider that changes no verdict), and `landing_failed`'s `--` before
`origin`, which its line always carried.

Tests: `tests/test_update_shape_apply_lines_and_headers.py`, one or more per
case, on the neighbours' harnesses. The tests that held the bare push form
move with it (`tests/test_update_shape_landing.py`,
`tests/test_update_shape_next_quoting.py`), and
`tests/test_update_shape_header_comments.py`'s docstring no longer says a
quoted header is not found.

State at this checkpoint: the new file passed alone (38 passed) and all
seven mutants were killed; the targeted run was stopped by PID at 48%
for the account swap, with no failure before it.

Closes #674

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main moved from 4ce9045 to bf1f099. The only change to update-shape.py on
main in that span is #742 (`Upstream` and `_classify_row`, the partial-clone
currency row); none of #674's functions. No conflicts, so nothing was
resolved by hand, and no pinned copy changed under the files #674 touches.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes four update-shape.py apply correctness and usability gaps: rollback instructions now name the directory that must be writable, commit summaries reflect the files actually committed, branch pushes and branch detection use unambiguous full refs, and header discovery follows the reader's parsing rules. A new 38-test suite plus updates to existing push/header tests cover the fixes, including end-to-end, rerun, pasted-command, tagged-branch, and partial-rollback scenarios.

Sequence diagram for unambiguous branch push and detection

sequenceDiagram
    participant User
    participant Apply as update-shape.py
    participant Git
    participant Remote
    User->>Apply: apply --branch v1.0.0 --push
    Apply->>Git: symbolic-ref --quiet HEAD
    Git-->>Apply: refs/heads/v1.0.0
    Apply->>Apply: current_branch
    Apply->>Git: push -q -u -- origin refs/heads/v1.0.0:refs/heads/v1.0.0
    Git->>Remote: Push branch ref
    Remote-->>Git: Push result
    Git-->>Apply: Push result
    Apply-->>User: NEXT push line uses full refspec
Loading

Sequence diagram for committed-file reporting

sequenceDiagram
    participant Apply as update-shape.py
    participant Git
    participant User
    Apply->>Git: commit_on_branch
    Git-->>Apply: Commit created
    Apply->>Git: diff-tree --root --name-only --no-renames -z HEAD
    alt Git returns file list
        Git-->>Apply: Files held by HEAD
        Apply-->>User: committed on branch: actual files
    else Git does not answer
        Git-->>Apply: No answer
        Apply-->>User: committed on branch: requested paths
    end
Loading

Flow diagram for update-shape apply corrections

flowchart TD
    Apply[update-shape.py apply] --> Rollback[Rollback restore]
    Apply --> Land[Commit and landing]
    Apply --> Headers[Header discovery]
    Rollback --> Directory[_nearest_directory]
    Directory --> Writable{Directory writable?}
    Writable -->|No| Instruction[Print make directory writable]
    Writable -->|Yes| Restore[Print cat-file restore command]
    Land --> CommitFiles[diff-tree lists committed files]
    Land --> Push[_full_refspec]
    Push --> BranchRef[refs/heads/branch to refs/heads/branch]
    Headers --> ReaderRules[_split_key and _strip_comment]
    ReaderRules --> Consistent[check and apply agree]
Loading

Flow diagram for reader-compatible header discovery

flowchart TD
    Line[Header line] --> Strip[_strip_comment and strip whitespace]
    Strip --> Indent{Top-level indentation valid?}
    Indent -->|No| NotHeader[Not a header]
    Indent -->|Yes| Split[_split_key]
    Split --> Parsed{Reader accepts key?}
    Parsed -->|No| NotHeader
    Parsed -->|Yes| Match{Key matches files or shape?}
    Match -->|Yes| Found[Find and preserve header exactly]
    Match -->|No| NotHeader
Loading

File-Level Changes

Change Details Files
Align header detection and rewriting with the repository reader's key parsing rules.
  • Use _split_key and comment stripping to recognize quoted, spaced, and commented files:/shape: headers while safely rejecting malformed lines.
  • Preserve the original header spelling and reject duplicate logical keys consistently.
  • Add end-to-end and parameterized coverage for pin and manifest variants, malformed headers, duplicate headers, and validator/check/apply agreement.
update-shape.py
tests/test_update_shape_apply_lines_and_headers.py
tests/test_update_shape_header_comments.py
Make branch identification and push commands unambiguous when a branch shares a tag name.
  • Read the full symbolic HEAD ref and normalize refs/heads/ before comparing current branches.
  • Centralize full branch refspec generation and use it for direct pushes and printed NEXT commands.
  • Verify tagged-name branches, reruns, pull-request flows, and pasted commands.
update-shape.py
tests/test_update_shape_apply_lines_and_headers.py
tests/test_update_shape_landing.py
tests/test_update_shape_next_quoting.py
Improve rollback guidance and commit reporting so printed recovery information matches actual filesystem and Git behavior.
  • Identify the nearest existing directory receiving a redirected restore and tell users to make it writable when necessary.
  • List files actually present in the resulting commit via git diff-tree, retaining the previous path list only as a fallback.
  • Add coverage for read-only parent directories, deleted directory trees, accepted-local changes, and merged-away rows.
update-shape.py
tests/test_update_shape_apply_lines_and_headers.py

Assessment against linked issues

Issue Objective Addressed Explanation
#674 Make rollback instructions for absent files account for a non-writable directory, and report the paths actually included in the resulting commit. ✅
#674 Handle branches whose names collide with tags consistently: identify the checked-out branch correctly and use an explicit branch refspec for pushes and printed next steps. ✅
#674 Find the pin’s files: and manifest’s shape: headers using the YAML reader’s key-parsing rules, while preserving the original header spelling. ✅

Possibly linked issues


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

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-17T11:23:25Z)

Lane 1's independent review of #810 at 31c2ca0: CHANGES NEEDED

All four fixes are correct, and every line of #674 is fixed, measured on main and at the head. Two things must change before this lands: the PR adds two Sonar code smells of kinds the review rules forbid (S1192 and S5778). Both fixes are small and mechanical, and I applied and tested them below. With those two changes and nothing else, I would call this PR-READY.

Basis: the head already contains main bf1f099b (the merge base is main), so the local merge's tree is the head's tree. git 2.43.0, with git-filter-repo on PATH. Every probe used local bare remotes and a stub gh, and nothing was pushed.

The issue's lines

#674 line main bf1f099b head 31c2ca02
Rollback line over a shut directory ...kept as blob cbda7c46e0c0 in this repository, then: git -C <root> cat-file blob ...; pasted with contracts/ at 0555: Permission denied, rc 2 fixed: ..., so make <root>/contracts writable, then: git -C ...; pasted while shut it fails (rc 2), and after chmod it restores the pin's bytes (rc 0)
committed on <branch>: committed on shape/update-partial: contracts/shape-pin.yaml, project.yaml (the commit holds the pin only) fixed: the pin only, in both the --accept-local case and the merged-away-row case
Branch named like a tag: push error: src refspec v1.0.0 matches more than one, exit 1 fixed: pushes the branch; see finding-free check 2
Branch named like a tag: read REFUSED update-branch-exists: --branch v1.0.0 already exists and is not the current branch ('heads/v1.0.0' is checked out) fixed: commits onto it; the --pr re-run opens the missing pull request
Quoted files: / shape: header update-pin-unrecognised ... has 0 files line(s); update-manifest-no-shape ... has no shape: block fixed: agrees with the reader on all 72 spellings (main disagreed on 26)
Rider: the lexists choice was untested no test fixed: a 0555 contracts/ over an absent pin is tested (mutants M1 and M2 are killed)
Rider: run_validator's environment left alone left alone, as the issue directs
+x half refuted still refuted: BRANCH_RE is unchanged

The new test file run against main's update-shape.py gives 34 failed, 4 passed. The 4 that pass are the "still no header" guards, which hold on main too.

Findings, ranked

1. MUST: S1192 (CRITICAL), "refs/heads/" is written 4 times in update-shape.py

SonarCloud's open issues for pullRequest=810 (api/issues/search):

python:S1192 CRITICAL update-shape.py 2342 Define a constant instead of duplicating this literal "refs/heads/" 4 times.

The PR adds two of the four. Counting '"refs/heads/"' in update-shape.py: main has 2, the head has 4 (current_branch :2342 and :2344, Rollback.new_branch :2845 and :2846). The gate passed, but the PR's new-code-smells count is 2, and these are those two.

Fix: one helper for both readers. In current_branch, replace

    head = proc.stdout.strip()
    if not head.startswith("refs/heads/"):
        return None
    return head[len("refs/heads/"):] or None

with

    return _branch_of(proc.stdout.strip())


def _branch_of(ref: str | None) -> str | None:
    """The branch the full ref `ref` names, `refs/heads/` taken off, or None
    where it names none: a detached HEAD, or one attached outside
    `refs/heads/`. One spelling for `current_branch` and
    `Rollback.new_branch` (#674)."""
    prefix = "refs/heads/"
    if not ref or not ref.startswith(prefix):
        return None
    return ref[len(prefix):] or None

In Rollback.new_branch, replace

        head = _symbolic_head(self.root)
        start = (head[len("refs/heads/"):]
                 if head and head.startswith("refs/heads/") else None)

with

        start = _branch_of(_symbolic_head(self.root))

2. MUST: S5778 (MAJOR), more than one raising call in a pytest.raises

python:S5778 MAJOR tests/test_update_shape_apply_lines_and_headers.py 223 Refactor this exception test to have only one invocation possibly throwing an exception.

In test_a_line_that_is_no_files_header_to_the_reader_is_still_counted_none, the Row(...) calls are built inside the block. Replace

    with pytest.raises(Refusal) as caught:
        update_shape.rewrite_shape_pin(
            before, NEW_COMMIT, NEW_TREE,
            [update_shape.Row(path, digest) for path, digest in NEW_ROWS])

with

    rows = [update_shape.Row(path, digest) for path, digest in NEW_ROWS]

    with pytest.raises(Refusal) as caught:
        update_shape.rewrite_shape_pin(before, NEW_COMMIT, NEW_TREE, rows)

I applied both fixes at the head in a scratch clone. The literal count went from 4 to 1. The new file, test_update_shape_partial_rollback.py and test_update_shape_landing.py gave 134 passed in 120.80s. After the push, SonarCloud should show 0 new code smells on the PR.

3. Optional: the fallback sentence is slightly wider than the code

named or paths falls back to paths when git lists nothing, as well as when git does not answer. I could not reach that case: apply commits with git commit -- <paths>, which I read but did not run for an empty or merge commit. If you want the words exact:

  • Body: replace "and falls back to paths only if git does not answer." with "and falls back to paths only if git does not answer or lists nothing."
  • _land comment: replace "Should git not answer, the line keeps the paths it was asked to commit" with "Should git not answer, or list nothing, the line keeps the paths it was asked to commit".

4. Optional: the directory in "make writable" is printed unquoted

The command half of the line is quoted, and it works pasted under a root holding my $HOME id 'q' "d" ; & |, a newline, or a non-UTF-8 byte: each failed while shut and restored once opened (rc 0). The prose half prints the same directory raw:

so make .../my $HOME `id` 'q' "d" ; & |/Atlas/contracts writable, then: git -C '.../my $HOME `id` '"'"'q'"'"' "d" ; & |/Atlas' cat-file ...

It is prose, not a command, and the issue's wording asks for make <that directory> writable. But shlex.quote(str(shut)) would let a person copy it into chmod u+w safely. This does not block landing.

Correctness checks with no finding

  1. The header finder against the reader. I tried 24 spellings, for the pin with LF and CRLF line ends and for the manifest: "files":, 'files':, files :, "files" :, "fil\x65s":, a trailing # c and a tab before the comment, a leading no-break space, files\xa0:, indented, tab-indented, - files:, ? files, &a files:, !!str files:, files: [], an unclosed "files, files[0]:, "files" x:, a BOM, and a glued #. The head agrees with parse_yaml on all 72, with no traceback and the header kept as it was; main disagrees on 26. A # "files": comment line and an indented "files": inside a top-level note: | block scalar are not headers at the head; main refused both pins. The doubled "shape": and shape: is refused by name in both orders.
  2. The full-refspec push. Tag v1.0.0 at f798cf1 and branch v1.0.0 at 7b42985: the head's git push -q -u -- origin refs/heads/v1.0.0:refs/heads/v1.0.0 exits 0. The remote holds refs/heads/v1.0.0 7b42985 and no tag, and the upstream is origin refs/heads/v1.0.0. For an ordinary branch (shape/update-x) both forms give empty output, the same branch.* config, the same tracking ref and the same remote refs.
  3. Short-name reads. Every git call that takes the branch now uses a full ref or a branch-only command (show-ref refs/heads/<b>, checkout -q -b, branch -D, and refs/remotes/origin/<b> in landing_failed). The update-branch-exists remediation is git checkout v1.0.0; next to the tag, git warns refname 'v1.0.0' is ambiguous and lands on refs/heads/v1.0.0.
  4. committed on. diff-tree --root -z lists a root commit's files, and names with a space or a newline come back raw and NUL-separated. diff.orderFile is not read. The line is printed right after commit_on_branch, so HEAD is this run's commit.
  5. The rollback's "make writable". A present file still gets "make it writable". An absent file under a shut contracts/ names contracts. A deleted contracts/ under a shut root names the root, with mkdir -p. A deleted contracts/ under a writable root gets the bare line (update-shape.py: apply refuses to rewrite a file that has a second, hard-linked name, and a rollback that cannot put a byte back names it with the line that finishes it and puts back the rest (#422, #428) #560's test passes). A contracts replaced by a link to a shut directory outside the root never reaches the new words: it gets _linked_by_hand's "once contracts is this root's own directory again".
  6. Byte for byte against main. I ran each tool on its own copy of one root, with fixed commit dates. check, apply --yes and apply --yes --branch X --push give identical stdout, stderr and commit. apply --yes --branch X differs only in the NEXT push word, refs/heads/shape/update-x:refs/heads/shape/update-x, which is intended and stated in the body.
  7. No new network call, and no new write or remove path. The only new subprocess is a local git diff-tree, and the rollback change is text only.

Tests

Windows

_full_refspec, current_branch, the header finder and diff-tree -z have no OS-specific behaviour. The two chmod tests are WINDOWS_SKIP, as their neighbours are. The pasted-push tests run without a shell, so they run everywhere. respell_the_header edits bytes and keeps any CR. There are no text-mode byte counts. tests-git-bash passed. The body's os.access caveat is accurate. At 11:21Z the Linux tests job was still IN_PROGRESS.

The texts

The title and body are true at the head apart from the optional sentence in finding 3. The form refs/heads/<b>:refs/heads/<b> is the one #689 asks for, one of the two #672 allows, and the one #746 uses, so the three tools print one form.

For the checklist (outside #674)

  • Quoted or spaced commit: and tree_sha256: in the pin are still refused while the reader takes them. Measured at the head: "commit": and commit : give update-pin-unrecognised ... has 0 commit line(s), and "tree_sha256": gives has 0 tree line(s). It is the same class as update-shape.py apply: a rollback line fails over a read-only contracts/ unsaid, committed on <branch>: lists a path the commit lacks, --branch v1.0.0 cannot push beside a tag v1.0.0, and a quoted "files": header is refused #674's headers, for the pin's two scalar lines (and probably the manifest's shape: children, which I did not run).
  • The pin's doubled files key is not refused by name, as the manifest's doubled shape is. The first header wins and everything after it is replaced. With "files": first and files: last, main kept both blocks and rewrote the last; the head keeps one block. Both read back the new rows.
  • A remote.origin.push mapping now changes the result. With refs/heads/*:refs/heads/review/* configured, main's bare push went to refs/heads/review/b1 (upstream refs/heads/review/b1), while the head's goes to refs/heads/b1. The head's result matches gh pr create --head b1, so it reads as a fix, but the body does not mention it.
  • gh pr create --head v1.0.0 on GitHub next to a tag of the same name was not measured: only the stub gh ran here.
  • On Windows, a directory with the read-only attribute still takes new files, so after a write that failed for another reason the words "make writable" can name the wrong cause. Unmeasured.

Lane: openreposhape-1 (openRepoShape-1)

Brett Heap and others added 2 commits October 17, 2026 11:32
…anch off its full ref through one `_branch_of`, and the header test builds its rows before `pytest.raises`, so SonarCloud's S1192 and S5778 on #674's lines go (#674)

Lane 1's review of #810 at 31c2ca0 (CHANGES NEEDED, issuecomment-6233308231)
found two code smells the PR added, and nothing wrong with the four fixes.

S1192: `"refs/heads/"` was written four times in update-shape.py, two of them
`current_branch`'s new reads and two of them `Rollback.new_branch`'s existing
ones. `_branch_of(ref)` is now the one place that takes `refs/heads/` off a
full ref, returning None for a detached HEAD or one attached outside
`refs/heads/`, and both callers use it. The literal is written once.

S5778: `test_a_line_that_is_no_files_header_to_the_reader_is_still_counted_none`
built its `Row` list inside the `pytest.raises` block beside the call under
test, so a failure of either could satisfy it. The rows are built first.

The `_land` comment now says the line keeps `paths` where git does not answer
or lists nothing, as `named or paths` does.

Left for #458's checklist, not changed here: the directory in `so make <dir>
writable` is printed raw, as the tool's other prose prints a path; the command
half of the line is quoted.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-1, 2026-10-17T13:06:58Z)

Lane 1's re-verification of #810 at a84f445: PR-READY

Both required fixes are in exactly as proposed, and nothing else in the code changed. The merge of main is clean, the tests pass on a local merge with current main, and SonarCloud reports 0 issues on this head. The Linux tests job is still running.

Check Measured
088a2267 against 31c2ca02 Only update-shape.py and the new test file change (19 lines added, 12 removed). The diff is the _branch_of helper used by current_branch and Rollback.new_branch, the rows list built before pytest.raises, and the _land comment "Should git not answer, or list nothing". The only other change is a docstring wording: current_branch's docstring now says "taken off by _branch_of". No other line moved.
a84f445d adds only main's merge git merge-tree --write-tree 088a2267 741679e7 gives ac6b5f0c, which is exactly a84f445d's tree. Main bf1f099b..741679e7 touches none of the PR's 5 files. The PR's own diff is the same patch on both sides of the merge (patch-id --stable matches).
"refs/heads/" count in update-shape.py 1 (it was 4 at 31c2ca02)
pytest.raises blocks in the new test file :223 rows = [update_shape.Row(...) ...] comes before the block, and :225-226 makes one call, rewrite_shape_pin(before, NEW_COMMIT, NEW_TREE, rows). The other block (:194) makes one call, rewrite_manifest.
Local merge with current main 98f14976 Clean (tree 92939c94); main's newer commits cbff780c and 98f14976 touch none of the PR's files or the test neighbours. Run in one mode, with git-filter-repo on PATH and no GIT_ALLOW_PROTOCOL: the new file, test_update_shape_header_comments.py, test_update_shape_landing.py, test_update_shape_next_quoting.py, test_update_shape_partial_rollback.py, test_pin_rewrite_layout.py, test_update_shape.py, test_update_shape_pr_base.py, test_update_shape_add_ignored.py and test_update_shape_merged_away.py gave 509 passed in 321.27s, exit 0.
SonarCloud at a84f445d The analysis read a84f445d (2026-10-17T12:27:11Z). Gate OK, 0 open issues, 0 new code smells, 0.0% new-code duplication, 0 bugs, 0 vulnerabilities.
Linux tests job at a84f445d IN_PROGRESS since 12:27:09Z, still running at 13:05Z (run 39032570205). tests-git-bash, package and SonarCloud have passed.

New since the first review:

  • One optional wording fix in the body. The fix-round paragraph says "Nothing else in update-shape.py moved but the wording of one comment in _land ("or list nothing")." The current_branch docstring also changed. To be exact, replace that sentence with: "Nothing else in update-shape.py moved but the wording of one comment in _land ("or list nothing") and of current_branch's docstring, which now names _branch_of." It needs no re-run.
  • Finding 4 (the unquoted directory in "make writable") is left for Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458, as lane 4 says. The rest of the earlier "For the checklist" list still stands.
  • Landing gate. Main is now 98f14976, past the 741679e7 this head merged. The merge with it is clean and passes the set above. The landing still needs the Linux tests result at a84f445d.

Lane: openreposhape-1 (openRepoShape-1)

@brettheap
brettheap marked this pull request as ready for review October 17, 2026 17:16
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

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 7 hours and 30 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap merged commit d17564d into main Oct 17, 2026
5 checks passed
@brettheap
brettheap deleted the fix/674-update-shape-apply branch October 17, 2026 17:21
brettheap pushed a commit that referenced this pull request Oct 17, 2026
main gained #748 (#662, `431c8cf8`), #731 (#692, `d66a9054`), #810
(#674, `d17564d9`), #809 (#688, `42aed788`), #816 (refs #684,
`fa04d2cd`), #813 (#689, `f6d17bc5`) and #723 (#663, `bad05d21`) since
64ec47c. No conflict, and none of them touches README.md, the handbook,
AGENTS.md, docs/ or the README cap's ledger, so every handbook marker
reads as before. README.md is 1474 lines, its cap; AGENTS.md 494, its cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
#813 and #723, v1.0.0's quoted-header and plain-path-clone limits closed and the counts current to main bad05d2 (#563)

Each entry is the one RV630 corrected in its pre-review of the B2 stage,
put at its anchor, and checked against its squash on main: the
`git merge-tree --write-tree` of main before the squash with the head the
entry was written from equals the squash's tree for all seven.

- #810 (#674, `d17564d9`): a new bullet, "`apply`'s lines, its branch and
  its headers.", after "Headers, branches and messages.". It closes
  v1.0.0's quoted `files:`/`shape:` header (v1.0.0.md:439) and `apply`'s
  half of the paths listed as staged (:433); `bump-leg.py`'s half waits
  for #746.
- #731 (#692, `d66a9054`): appended to "Folders, asked before they are
  made.".
- #813 (#689, `f6d17bc5`): appended to "`--tracking-branch`.".
- #809 (#688, `42aed788`): a new bullet, "What `family.py init` records
  and commits as.", after "Names that are not UTF-8.".
- #816 (part of #684, `fa04d2cd`): a new bullet, "More holder states,
  named.", after "family.py refuses before it writes.". #816 closes no
  issue; #684 keeps its reftable case and is on v1.1.1.
- #748 (#662, `431c8cf8`), then #723 (#663, `bad05d21`): in the CI
  bullet, after #713's sentence. With #554 and #578, #748 closes v1.0.0's
  limit on fixtures that clone a just-pushed remote by plain path (:530).

The counts, in the header comment, the counts paragraph, "What changed"
and the open list: main `bad05d2`; 120 pull requests since `bfed143`;
at 18:02Z, 3 of the `v1.1.0` milestone's 133 issues open and 130 closed.
The open list is #563 (#630), #672 (#746) and #686 (#795); #684 and #695
(#738) are on v1.1.1. #746 and #795 have not landed, so neither has an
entry yet. Nothing since 2000488 touches pyproject.toml,
src/openreposhape/, install.sh or .github/workflows/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…it closed as one clause, the list's and the known limits' markers made final, the triage lines named by their issues, and the counts current to main fc86720 (#563)

- #746 (#672, `fc867207`): a new bullet, "`bump-leg.py`'s branch and its
  lines.", after "`bump-leg.py --dry-run` gives the real run's answer.".
  `git merge-tree --write-tree bad05d2 a5139a3`, the head #746 was
  gated and landed on, equals the squash's tree `bb920ac1`. The head the
  entry was written from, `4a014a79`, sat on #723's head before its
  squash and does not merge clean with main; `scripts/bump-leg.py` is one
  blob (`226e7b0d`) at `4a014a79`, `a5d473ee`, `a5139a32` and the squash,
  and the squash's own lines in each of its five files equal that head's,
  so the entry stands as written.
- v1.0.0's limit on the paths listed as staged (v1.0.0.md:433) is one
  clause, in #746's entry: with #810 it closes. #810's half-clause is cut.
  `bump-leg.py --dry-run` over a pin its validators refuse (:440) stays a
  limit, which the dry run now says.
- "What changed" runs to main `fc86720`, and says #795 is added if it
  lands next; Known limits' re-read marker and "as read so far" are cut.
  The counts marker stays as the release-body tool reads it.
- Known limits name each triage by its issue: "the checklist triage of the
  L4 lines on #457 and #459" and "of the L1 lines on #457", where they
  named lanes 4 and 1 and D9, and each id reads "#457's L4-05", "#459's
  L4-24" and so on.
- Counts in the four places: main `fc86720`; 121 pull requests since
  `bfed143`; at 19:08Z, 131 of the `v1.1.0` milestone's 133 issues
  closed, the open two #563 (#630) and #686 (#795).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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