Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ checkout of this standard, pointed at the project.
```sh
python3 scripts/bump-leg.py --root <path> --leg spec --to <40 hex> --dry-run
# 1. show the human the printed `old -> new` line
git -C <path> switch -c <branch> # 3. never commit on the tracking branch
python3 scripts/bump-leg.py --root <path> --leg spec --to <40 hex>
git -C <path> push -u origin <branch> # then open a pull request
```
Expand All @@ -342,22 +343,21 @@ git -C <path> push -u origin <branch> # then open a pull request
agree.** The digest is RECOMPUTED from the leg's own objects, never
adjusted to fit; a hand-written one turns the check into a formality and
hides exactly the drift it exists to find.
3. **The commit is made on a BRANCH and lands as a pull request.** The tool
refuses to commit onto the tracking branch for that reason. Never suggest
a direct push to the default branch, and never `--admin`.
3. **The commit is made on a BRANCH, created BEFORE the real run, and lands
as a pull request.** A new project stands on its tracking branch; the dry
run runs there and prints the `git switch -c`, but the real run is refused.
Never suggest a direct push to the default branch, and never `--admin`.
4. **A refusal is not something to work around.** A commit no branch of the
leg's remote contains must be PUSHED, not pinned — a pin the rest of the
world cannot fetch is a root it cannot bootstrap. A red validator has
already been rolled back; the finding above it is the thing to fix.
5. The leg is left DETACHED at the new commit. `make bootstrap` in the root
re-places it on its tracking branch AT the new pin; say so when you relay
the result.
re-places it on its tracking branch AT the new pin; say so in your relay.
6. **`--trailer "<Key>: <value>"` puts a line on the commit this writes.**
Repeatable, kept in the order given — the same flag `update-shape.py
apply` and `family.py bump` already take. `--dry-run` prints the
trailers it would write, after the `old -> new` line, so the human reads
them before they land rather than after. A lane passes its `Lane:` line
and this estate's `Co-Authored-By:` there, not by amending afterwards.
Repeatable, kept in the order given, as `update-shape.py apply` and
`family.py bump` take it. `--dry-run` prints them after the `old -> new`
line, so the human reads them before they land. A lane passes its `Lane:`
line and this estate's `Co-Authored-By:` there, not by amending.

## Creating a family, and adding a member

Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,12 +500,12 @@ there does not advance the project. Advancing it is one lockstep commit in
the root, written by the tool from a checkout of this standard:

```sh
git -C ../Atlas switch -c <branch> # the tool will not commit on main
python3 scripts/bump-leg.py --root ../Atlas --leg code --to <40-hex commit>
git -C ../Atlas push -u origin <branch> # then open a pull request
```

Editing the pin file by hand to make the validator agree is what the
validator exists to refuse.
Editing the pin by hand to make the validator agree is what it exists to refuse.

**Variants.** An existing repository becomes the root in place with
`adopt-project.py plan`, then `check`, then `execute`, the human answering
Expand Down Expand Up @@ -972,8 +972,7 @@ Moving those three facts by hand is how they come apart, so one command moves
them together, run from a checkout of this standard:

```sh
python3 scripts/bump-leg.py --root <path-to-the-project> --leg spec \
--to <40 hex commit> # --dry-run prints the move and writes nothing
python3 scripts/bump-leg.py --root <path> --leg spec --to <40 hex> # --dry-run prints the move and writes nothing
```

It fetches the commit into the leg, checks the leg out AT it, recomputes the
Expand All @@ -985,14 +984,15 @@ included — then runs the project's own `validate-pins.py` and
`validate-manifest.py` and commits ONCE with explicit pathspecs.

It REFUSES a `--to` that is not exactly 40 hex, a commit no branch of the leg's
remote contains (including one committed inside the mount and never pushed — a
pin the rest of the world cannot fetch is a root it cannot bootstrap), a dirty
root, an untracked or ignored workflow file it would rewrite, a root standing on
its own tracking branch (the next step is a pull request, never a push to the
default branch), and a validator that goes red — rolling the pin, the workflow
files, the leg's checkout and the index back first, so a refusal leaves the tree
exactly as it was found. The leg is left DETACHED at the new commit; `make
bootstrap` re-places it on its tracking branch AT the new pin.
remote contains (including one committed in the mount and never pushed — a pin
the rest of the world cannot fetch is a root it cannot bootstrap), a dirty root,
an untracked or ignored workflow file it would rewrite, a root standing on its
own tracking branch (the next step is a pull request, never a push to the
default branch: `git switch -c <branch>` first; `--dry-run` writes nothing, so
it runs there and prints that command), and a validator that goes red — rolling
the pin, the workflow files, the leg's checkout and the index back first, so a
refusal leaves the tree as it was found. The leg is left DETACHED at the new
commit; `make bootstrap` re-places it on its tracking branch AT the new pin.

## Bootstrap is COPIED into the project, not fetched

Expand Down
11 changes: 7 additions & 4 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,9 @@ <h3>Advancing a leg</h3>
that this user cannot open, naming the path and the system&rsquo;s reason, with the same ids
and the remedy of making it readable and running the command again; a root standing on its
own tracking branch (the next step is a pull request, never a
push to the default branch); and a validator that goes red &mdash; rolling the pin, the
push to the default branch: <code>git switch -c &lt;branch&gt;</code> first;
<code>--dry-run</code> writes nothing, so it runs there and prints that command); and a
validator that goes red &mdash; rolling the pin, the
workflow files, the leg&rsquo;s checkout and the index back first, so a refusal leaves the
tree exactly as it was found.</p>
<p>The leg is left DETACHED at the new commit; <code>make bootstrap</code> re-places it on its
Expand Down Expand Up @@ -1799,9 +1801,10 @@ <h2>For AI assistants</h2>
name, added (#289); <code>bump-leg.py</code> refusing a workflow file, the workflows directory
or the leg&rsquo;s pin that this user cannot open, by name, added (#383); what
<code>setup.sh --help</code> does from a download, which clones nothing, and which commands
list every flag, added (#341) &mdash; a page cannot name the commit that contains it, and
the blob below is the fact a test checks.
README.md blob 5465d135ca7263010ee354424041f90c4553d2e7. Every command, flag, exit code
list every flag, added (#341); <code>bump-leg.py --dry-run</code> running on the tracking
branch and naming the branch the real run needs, added (#477) &mdash; a page cannot name
the commit that contains it, and the blob below is the fact a test checks.
README.md blob 3a4f26ade02d2086157f1bd30c533444101710f8. Every command, flag, exit code
and file name on this page is quoted from that README and from AGENTS.md.</p>
</footer>
</main>
Expand Down
24 changes: 19 additions & 5 deletions scripts/bump-leg.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
git's spelling, never refused as untracked.
* the root standing on its own tracking branch. The next step this prints
would then be a push to the default branch, and these organisations are
pull-request only.
pull-request only. `--dry-run` commits nothing, so it runs there, and ends
by naming the `git switch -c` the real run needs (#477).
* a workflow file under `.github/workflows/`, or the leg's
`contracts/<role>-pin.yaml`, that is not UTF-8 (a Latin-1 comment, a
UTF-16 file with a byte-order mark): `bump-leg-workflow-unreadable` and
Expand Down Expand Up @@ -96,6 +97,7 @@
import errno
import os
import re
import shlex
import stat
import subprocess
import sys
Expand Down Expand Up @@ -1000,13 +1002,16 @@ def _ensure_root_clean(root: Path) -> None:


def _branch_to_bump_from(root: Path, project: "Project", role: str,
commit: str) -> str:
commit: str, *, dry_run: bool = False) -> str:
"""The branch on which the bump commits, refused if there is none or
it is the project's own tracking branch.

Split from `cmd_bump` for #136: these organisations are pull-request
only, so a bump is refused before it ever makes a commit whose only
next step would be a push to the default branch.
next step would be a push to the default branch. A `--dry-run` makes no
commit, so on the tracking branch it is let through and ends by naming
the branch the real run needs (#477); a root on no branch at all is
refused either way.
"""
branch = current_branch(root)
if branch is None:
Expand All @@ -1016,7 +1021,7 @@ def _branch_to_bump_from(root: Path, project: "Project", role: str,
"open a pull request from",
"Remediation: `git -C " + str(root) + " switch -c bump/"
+ f"{role}-{commit[:12]}` and re-run.")
if branch == project.tracking:
if branch == project.tracking and not dry_run:
raise Refusal(
"bump-leg-on-tracking-branch",
f"{root} is on {branch!r}, which {MANIFEST} declares as this "
Expand Down Expand Up @@ -1432,7 +1437,8 @@ def cmd_bump(args) -> int:
commit = _checked_target_commit(args.to)

_ensure_root_clean(root)
branch = _branch_to_bump_from(root, project, role, commit)
branch = _branch_to_bump_from(root, project, role, commit,
dry_run=args.dry_run)
submodule = _initialized_submodule(root, path)
was = _recorded_leg_commit(root, path)

Expand Down Expand Up @@ -1463,6 +1469,14 @@ def cmd_bump(args) -> int:
"fetched into, which is how the digest above was computed; the "
"working tree, the pin, the workflows and the index are "
"untouched.")
if branch == project.tracking:
# LAST, after everything a dry run on a branch prints, so that
# line is the whole of the difference between the two (#477).
# The command is quoted, so a root under `My Projects/` pastes.
print(f"\nthe real run needs a branch: {root} is on {branch!r}, "
"the tracking branch. Create one first with: "
+ shlex.join(["git", "-C", str(root), "switch", "-c",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit the switch command in PowerShell syntax on Windows

When the assembly-root path on Windows contains an apostrophe, such as C:\work\O'Brien\Atlas, shlex.join emits POSIX's closed-escaped-reopened form ('O'"'"'Brien'), which PowerShell does not parse as one path argument. Consequently the copy-and-paste command printed by a successful dry run fails before creating the branch required for the real run. Use platform-aware quoting and test the rendered line with PowerShell semantics rather than round-tripping it through shlex.split and invoking the resulting argv directly.

AGENTS.md reference: AGENTS.md:L346-L349

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(lane openRepoShape-3, 2026-10-12T23:40Z) Thank you. This is a known limit for v1.0.0, not a blocker for this PR. The release bar is ordinary repositories on all three OSes, with exotic inputs documented as known limits, and an apostrophe in a Windows path is one. It is also not new with this line: update-shape.py's NEXT lines (shlex.join, e.g. update-shape.py:2419, :2900) print POSIX-quoted commands on Windows the same way; only shape-doctor.py carries a PowerShell alphabet. It goes on the tools parent #458's checklist as one item covering every printed command outside the doctor: PowerShell-aware quoting on Windows, and tests that render the line with PowerShell semantics rather than through shlex.split.

f"bump/{role}-{commit[:12]}"]))
return 0

# ---- the rewrite and the commit, remembered byte for byte so a failure
Expand Down
Loading
Loading