Repository navigation
openRepoShape v1.2.0
v1.2.0 is a minor release, the next after v1.1.1. It adds no command and no
flag. It adds refusals and findings, each under its own code, where a tool
read a path it could not read as absent, ended in a traceback, or said ok
and failed later, after a repository or a leg was made. It fixes how
adopt-project.py check rehearses the split, how the YAML reader takes a
key written twice and a block scalar, how bump-leg.py and family.py bump
fetch, how a holder's make bootstrap says where git's update stopped, and
what the scaffold, setup and both openRepoShape commands refuse before
they act. It changes pinned shape copies, so a project or a family holder
takes those fixes into its own gates by update-shape.py (below). It is
tagged on main.
At main 961640c, on 2026-10-22, 100 pull requests had landed on main
since v1.1.1's commit, 5017830, the last of them these notes (#1155). At
19:54Z, 91 of the v1.2.0 milestone's 91 issues were closed, and 32 merged
pull requests were in it.
What 1.2.0 fixes
- A path this user cannot read is named, never read as absent. From
Python 3.14,Path.is_file(),is_dir()andexists()answer False to
anyOSError, so a path this user could not search read as one that was
not there; up to 3.13 they raise, and the run ended in a traceback. Every
shipped tool now refuses such a path by name, on every Python, but at
eleven sites left to #1110 and over a root's own.git(#1146, #1148;
see the known limits), and a test holds each tool's guards to that rule
(#934, #976, #975, #958, #956, #951, #955, #970, #1072, #1111, for #430). adopt-project.py checkfinds whatexecutewould meet. It
rehearses the split in the work directory, with the source's object
format and none of this machine's hooks; it names a symlink target no
system can make (a NUL after its first byte, or over 4095 bytes), a link
into the clone's.gitand a pin's file reached through a kept link; and
it refuses a hand-edited plan value read as a number or a boolean, each
before either leg exists (#1031, #965, #1070, #1091, #1016).planasks
its flags' own refusals before it clones, andcheckandexecutename
the same first problem (#963, #1015).- The YAML reader refuses a key written twice, and reads a block
scalar as YAML does or refuses it by its line (#974, #1021). The scaffold
andadopt-project.pyspell aneutral_product_pins:entry one way,
quoted wherever a stock reader would misread it (#995, #1090). - Fetches that answer for a branch.
bump-leg.pyandfamily.py bump
no longer move the mount's own branches, and leave out every refspec that
can bring a tag, or that puts a branch under another name, through one
rule both import (#937, #946, #1027, #1068, #1087). A commit only a tag
reaches is named so, whichever kind of tag reaches it (#988). - A holder's
make bootstrapfails by name where git's update
stopped: at a gitlink with no url, at a commit a fetch did not bring,
or where a signal ended git, where it saidfamily bootstrap ok. A token
reaches no trace, a member's make undermake -jNno longer warns of the
jobserver, and the FAILED line names the member's own exit code past a
-make, or past|| trueand a-command (#889, #954, #1045, #1076,
#1075, #950, #1023, #891).validate-family.py's remedies type as
printed, in order (#889, #1025, #1069). - The scaffold and setup. A push or a seed fetch no longer waits at
git's username prompt, a login or key the host refuses names its fix, and
the help says what can still make the re-run wait (#971, #1012, #1101);
the plan names the tracking branch and, on disk, the visibility (#969); a
flag passed empty is refused by name (#972, #1010); a Windows device name
is refused (#1010, #1041); and setup's credential-helper check and its
step (4) read what the run will meet (#1008, #1022, #949, #1011). - Both
openRepoShapecommands. The onepipxplaces refuses what the
bash one refuses wherever it prints a value or hands one to git (#947);
under theClocale a letter past ASCII is no longer "a control
character", the regression v1.1.1 named (#986); and the bash--doctor
checks the ref before it clones (#967). - Lines a person pastes.
make bootstrap'smerge --ff-only,
--continue,--abortandsubmodule updatelines give a leg's path
and an upstream as one shell word (#944, #973); the split message names
each path asplanshows it (#1030); and every remedy that names
family.py bumpnames--family-root(#1002). - A leg on a branch spelled like the tracking branch.
make bootstrap
leaves a leg whose branch folds to the tracking branch on macOS or
Windows (Main, or another Unicode form), where it moved the branch
under HEAD and left the index at the old commit (#948, #984, #1006). - CI. The full suite and the Python 3.9 jobs run on
ubuntu-24.04
(#1095), the release's install check runs the doctor over a scaffolded
root (#945), and every release's notes are held to the one-line install
(#887, #959).
Install and upgrade
-
The newest release, by the one line:
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashIt places the bash
openRepoShape. The same line again upgrades.
… | bash -s -- v1.2.0names this release.openRepoShape --version
printsopenRepoShape (opensoft/openRepoShape @ v1.2.0). -
From PyPI:
pipx install openreposhape==1.2.0for this release, and
pipx upgrade openreposhapeto upgrade one already installed.
openRepoShape --versionprints
openRepoShape 1.2.0 (opensoft/openRepoShape @ 961640c681b54d9258e6c9d3eab8c6f46a26008b).
v1.1.0's notes say wherepipxcomes
from, and that the two installers do not replace each other's command. -
A project's and a holder's shape copies. A copied file reaches no
project by itself. v1.2.0 changes five of the copies an assembly root
pins:scripts/validate-pins.py,scripts/validate-manifest.py,
scripts/bootstrap.py,scripts/repo_shape.pyand
scripts/validate-repository-naming.py. It changes four of the copies a
family holder pins:scripts/validate-family.py,scripts/bootstrap.py,
scripts/siblings.pyandscripts/repo_shape.py. NoMakefile,
.gitignore,.gitattributes,validate.ymlorAGENTS-shape.md
changes, nor the naming policy.
From a checkout at the v1.2.0 release commit,
update-shape.py check --root <path> --at 961640c681b54d9258e6c9d3eab8c6f46a26008breports
each asupstream-changedin a root pinned at v1.1.1 that has not
edited it, and
update-shape.py apply --root <path> --at 961640c681b54d9258e6c9d3eab8c6f46a26008b --yes --branch shape/update-<sha>
re-copies and re-pins them, on the person's yes, as AGENTS.md's
"Updating a project's shape" says. Until then the root's own gates and
make bootstrapare v1.1.1's. -
A holder that keeps its own
scripts/bootstrap.py. The new
siblings.pyimportsmember_make_environment(#1023) anduntraced
(#1075) from the holder'sbootstrap.py. From a pin at v1.1.1 or
earlier,bootstrap.pychanged upstream, so a kept copy readsboth: a
plainapplyrefusesupdate-conflict, andapply --accept-local scripts/bootstrap.pyrefusesupdate-accept-local-missing-name, both
before any write. #1082's gap, where a kept copy readsunchangedand is
not asked again, reaches only a holder pinned at a main commit between
the two releases whosebootstrap.pyis already v1.2.0's (from
ff0c72ebup to #1075's7e00834c). There a plainapplycopies the
newsiblings.py, andmake siblings,make parkandmake resume
then end inImportError(#1082, open). Merge the upstream's
bootstrap.pyinto the kept copy by hand, or take the upstream's bytes;
commit, and runapplyagain. -
A YAML layout the new reader refuses. A project keeps its old
repo_shape.pyuntilupdate-shape.py apply, and the old copy reads the
last of a key written twice without a word, so aproject.yamlthat
holds a key twice passes its ownvalidate-manifest.pyuntil then. At
applythe new copy refuses it,yaml-unparsablewith the line of the
second key, thenupdate-validators-red, then ROLLED BACK. Delete the
first of the two lines to keep the reading the project has now, and run
applyagain. A block scalar's indentation digit (|2,>-2,|2-)
ends the same way; with the digit deleted the value reads as before,
except where the header also holds-or+, or the body holds another
layout #1021 refuses (#974, #917; #1021, #1004). A holder'sfamily.yaml
is read by the same reader. -
The rendered READMEs, and a holder's
cdline. A project's or
holder'sREADME.mdis rendered once, when it is made, and
update-shape.py applyrewrites only a holder's closingShape:line.
So a holder made withfamily.py init --family X --name Ybefore v1.1.0,
whereXandYdiffer, keepscd Yin its README, while a clone of it
is the folderX/. Edit that line by hand, to the basename of
family.yaml'srepository:;applyleaves it as it is, by design
(#921's U097). The comments abovevisibility:inproject.yamland
family.yamlreach new roots only, for the same reason (#1102, #913). -
What a script sees. Runs that passed on v1.1.1 and fail now, among
them:adopt-project.py plan --project NUL,Con,com1orLpt9, exit 2
adopt-bad-project-name, where it wrote a plan and exited 0, and
checkof such a plan, exit 1 (#1041, #1014); the scaffold over such a
--project, exit 2scaffold-bad-project-name, where it made the
three repositories (#1010, #989);adopt-project.py planwith--org '',--elected-on ''or
--reference '', exit 2, where it exited 0; the scaffold and
family.py initwith a flag passed empty, such as--id ''or
--into '', exit 2 (#1016, #902; #972, #911; #1010, #999);adopt-project.py checkandexecuteover a hand-edited plan whose
id:,tracking_branch:oradopt_branch:reads as a number or a
boolean, or whosereference:is empty, refused where it was adopted
(#1016, #902);checkover a symlink target holding a NUL after its first byte or
over 4095 bytes, over a kept link into the clone's.gitto a name that
is not there when the shape is written, or over a pin's file reached
through a kept link, exit 1, where it saidplan ok(#965, #908; #1091,
#904);- a path this user cannot read, exit 2, by name, in
bump-leg.py,
update-shape.py, a project'sbootstrap.pyandvalidate-manifest.py,
family.py,validate-family.py, a holder'smake bootstrapand
make siblings, andadopt-project.py, where each ended in a
traceback, exit 1, on 3.12, or on 3.14 read it as absent and could exit
0; and the doctor over a root it cannot search, exit 3 (#934, #976,
#958, #955, #956, #951, #970, #1072; #975); - a holder's
make bootstrap,family bootstrap FAILED, where git's
update stopped with every member checked out, at a gitlink with no url
or a commit a fetch did not bring, or where a signal ended git, where it
saidfamily bootstrap ok(#889, #879; #954, #892; #1045, #996, #998;
#1076, #1056); family.py addover a commit that records the member's gitlink
anywhere butmembers/<Project>, exit 2member-gitlink-misplaced,
where it exited 0 (#957, #920);family.py bumpto a commit only a tag
of the member's remote reaches, where the mount's own refspecs bring
its tags underorigin/, exit 2, where it pinned it (#946, #905;
#1027, #1009);- a project's or a holder's YAML with a key written twice or a block
scalar's indentation digit,yaml-unparsable, once its copies are
re-pinned (#974, #917; #1021, #1004); - on Windows, a
gitthat cannot be started,git-not-runnable, exit 2,
where each gate ended in a traceback, exit 1 (#1028, #1001; #1062,
#1040); - the package's
openRepoShape --versionover a$OPENREPOSHAPE_REPO
holding one of the 41 code points the bash command refuses, exit 2,
where it printed it and exited 0, and over a$OPENREPOSHAPE_REFsuch
asa.lock/b, exit 2; a--doctor, scaffold or tool run over such a
ref, exit 2 before any clone (#947, #939, #941).
Runs that failed on v1.1.1 and pass now, among them:
checkover a
SHA-256 source, which endedgit-failed(#1031, #910);family.py bump
andremoveover a member on a branch that is not UTF-8, which ended in
a traceback (#1072, #918);family.py bumpwhere the member's remote
holds a branch and a tag of one name and the mount's own refspecs bring
its tags underorigin/(#946, #905; #1027, #1009);family.py addover
afamily.yamlwhosemembers:isnull,Null,NULLor~, which
was refusedfamily-members-unrecognised(#957, #920); under theC
locale, the bashopenRepoShape,setup.shandinstall.shover a
repository or a ref with a letter past ASCII (#986, #940); and
update-shape.py applyandbump-leg.pyover a pin or a manifest whose
first line opens with a byte-order mark (#1050, #933). -
A project's pin on the standard works as before. Follow
How to pin 1.0.0 with the v1.2.0 release
commit. A scaffold run through the package pins that commit and digest by
itself.
What changed since v1.1.1
v1.1.1 was tagged on 5017830 on 2026-10-18. Everything below landed on
main after that commit, by area; release/v1.1, which holds v1.1.1, takes
none of it. The list runs to the commit v1.2.0 is tagged on. Each bullet
names its pull request, then the issue it closed or took a part of.
adopt-project.py
planasks its flags first.planasks the refusals that a flag's
own value decides straight after--out, before it makes a work
directory, reads the source with git or runsgh repo clone:--project,
a passed--tracking-branch, the leg paths and their collisions, the
--pinrefusals, a passed--id,--org,--elected-on,--elected-by
and--reference. Each keeps its code and its words; only when it is
asked moved. Anorg/reposource is held to its own name before the
clone too:--project Otheragainsttestorg/Thingis
adopt-not-the-source, and an id derived from--projectthat is no id
(--project North_Wind) isadopt-bad-id, with noghcall, where each
was said after the clone. An unwritable--work-dirsays whyplan
cannot clone into it ("andplanclones testorg/Thing into
source-readonly/Thingthere"), and a local source's--work-diris no
longer probed for a write (#963, #903).- An empty flag is a value.
--org ''isunsafe-value, refused with
the other flags. It was counted as given in one place and as not given in
another, so a refusal named--org is '~x', which nobody typed, and
adopt-not-the-sourcesaid "(--orgnot given)".--elected-on ''is
election-date-malformedand--reference ''is
adopt-manifest-invalid, in the validator's words; both exited 0 and
recorded today's date or the default reference. A plan's own
reference: ""isplan-manifest-invalidincheck, andexecute
refuses it before any leg, where it wrote the default reference without
a word (#1016, #902). - A plan value read as a number or a boolean.
checkandexecute
refuse a hand-edited plan whoseid:,tracking_branch:or
adopt_branch:the plan's reader reads as a number or a boolean, such as
id: 007,tracking_branch: 007oradopt_branch: true. The code is
plan-bad-id,plan-bad-tracking-branchorplan-bad-adopt-branch, and
the words say what was read ("id:was read out of the plan as the
number 7, not as text");executerefuses it before any leg exists.
Before,id: 007was adopted andproject.yamlsaidid: "7",
tracking_branch: 007was adopted onto a source branch7, and
adopt_branch: truepushed the split to a branchTrue. The remedy is to
write the value between double quotes, as typed, or to runplanagain,
which writes every value so that it reads back as typed (#1016, #902). - Id and name refusals say the rule. A plan with no
id:whose
legs.assembly, lowercased, is no id isplan-bad-idsaying so ("id
(not set in the plan, so legs.assembly lowercased) is 'north_wind'"), and
its remedy says to changelegs.assembly, where it named anid:nobody
set. A passed--id, or a plan'sid:, that is no id is told what an id
may hold: lowercase letters, digits and hyphens, starting with a letter or
digit, in the scaffold's words. Where the source is adopted in place, the
naming refusal says the rule for the new name, one CamelCase token with no
hyphen, underscore, dot or space, before it says to rename the source
first;adopt-not-the-sourceandplan-not-the-sourcesay that the
source's own name is no remedy where the naming policy refuses it. A
refused name speaks before a passed--id's. AGENTS.md's adoption step
says that--orgdefaults to the organisation of anorg/reposource or
of a urlorigin, and that a pathorigin, or none, needs--org
(#1016, #902). - One first problem in a hand-edited plan.
checkandexecuteask a
plan'sorg:andid:before its leg paths and branch names, so both
name the same first problem where a plan has more than one. A plan with
id: "North Wind"andlegs.spec_path: "../spec"wasplan-bad-idin
checkandplan-bad-leg-pathinexecute; it isplan-bad-id, exit
2, in both, and an unsafeorg:beside the same path isunsafe-value
in both. Apins:entry that the pin reader names is that one finding: a
pin edited toproduct: "a<TAB>"was three findings,
plan-pin-malformed,plan-unwritable-valueandplan-manifest-invalid,
and isplan-pin-malformedalone. Apins:that is no list is said as
the reader says every other key ("pins:is the boolean true, not a list
of pins"), and a finding about a pin'sdigest_source:says that
executewrites the pin file from the commit and the digestplan
records, "and how it was read, and nothing else" (#1015, #901). - A name no Windows disk can make.
plan --project NUL, andCon,
com1andLpt9, in any case and with or without an extension, is
refused asadopt-bad-project-name, exit 2, with the other flag
refusals, before a work directory, git,ghor a plan file exists.
Before, it wrote a plan and exited 0.--projectnames the assembly root
and the folderexecuteclones it into, and no Windows disk can make a
file or a folder of a device name.checkfinds the same name in a
plan'slegs.assemblyas one FINDING, exit 1, and asksghnothing,
where it saidplan okafter twogh repo viewcalls;executerefuses
it. It is the one questionscaffold-project.pyandfamily.py initask
(#1010), and the first of the namesexecutecannot make. Where
--projectis the source's own name, the remedy gives the way out in
place, to rename the source on GitHub first. A name the naming policy
refuses as well, such asNUL.git, keeps the refusal it had:
naming-unclassifiedwith an--id, andadopt-bad-idfor the derived
id without one (#1041, #1014). - A source this user cannot reach. A
--source, or a plan's recorded
source.local_path, that this user cannot reach, one under a directory
it cannot search, issource-unreadable, exit 2, with thechmod u+rx
cure for the directory and every directory above it. It is never read as
absent and never sent togh: anorg/repospelling beside an
unreadable./orgis not cloned, and a recorded source is neither
plan-source-gonenor cloned again. On Python 3.12 these were a
PermissionErrortraceback; on 3.14, wherePath.exists()answers False
to anyOSError, a--sourcewassource-unresolvable, anorg/repo
spelling was cloned from GitHub, and a recorded source was
plan-source-goneor cloned again. A leg under--local-remote-dir
whosestatis refused waits for the leg question, which refuses the
folder by name,adopt-folder-cannot-be-made, where 3.12 ended in a
traceback.plan-root-symlinksays of a place it cannot read "cannot be
read (Permission denied), so whether anything is there cannot be told
until this user can search every directory above it (chmod u+rx)",
where it said "and nothing is there".--source's help in docs/cli.md
says so (#970, part of #430). - A source this user cannot enter. A
--sourcedirectory that is
itself at mode 000 issource-unreadabletoo, whereplanended in a
PermissionErrortraceback, exit 1. A recorded source in that state,
whichcheckcalled a clone "no longer whole (git finds no repository
there)" and cloned again with oneghcall, is the same refusal, before
anything is cloned or written. A plan written in UTF-8 and read under
LC_ALL=C PYTHONUTF8=0asks a recorded path that the locale cannot spell
as the plan's UTF-8 bytes, sochecksaysplan okwhere it said
plan-source-gone, as it does in UTF-8 mode; where those bytes name
nothing either, the refusal says so, naming the locale ("this locale's
file names, ascii, cannot spell it"). A one-byte locale such as Latin-1
still asks only the spelling as written (#1072, #918). checkasks git as the split will meet it. The throwaway
repositoriescheckmakes, to rehearse the split, to probe
.gitattributesand to ask how files are stored, are made in the work
directory (--work-dir, or the onecheckmakes inTMPDIR, never
TMPDIRitself) with the source's object format and none of this
machine's hooks, and each names its branch. A SHA-256 source, which ended
checkingit-failedat the rehearsal'sread-tree, isplan ok, and
so is a SHA-1 source underGIT_DEFAULT_HASH=sha256. A path of the
source commit that git refuses as a path (..,.Git/x,
notes/.git/x) is the new findingplan-path-git-refuses, which names
the paths and whichexecuterefuses before it makes either leg, where
checkended ingit-failed. A repository git cannot make in the work
directory is one finding,adopt-git-unasked, with git's reason, and
executerefuses it before any leg. A symlink probe whosegit init
fails takes git to check symlinks out as symlinks and prints one NOTE
with git's words, wherechecksaid "a git or gh command failed" (#1031,
#910).- A symlink target that git cannot make.
checknames a symlink whose
target holds a NUL after its first byte, or is longer than 4095 bytes, as
plan-bad-symlink-target, before any leg exists.executerefuses it as
it refuses anycheckfinding, andplanstill writes the plan and
notes it. Before,checksaidplan okandexecutestopped after both
legs existed, with a clone that exited 128, "File name too long", or with
adopt-lost. The finding says what git makes of the blob ("a link to
ci, not to the 5 bytes the blob holds"), or the bound; 4095 is Linux's,
and macOS and XFS take less (see the known limits). A root-kept
.github -> ci<NUL>xxis no longerplan-root-symlinksaying it leads to
ci\x00xx. A link atproject.yamlis one finding,plan-empty-symlink
orplan-bad-symlink-target, ending with where the link sits and what
ends the manifest block, whereplan-root-blocks-manifestnamed it again;
a link at a copy undershape/, or at a pin's file, is one finding too,
whereplan-root-blocks-shape-copyorplan-root-holds-pinnamed it
again with a remedy, to send it to a leg ordropit, that cannot help a
link no clone makes.checkreads the link targets in one
git cat-file --batch, not onegit cat-file blobfor each link, walk
and disk (#965, #908; #1070, part of #979). - A link into the clone's
.git.checknames what the clone's
.githolds as git keeps refs: it asks a throwaway repository in the work
directory whether this git uses the reftable format and writes reflogs. A
root-kept link to a name that is not there when the shape is written,
such asCLAUDE.md -> .git/refs/remotes/origin/mainor
-> .git/modules/nope, is refused asplan-root-symlinkbefore any leg
exists, saying that the shape's file would be written through the link
into.git, where no commit holds it, or would find no directory there
and stop after both legs. Before,checksaidplan okandexecute
saidadoption verified, with the shape'sCLAUDE.mdin a file of the
work clone's.git. A link to a name inside the object store, a leg's
store or the reftable tables is refused as onecheckcannot tell about;
a link to a name git does make there, such as.git/refs/heads/mainor
.git/logs/refs/heads/main, is adopted with the shape's copy beside it.
.git/refs/../HEADand.git/../AGENTS.md, refused as "not a directory
in the assembly root", are adopted the same way. Where git keeps refs as
a reftable, a link to.git/reftableis adopted with the copy beside it,
where it was refused, and a link to.git/packed-refsor.git/logs/HEAD
is refused, where it passed andexecutewrote the shape's file into
.gitor stopped after both legs (#1091, #904). - A pin's file through the links the root keeps.
checkasks a pin's
file, the shape's own and each declared pin's, where the root walk
reaches it through the links the plan keeps above it. A file
governance/openglass-pin.yamlundercontracts -> governanceis
plan-root-holds-pin, naming the link and saying to send it to a leg or
to rename the file in the source. Before,checksaidplan okand
executeadopted, and the first bootstrap exited 2 with
yaml-unparsable, or withneutral-pin-unreadablewhere the file is a
link to another file; for the shape's own pin,executestopped after
both legs withadopt-shape-pin-unreadable.
plan-root-blocks-shape-copysays what the materializer does at a link
or a file where a copy goes: it stops there,materialize-copy-blocked,
after both leg repositories were created and pushed, soexecuterefuses
before either exists (#1091, #904). - A
--work-dirthat runs round a symlink loop.plan,checkand
executerefused a--work-dirthat is a symlink round a loop as
"making it a directory failed (File exists)", which names a file in the
way, with the way out for a folder this user cannot write in. They now
say that--work-dir <dir>/self"cannot be made: it is a symbolic link
that leads round a loop", or, for a path that runs through one, that "the
way to it leads round a loop, at the symbolic link /loop", naming the
--work-diras given and not as resolved. The way out says to remove the
link, make it lead to a directory, or pass another--work-dir. A link
to a file is named as one ("a symbolic link that leads to , which
is not a directory"). A file at the--work-dirkeeps the system's
words. The code is stilladopt-work-dir-unwritable, exit 2, and nothing
is made (#1065, #978). - Names printed one way, and never raw. The split commit's message,
which is also the pull request's body, lists each moved, dropped and kept
path asplan's report shows it. A source that keeps a file named
caf<0xE9>.md, which is not UTF-8, and one namedcr<CR>had them
listed ascaf?.mdandcr, two names of other paths; they read
caf\udce9.mdandcr\r, each between backticks. A backtick is spelled
\x60and a double quote\", as the plan's own table spells them, so a
spelled name copied between the plan's double quotes is the entry it
names, where the old spelling of a backtick kept its backslash and named
another entry, and a bare"ended the quoting and left the plan
unreadable. A name that begins and ends with a backtick, or ends in a
space, whichgit commitstrips, is spelled too.planprints a path
policy'squestion:and rule id the same way, where an ESC in either
reached the terminal raw. The history scan'sgit-failedis one
printable line, where a source folder named with a line feed and an ESC
split it over two and sent the ESC raw.plan's table pads a name as
stdout prints it, so underLC_ALL=C PYTHONUTF8=0a source holding
café.md, printedcaf\xe9.md, no longer leaves itslegcolumn 3
columns to the right. A follow-up that names a file, the read-across one
among them, spells it as the listing does (#1030, #900). - The Makefile follow-up says where the block waits. Where the source
keeps aMakefileof its own at the root,plan's follow-up,check's
output and the split message said "the root Makefile wires
CONTRACTS_DIR". The shape's Makefile goes beside it as
shape/Makefile, with the block, and the root wires nothing, so
make contracts-dirat the root answered "No rule to make target". The
follow-up now says that the shape's Makefile wires it, that the block
waits inshape/Makefileuntil the two are merged, and that the run adds
none of it to the root's Makefile (#964, #906). It also names a kept
makefileorGNUmakefile. GNU make reads the first ofGNUmakefile,
makefileandMakefileit finds, so either comes beforeMakefile,
andmakethere exportsCONTRACTS_DIRonly once that file does, or
includes a Makefile that does. Where a disk ignores case, a kept
makefileisMakefile, and the follow-up says the shape's goes to
shape/Makefilethere. A source that keeps onlyMakefile, or none of
the three, gets the line it had. README says that the shape's Makefile
exportsCONTRACTS_DIR, and that it is the root's Makefile or, where the
source keeps aMakefileof its own,shape/Makefile, which holds the
export until a person merges the two (#1046, #977). - Four more printed sentences say what the run does (#964, #906):
- Where a follow-up says a file must read across the assembly root,
check'smount the legs at X/ and Y/and the split message's
mounted back here at X/ and Y/end ", so from the code leg the spec
leg is../spec/", and, wherecontracts/is there too, " and the spec
leg'scontracts/is../spec/contracts/". - A refusal that said
execute"would stop there" says where: for a
refused store or copy, "no later than committing the split, and sooner
where a step before it meets the same rule", and for a refused
checkout, "at its first clone of the source". - The sentence on a root
.gitattributeslink no longer says "a path
names no attribute". Git reads a link's text as one line of rules, so
it names an attribute where the text holds a space, a tab or a carriage
return; for a link to a path, no line-ending rule reaches the pinned
copies outside it. - Under
--local-remote-dir,WHAT WILL HAPPENprints
create <Project>-spec and <Project>-codewith no(private), since
executemakes bare repositories. Against GitHub the line is as it was.
- Where a follow-up says a file must read across the assembly root,
- An identical drop, and
answered_paths:read path by path.
execute's verification counts a path dropped from the plan whose bytes
the root also holds asdrop, not as the root's landing: where an
identical drop made the table readroot 2 of 4anddrop 0 of 4, it
readsroot 1 of 4anddrop 1 of 4.answered_paths:is looked up by
each path's own names, not by asking every entry of the plan: the same
answer, in 0.26 s where it took 3.65 s, for 120,000 files and 300
entries. README's verdict table and the handbook's name the exception in
theMISPLACED (n paths)row: a pathanswered_paths:answers into that
leg is not one (#1073, #914). - The Free-plan hint and an internal root. Where a root's plan is read
asfreeand the root is internal, the hint said "acme/Atlas is
private". It now says "acme/Atlas is internal, which this hint treats as
private", then the hint's own words. The scaffold andfamily.py init
print the same hint through the same function, so each says the same.
repo_shape.py's hint is not touched, so no project re-pins (#1038,
#994).
update-shape.py, bump-leg.py and a project's own scripts
- A path nobody may read is named, not taken for absent:
bump-leg.py.
From Python 3.14,Path.is_file(),is_dir(),exists()and
is_symlink()answer False to astatthe system refused, where up to
3.13 they raise. So a path this user could not search read as one that
was not there. The worst case was inbump-leg.py: on 3.14, with a
project'sscripts/at mode 444 or 000, it printedSKIPPEDfor both of
the project's validators, committed the bump and exited 0. Now an
unreadable validator is refused before anything is written, a dry run
included, asbump-leg-validator-unreadable, exit 2, naming each file
with the cure (chmod u+r, or the advice for a cause that is not a
permission, such as restoring the file from the project's history). A
validator that is not there is stillSKIPPED. A leg's checkout it
cannot search isbump-leg-submodule-unreadable, naming<path>/.git,
and a root, or a directory above it, that it cannot search is
bump-leg-manifest-unreadable, namingproject.yaml, exit 2. Those two
werebump-leg-uninitializedandbump-leg-root-not-a-projecton 3.14,
and aPermissionErrortraceback on 3.12 (#934, part of #430). - The same, for
update-shape.py. A path in the root or in the
upstream that this user cannot read isupdate-unreadable, named from
the root, exit 2, with the same cure, on every Python. Before, a
scripts/it could list and not search endedcheckin a
PermissionErrortraceback on 3.12, and on 3.14 read as five
copy-missing, withapplyrefusingupdate-conflict; acontracts/
at mode 000 read asupdate-pin-missing("A project without a shape pin
is adopted withadopt-project.py, not updated"); ashape/it could
not search made a pin row read as merged away, whichapplywould have
dropped; and an--upstreamunder a folder at 000 was "neither a path on
this machine nor anowner/reponame".applyreads every file it
judges by before its first write. A validator it cannot stat is refused
and the run rolled back, neverSKIPPED. A rollback lists an--add
file it could not remove, where on 3.14 it said the tree was exactly as
it had been. A read refused inpreparecloses the bare clone it made of
a pin'sowner/repo, where it left that clone behind (#976, part of
#430). - The same, for a project's own
bootstrap.pyand
validate-manifest.py. The assembly root's two pinned scripts name a
path this user cannot read, relative to the root, and never read it as
absent. Withscripts/at 000,bootstrap.pyon 3.14 saidSKIPPEDfor
all three validators and endedbootstrap ok, exit 0, and on 3.12 ended
in a traceback. It now refusesvalidator-unreadable: scripts/validate-repository-naming.py: cannot be read (Permission denied), exit 2, before any validator runs. Aproject.yamlbehind a
folder nobody may enter isyaml-unreadable, exit 2, before step (a),
where on 3.14 no leg was placed and the run saidbootstrap ok. A leg
whose.gitit cannot read is named and left as it is. A cherry-pick
stopped in a leg whosesequencer/it cannot read leaves the leg
detached, named, wherebootstrap.pyput the leg onmainon every
Python. Agovernance/it cannot enter heads step (c)
register, if one is there:, says why, and claims nothing from it, where
3.14 saidauthority is not wallet-carried in this org. In
validate-manifest.py, a declared referent's pin file behind such a
folder isyaml-unreadable, exit 2, where 3.14 gave the finding
naming-referent-missing. Both scripts are pinned copies, so a project
takes them withupdate-shape.py(#958, part of #430). bump-leg.py's fetch. The fetch of a leg prunes the branches its
remote deleted, so a dry run no longer says it would pin a commit no
branch reaches any more, and it moves no branch of the leg's own. It runs
git fetch -q --prune --no-prune-tags --refmap= <remote> <refspecs>.
Before, a leg configured+refs/heads/*:refs/heads/*had its local
mainmoved off an unpushed commit by a dry run, and with the leg on
mainthe fetch failed (refusing to fetch into branch ... checked out)
and the run answered from stale refs (#937, #898). The leg's own
remote.origin.fetchrefspecs are carried only where they write under
refs/remotes/origin/, and now it leaves out every one of them that can
bring a tag underorigin/, and one that gives another namespace the
branches' names. A branch and a tag of one name no longer fail the fetch,
and a tag no longer answers for a branch. It is the rulefamily.py bump
asks of a member, now one function,origin_fetch_refspecsin
scripts/shape_materialize.py, that both tools import: the rule was two
copies, andfamily.py's had been fixed alone (#1068, #1037).- The fetch for a branch's answer, in both tools.
family.py bumpand
bump-leg.pyleave out of the fetch every refspec of the repository's
own onto the branches' names, whatever its source, and every refspec
that puts a branch under a name not its own (xontorelease/x, every
branch into a folder), so a repository whose only refspec is
+refs/heads/release/*:refs/remotes/origin/*, with the branchesxand
release/xon its remote, no longer fails where its own
git fetch originworks. The tools' fetch diedCannot fetch both refs/heads/x and refs/heads/release/x to refs/remotes/origin/x, exit
128, and both runs of both tools answered from stale refs. A refspec that
keeps a branch under its own name, one branch or a folder of them, is
still carried. The cost is the one that tags onto the branches' names
already had: a ref the repository kept under another name is written back
by its own nextgit fetch origin, and a refspec without+is refused
there as non-fast-forward, exit 1, where the two commits are not in line
(#1087, #1066). - A commit only a tag reaches.
bump-leg-commit-local-onlyfor such a
commit saysa tag of <repository> reaches it, but no branch of <repository> does, and to push it to a branch of the leg, since a bump
pins only a commit a branch of the leg's remote reaches. It said
somebody had committed inside the leg's mount and never pushed it (#937,
#898). The check compares the commits the tags point to, not the tag
objects, so a lightweightv1in the leg against an annotatedv1on
the remote at the same commit, two annotatedv1made apart, and a tag
of a tag all reach it.family.py bumpasks the same check and gets the
same change (#988, #982). - A leg with no
origin. A leg whoseoriginwas renamed or removed
is told so. The fetch's note says it has no remote namedorigin, and
the refusal says to give the mount a remote namedorigin, for the
manifest'sowner/name, and run again. It told the person to fix the
network or the credential. A commit the leg's own object store lacks is
refused asbump-leg-commit-not-on-remote, naming the missing remote
(#937, #898). update-shape.py's kept-copy check and a handler that raises. The
check behindupdate-accept-local-missing-nameread an
except ImportError:handler as a fallback for what it binds, and never
asked whether the handler then raises. A kept copy that binds
NEW = Noneand then re-raises, beside arepo_shape.pywithoutNEW,
was not refused, and Python stops on theImportError. It is refused
now. A bareraisehands theImportErrorto the nexttryout, so a
nestedtrywhose outer handler binds the name still starts and is still
kept. Any otherraise, such asraise X, answers nothing, since
whether an outer handler catchesXdepends on its list: a copy that
raises an error of its own is refused where Python stops on it, and one
whose outerexcept ImportError:would catch it, which Python starts, is
refused too. A handler whoseraisesits under anif, or inside a
with, is still read as binding (#935, #897).- A byte-order mark, in the three line rewriters. Since v1.1.1 every
reader takes a leading byte-order mark off. The three line rewriters did
not, soupdate-shape.py applyrefused a pin whose first line is
commit:after a mark asupdate-pin-unrecognised("0commit
line(s)"), and a manifest whose first line isshape:after one as
update-manifest-no-shape, andbump-leg.pyrefused a leg's pin the
same way asbump-leg-pin-unrecognised. Each finds its key after any
number of marks on the first line and writes the marks back as they
were. A mark on a later line is still refused as before. A marked
shape:on line 1 beside a secondshape:header is refused by name,
update-manifest-unrecognised, as the reader refuses that file, where
only the second block was seen and rewritten (#1050, #933). make bootstrap's pasteable lines. The
git -C <leg> merge --ff-only <upstream>line printed when a leg is
moved back, and the--continueand--abortlines for an operation
stopped in a leg, give the leg's path and the upstream as one shell word
that a shell reads back as the same bytes. A name that prints as itself
is quoted as it was. Any other, a byte that is not UTF-8, a control
character, a U+2028, a bidirectional control or a name the stdout cannot
write, is"$(printf -- '<octal escapes>')", which dash, bash and zsh
read. An upstream oforigin/a, the byte 0xff andbwas printed as
'origin/a\udcffb', whichshreads as the characters it holds, so the
line named no ref (#944, #932). A word that begins with=is quoted, as
zsh expands a bare=wordto the path of a command. The choice between
the quoted word and theprintfword follows the bytes git is handed,
which are UTF-8 on Windows, and not the stdout's codec alone: under a
latin-1 stdout,origin/caféwas quoted and printed as the byte 0xe9,
where the ref holds 0xc3 0xa9. And theNOT INITIALIZEDcommand is
git submodule update --init -- <word>, the leg's path one word after a
--, with./in front of a path that begins with:so git does not
read it as pathspec magic. Before,my specwas two pathspecs,sp'eca
syntax error in every shell,-specan option and=specfailed in zsh.
A name that ends in a line feed or holds a NUL has no such word and keeps
the text it had (#973, #961).bootstrap.pyis a pinned copy.make bootstrapand a branch that differs from the tracking branch
only in case. Where a leg's HEAD is onMainand the tracking branch
ismain,bootstrap.pyleaves the leg where it is. No ref moves and
none is made. It says where the leg is, why it is left, and the way out.
On a disk that folds case, macOS or Windows,Mainandmainare one
ref, sobranch -f mainmoved the ref HEAD stood on and left the index
at the old commit (git statusreadD NEW-CONTRACT.mdand
M README.md), while the run said the leg was on a branch that keeps
its commits (#948, #896). The same holds where the tracking branch is
missing and anyrefs/heads/branch, packed or loose, folds to it, a leg
detached at the pin included. Only HEAD's own ref was asked before, and
makingmainover such a twin would shadow the twin's commits. A name
that differs in Unicode form is a twin too: names are compared in NFC
first, so the NFD spelling ofcaféis a twin of the NFC one, as APFS
reads the two as one file. Where HEAD is on the twin andmainis there,
behind the pin or at it, the way out isgit -C <leg> switch <branch>,
which moves no branch. Wheremainis missing, it is a rename,
git -C <leg> branch -m Main Main-kept, which keeps the commits and
moves HEAD with the branch. The new name is the first of<twin>-kept,
<twin>-kept-2and on that no branch of the leg folds to and none is
stored under, so the pasted line works and the next run makes the
tracking branch;Main-keptoverMain-kept/x, which git refuses, is
skipped (#984, #962; #1006, #1000).bootstrap.pyis a pinned copy.
scaffold, setup, family and the shared readers
- A key written twice is refused. The YAML reader in
scripts/repo_shape.py, which the tools and the validators in a project
and a holder read their YAML with, refuses the second of a key written
twice in one mapping, block or flow, by that line
(line 2: key 'a' is written twice), where it kept the last without a
word. A flow collection is named by the line it is on.aand"a"are
one key, and so are1and"1", because the reader reads every key as
text. The same key in two mappings or two sequence items, or a repeated
value, is not a key written twice and still reads. A block-scalar line
indented less than the block's first line is refused by its line, where
its first characters were cut; one that opens#ends the block as a
comment, as YAML reads it.update-shape.py's refusal of a manifest
holding twoshape:blocks now says the reader refuses a key written
twice, and no longer says it keeps the last (#974, #917). - A block scalar is read as YAML reads it, or refused by its line. A
block scalar's indentation digit (|2,>-2,|2-,|0,|10) is
refused by its header's line (line 1: a block scalar's indentation indicator is not supported: '|2'), where the digit was accepted and
ignored, sok: |2overaandbread'a\n'and lostb.+
keeps the empty lines at the end of the block, where it read as no
indicator. A line of spaces deeper than the block is text. A blank line
above the first line of text that holds more spaces than that line is
refused by its line, and so is a tab left of the block's column in a line
that holds nothing else. A folded block keeps the line breaks around a
line that opens with a space or a tab, and a block with no line of text
reads'', where it read'\n'. A seeded differential against PyYAML
6.0.3, over two runs of about 15,000 and 23,000 generated documents,
found no disagreement, where the reader before it disagreed on about
6,000 in each. No tracked YAML file and no tool writes any of these
layouts (#1021, #1004). "Install and upgrade" says what a project whose
YAML holds one meets atapply. - validate-pins.py,
--rootand gh's words.--rootis named as
shown_pathnames a path, as it is where every character is printable
and as a quotedreprotherwise, and gh's stderr is spelled as git's is,
on one line, each control written as its escape, so an ESC or a line
break in either no longer reaches the terminal or prints a forged line:
thenot-a-git-repoline, which every tool that callsfind_repo_root
prints, and thegh-unreadableskip, whose gh text is now one line (a
line break prints as\n). The repository in every gh detail,
gh-response-unreadableandgh-tree-truncatedamong them, is named the
same way. gh's stderr that is not UTF-8 is a skip, where it ended in a
UnicodeDecodeErrortraceback, exit 1. gh's stdout that is not UTF-8, or
JSON that is not a tree listing (a list,null, atreethat is a
number, a row with nomode), is a skip that names
gh-response-unreadable, where[]ended in anAttributeError
traceback, exit 1 (#952, #919; #985, #968). - What stands in a pin's place is named. Where a leg's pin, the shape
pin, a neutral-product pin,project.yamlor a shape copy is not a file,
validate-pins.pysays what stands there (is a directory, not a file,
a FIFO, a socket, a device, a symlink to nothing, a symlink loop, a
symlink to a directory) and gives a remedy that fits, to move it aside
and restore the file from the project's history, where it said the file
does not existunder the remedy for a missing one. Where something that
is not a directory stands wherecontractsshould be, the line says
which is a file, not a directory, or what else it is. A file that is
not there keeps its words (#952, #919; #985, #968). - A git directory this user cannot open is refused by name. A leg's
.gitgitfile at mode 000, or a leg's git directory (.git/modules/spec,
orspec/.gitwhere it is a directory) that this user cannot search, is
pin-submodule-unreadable, naming the path and the permission, with the
checkout remedy, wherevalidate-pins.pysaidpin-commit-unresolvable
over git's own failure, under a lockstep remedy that does not fit:
fatal: error opening ...: Permission deniedfor the gitfile,fatal: not a git repositoryfor.git/modules/spec, and an emptyexited 1
for aspec/.gitdirectory. A gitfile that ends in a CR and a line feed,
and one that names nothing, are read as before (#952, #919; #985, #968). - A chain source that is there and cannot answer. A project's
validate-manifest.pynames aSHAPE_PIN_SOURCE_<LINK>, or a checkout
beside the root, that is there and could not answer, with the reason
(the openXdox beside this root, <path>, could not answer: it holds no project.yaml; SHAPE_PIN_SOURCE_OPENXDOX would let it be read), and
offers only a source that is not there, where it offered the one that was
there. A manifest beside the root that is not a declaration reads
reached but could not be read as a declaration, where it said
not reachable here. ASHAPE_PIN_SOURCE_*variable that two products
of one run share answers for no chain, in the naming gate and in
validate-manifest.pyalike: a project with the pinsopenssand one
that spells it with a sharp s, which shareSHAPE_PIN_SOURCE_OPENSS,
failed its gate with the variable set (the declared chain openss -> openQuill is broken, exit 1); it now gives the same answer, exit 0, with
the variable set and unset, and offers it to no chain. A first chain link
that is a--pinis recorded in the pin's spelling, so
--pin openKdox@<sha> --referent-chain 'open<U+212A>dox,openInk'
recordsopenKdox -> openInk(#981, #909). - A
--link-source,--pin-sourceor--link-remedythat holds=.
A chain link or a declared pin whose name holds=can now be named. A
--link-sourcekey is the longest chain link before an=that leaves a
path after it, so the offer the naming gate prints for a link holding=
(--link-source open=y=<path>) verifies that link when typed as printed,
where the gate cut the key at the first=,open, which no chain
names, and leftopen=ydeclared-unverified under the same offer.
open=y=with no path is refusedlink-source-no-path, exit 2, as
openXdox=is.validate-pins.pyreads a--pin-sourcekey the same
way, and refusesopen=y=aspin-source-no-path; no scaffold writes a
pin that holds=, so that reaches a hand-editedproject.yamlalone,
and a pin's digest already stopped a wrong source from passing.
--link-remedyreads a link holding=whole, strips its words and
notes a remedy no holder used; a link whose owner, the text before its
last/, holds an=is no link, so
--link-remedy=openXdox=a/openYdox=bgivesopenXdoxthe words
a/openYdox=b, where it gaveopenYdoxthe wordsb. A set
SHAPE_PIN_SOURCE_<LINK>that the run's guard passes over, because two
products of the run share it, is named once by alink-source-collides
note, in the gate and invalidate-manifest.py(#949, #907; #1011, #990,
#991; #1044, #980; #1067, #1053). - setup-project.py's step (4) reads what step (5) does. Step (4) ran
the naming check from the standard's own folder, where a name on its
command line takes that folder as the project root, so a checkout of
openXdoxbeside the standard answered for a link before the person's
--pin-source. It now runs from an empty folder made inside a temporary
folder, so nothing stands beside it, and takes both away when the check
is done. A temporary folder it cannot make is a refusal that says to
pointTMPDIR(TEMPon Windows) at a folder you can write in, where it
would have been a traceback.--pin-source ./nowhere/, and a blank value,
name the same path in step (4) as in step (5). The Windows hand clone
that setup prints for finishing by hand passes-c core.autocrlf=false,
as setup's own clone does (#949, #907, #926; #1011, #991). - The scaffold's plan says what it is approved for. The plan has a
branchrow, the tracking branch each of the three is created on, and,
under--local-remote-dir, avisibilityrow (<visibility>, which project.yaml records; a bare repository on disk has none);
family.py init's plan gets the same two rows, and setup's step (5)
shows them. Where gh could not read the root's visibility (not where gh
does not find the root, which is the ordinary--reuse-empty-reporun),
a NOTE before the plan says, with no--visibility, that a leg this run
creates is created private and does not inherit the root's, and to pass
--visibilitybefore any leg exists, and, with one, that the run cannot
say whether the flag agrees with the root. A reused leg whose visibility
could not be read is named in a NOTE of its own, where it was counted as
the run's. Theremotesrow says that a private repository this token
cannot see is not found either, and thatgh repo createthen fails for
it. An internal root whose organisation's plan could not be read is told
that GitHub gives internal visibility only to an organisation an
enterprise account owns, so the plan is not what the Free-plan hint hangs
on.family.py initprints its members' credential NOTE for every
holder on GitHub, public or not, worded about the members: a member a
person adds that is, or has a leg that is, private or internal needs a
way for the holder to read it, where it went by the holder's own
visibility (#969, #913). - What
visibility:records. The comment abovevisibility:in
project.yamlsays the field is the visibility the run that made the
file asked for, in the order the scaffold takes it:--visibilitywhere
one was given, else the visibility GitHub gave, before the plan, of an
existing root the run reuses, elseprivate(a root whose read failed
too). The one infamily.yamlsays it isfamily.py init's
--visibility,privateunless the run was given another, not a
reading of any repository, and the holder's alone. Both say that a
repository the run reuses keeps the visibility it has, so the field can
differ from what GitHub says of it.project.yaml's said that the field
was all three repositories' visibility, andfamily.yaml's did not say
what it records. Nothing that is recorded changes. Neither file is a
pinned copy, so a project or a holder already made keeps the comment it
was made with (#1102, #913). - An empty value is read as given. In the scaffold and
family.py init, a flag passed empty was read as not given.--id ''is
nowscaffold-bad-idorfamily-bad-id
(--id '' must match ^[a-z0-9][a-z0-9-]*$);--elected-on ''is
election-date-malformed; and--name '',--reference ''and
--elected-by '', withinit's--name,--created-onand
--created-by, are refused asscaffold-manifest-invalidor
family-manifest-invalidin the validator's own words. With no identity
at all,--elected-by ''is no longer toldno --elected-by. The folder
flags--work-dirand--local-remote-dir, andinit's--into,
passed empty, were read as., the directory the run stood in, so
init --into ''from the root of a checkout planned the holder inside it.
Each is now refused by name,work-dir-no-path,
local-remote-dir-no-pathandinto-no-path, before anything is made,
dry run alike, and the five folder flags'--helpsay what an empty
value and.mean (#972, #911; #1010, #999). - A passed
--idand a name are told in the order a person can mend
them. A passed--idthat is no id is told what an id may hold,
lowercase letters, digits and hyphens starting with a letter or digit,
but only after the name passes: a name no assembly root or holder can
have (naming-unclassified,naming-role-mismatch, a device name,
GitHub's 100 characters) speaks first, so--project Atlas-spec --id foo_baris refusednaming-role-mismatch, where it was told to mend its
--idand the retyped id then met that refusal, which no--idmends.
--id=-northand--id 'north wind'are*-bad-id, where they were
unsafe-value.--family open_glassis no longer sent toopenGlass,
which is an assembly root's form and no holder's. An empty target or
holder folder that this user cannot list is told the permission to give,
and that the run cannot say whether it holds a tree, where it was told
There is no --force: re-running over a live tree is not a scaffold
(#972, #911, #930). - A name no Windows disk can make a folder of. The scaffold refuses
--project NUL,Conand the other Windows device names (CON,PRN,
AUX,NUL,COM1toCOM9,LPT1toLPT9, in any case, with or
without an extension) asscaffold-bad-project-name, by the one question
initasks of--family, before a passed id's alphabet and before any
ghcall, against GitHub and under--local-remote-diralike, dry run
included. It took them, and made the three repositories and their
folders (#1010, #989). - The scaffold asks nothing at the terminal on a push or a seed fetch.
Git's own terminal prompt is now off for each leg's push, the root's push
and the--reuse-empty-reposeed fetch, as it was for the probe alone,
so a first scaffold and a re-run fail in git's words
(terminal prompts disabled) instead of stopping atUsername for. A
push git has no login for names the fix,gh auth setup-git. A leg git
reaches over ssh, because aurl.<base>.insteadOfrule sends
https://github.com/there, is refused with ssh's reason
(git@github.com: Permission denied (publickey).), where the refusal gave
git's genericCould not read from remote repository.; the probe runs
underLC_ALL=C, so the line is found in any locale. A push refused for
its stored login (Authentication failed), or for its ssh key
(Permission denied (publickey)), says how to replace it, then the
--reuse-empty-repore-run, before the ruleset hint; for the login it
says the re-run is refused too until git has one the host takes; for the
key it names thessh -Tcheck, and does not offergh auth setup-git,
which changes nothing over ssh.
Each push now runs underLC_ALL=C, because git translates
Authentication failed, so git's own lines of a refused push print in
English. A push refused for any other reason, a ruleset's above all,
prints what it printed. Setup runs this scaffold, so the change reaches
it (#971, #915; #1012, #993). - Where a
--reuse-empty-repore-run can still wait. Only git's own
terminal prompt is off, and the run leaves the operator's own ssh,
credential-helper and askpass setup as it is. The--reuse-empty-repo
help,docs/cli.md, the README, the handbook and each remedy that sends
a person into that re-run, but the ruleset hint (#1108, in the known
limits), now say that it can still wait at an ssh passphrase or host-key
prompt, at a credential helper that prompts, or atGIT_ASKPASS,
SSH_ASKPASSorcore.askPass, and how to stop that: load the key into
an ssh agent, use a helper that answers without prompting, or set
GIT_SSH_COMMAND='ssh -o BatchMode=yes'(with anythingcore.sshCommand
adds) for the run. Forcing it would replace the operator's own ssh
command, a key given by-i, a proxy or a port. Under
--local-remote-dirno prompt can follow, and no remedy says so. A push
refused for another reason than a login keeps the ruleset hint alone
(#1101, #915). - A seed found only on another branch, and
--dry-run's words. A
--reuse-empty-repore-run that takes a seed found only on a branch
other than the tracking branch, such asseed/scaffold, the branch the
old ruleset hint made, prints a NOTE that says the run creates the
tracking branch at it and does not touch that branch, so it remains; on
GitHub the NOTE adds that it may be the leg's default branch, and under
--local-remote-dirit says nothing of GitHub. A seed on the tracking
branch as well prints none. A branch name the remote gives is spelled as
shown_pathspells a path.--dry-run's help says that its answer about
each folder isos.access's, which reads the mode bits and on Windows
says yes for every folder, so a quota, a Windows ACL or a read-only drive
on Windows can pass the dry run and be refused by the real run, and that
the run leaves nothing that stays: it makes and removes temporary
folders, creates no remote and pushes nothing, where it said
make no folder.docs/cli.mdis regenerated (#1052, #921, #923). - One spelling of a
neutral_product_pins:entry. One function in
shape_materialize.pywrites the entry for the scaffold and for
adopt-project.py, so the same--pinwrites the same line from either.
A--pinof0x1F,1_000,1e3,3dGlassor-xis double-quoted
from the scaffold as from adopt; the scaffold wrote them bare, and PyYAML
read0x1Fas 31 and1_000as 1000. Of the names of one or two
characters, 605 were spelled differently by the two tools, and none are.
Adopt's output is the same byte for byte (#995, #924). Then a name that
YAML 1.1 or 1.2 reads as a bool, a null or a float (y,n,yes,
no,on,off,true,false,null,.infand.nan, each in
the three cases YAML reads (yes,Yes,YES;.nan,.NaN,
.NAN), soyEsstays bare, and a point before digits such as.5) is
double-quoted by both tools too, so a stock YAML reader reads each back
as the text. Of the 278,915 names of up to three characters that--pin
admits, 132 were misread by PyYAML and 132, not the same names, by
ruamel.yaml's YAML 1.2 reader, and none are.openGlassis written as it
was (#1090, #912). - A tag whose commit the clone lacks. A clone that holds an annotated
tag but not its commit cannot answer for it, and now says so as it does
for an id it does not hold:adopt-project.py planand the scaffold,
--dry-runalike, refusepin-source-unreadablewith the remedy to
point--pin-sourceat a clone that has the pinned commit,git fetch --allfirst, where they saidpin-not-a-commit,is an annotated tag that names no commit;adopt-project.py checksays
plan-pin-source-unreadable; andbump-leg.pysays
bump-leg-commit-not-on-remotewhere it said
bump-leg-target-not-a-commit. A tag of a tree or a blob, which the
clone can peel, is still refused by name. Wherecheckcannot ask a
recorded commit what it names, because the recorded clone is gone or
partial, orgh apiread the digest, its NOTE says that the digest was
not rechecked and that thecommit:was not asked whether it names a
commit, where it spoke of the digest alone;planasks where it reads
the digest (#1090, #912). - A template that is no mapping. Where the template's own YAML reader
makes no mapping of the manifest a run would write,family.py init, the
scaffold andadopt-project.py checksay it is a defect in the copy of
the standard that was run, not in the person's values, name the template
(templates/assembly-root/project.yamlor
templates/family-root/family.yaml) and say to restore it from the
standard's repository or reinstall the standard, where they sent a person
to choose a value the validator accepts. The codes are kept, andcheck
no longer adds its sentence about correcting the plan's values to it
(#1064, #1059). - setup-project.py's credential-helper check. A configured helper is
not a stored login. Where every helper configured for github.com is
store, and none of the filesstorereads holds a login for
https://github.com, the preflight prints its[??], which says that
git's credential helper for github.com isstore, that no login for
github.com is stored in the files it names, and that the first push over
HTTPS will fail, and makes thegh auth setup-gitoffer, as it does for
no helper, where the check said thatstoreand every other helper
answers git's question for github.com. With git's prompt off for the
scaffold's pushes, such a push fails after the three repositories exist.
The files are the ones git'sstorereads,~/.git-credentialsthen
$XDG_CONFIG_HOME/git/credentials(or~/.config/git/credentials), or
the one--filenames, with~as git expands it; withHOMEnot set
the sentence says git givesstoreno file to read, where it named files
git never reads. Both warnings stay silent where aninsteadOfrule
sends github.com over ssh, which git's ownls-remote --get-urlsays
without asking the network, or where a helper under another spelling of
the key, such ascredential.https://github.com/.helper, answers.
git credential fill, which runs every helper, is never run (#1008,
#992; #1022, #1013). - setup-project.py's self-bootstrap and
$OPENREPOSHAPE_REPO. A
$OPENREPOSHAPE_REPOholding U+202E, or another character the launcher
refuses, is refused by code point and in the launcher's words, exit 2,
before the run prints the value or hands it togit clone, where it
printed the raw bytes in its clone line and failed in git, exit 128. The
table is the launcher's own, entry for entry, and a test holds the two
together. The onegit rev-parseprobe that every run makes still comes
before the refusal. A value the launcher lets by, such as U+00E9, U+00A0,
or U+200C and U+200D, goes on as it did (#1043, #953). - family.py init, and the words it refuses in. Where the holder's own
YAML reader refuses the manifestinitwould write,
family-manifest-invalidnow names the value on the line it refuses,
such as--org is '@x', and gives the reader's remedy. It named no
value, and called the reader's words the validator's (#960, #929).
initnow asks a template's YAML reader and validator through the one
implementationshape_materialize.pyholds, which the scaffold and
adopt-project.pyalready used, so the family's guards against a broken
validator hold for the project's manifest too: a validator that exits
while it loads or when it is asked, or answers with a finding that is not
text or with no list, is a named refusal, where the scaffold ended with
the validator's status and printed nothing, or in a traceback. Only a
broken copy of the standard reaches it, and no code or word the person
sees on a sound one changes (#1057, #925).init --dry-run's help and
docstring said it makes no folder, remote or repository. They now say
its answer about each folder isos.access's, which reads the mode bits,
and that it leaves nothing that stays, as the scaffold's help has since
#1052 (#1063, #923). - family.py add checks its own commit. A commit that records the
member's gitlink anywhere butmembers/<Project>is taken back and
refused,member-gitlink-misplaced, exit 2, naming the path git chose.
addexited 0 and left the branch on a commit whose.gitmodulesand
family.yamlnamemembers/<Project>, where a fresh clone would leave
the member empty. The take-back moves the branch to whereaddfound
it, only where the commit is one commit on that and the branch still
names it, and the mount is undone as for every refusal after it; where a
hook's commit is on top, nothing is taken back, and the refusal says so.
members: null,Null,NULLor~takes the first member as
members:does, where it was refusedfamily-members-unrecognised
(#957, #920).Family.rewritten's refusal of twomembers:lines is
tested again, and its docstring says that no verb reaches it since the
reader refuses a doubled key; no behaviour changes (#1039, #1029). - family.py bump's fetch.
bump's fetch of the member'soriginno
longer moves the mount's own branches: a mount with
+refs/heads/*:refs/heads/*had a localfeat, and the commit on it
that was never pushed, moved bybump --dry-run. It works where the
mount maps tags intoorigin/, where a branch and a tag of one name made
it fail withnote: could not fetch <member> from its originand a
member-to-local-onlythat said to fix the network. A commit only a tag
reaches is refused in the branches' words, where it was pinned. It reads
the mount's refspecs withgit config --list -z, so a value holding a
newline is one value, and asksgit remote get-url originfirst, so a
mount with nooriginis not fetched from a path of that name (#946,
#905). Every refspec of the mount's whose source can name a tag is left
out too, a pattern such asrefs/*, a short name or an object id among
them, so a mirror in a folder,+refs/*:refs/remotes/origin/all/*, no
longer fails the fetch on a branch and a tag of one name, or pins a
commit only a tag reaches (#1027, #1009).bump-leg.pyasks the same
rule (#1068, #1087, above). - family.py bump and remove, a member on a branch that is not UTF-8.
For a member on a branch such ascaf<0xE9>, both ended in a
UnicodeDecodeErrortraceback, exit 1, before any write. They run now.
Where apre-commithook refuses, they exit 2 with its refusal, and the
rollback puts the member back on that branch, at its commit, with the
holder as it was. A trailing U+00A0 is no longer stripped off a member's
branch name, where it named another branch (#1072, #918). A rollback's
by-hand checkout line for such a member spells the branch as aprintf
word of octal escapes,"$(printf 'caf\351')", where it printed
Python's'caf\udce9', which names no branch, so the line typed in a
POSIX shell puts the member back on that branch (#1084, #1079). - Remedies that name
family.py bumprun as printed. The remedies of
member-already-presentandmember-duplicate-idinfamily.py, and of
bump-leg-assembly-is-not-a-leg,bump-leg-root-not-a-projectand
bump-leg-wrong-kindinbump-leg.py, name--family-root, where they
namedfamily.py bump --member ... --to ...and the line failed with
the following arguments are required: --family-root.family.py
quotes the holder for the host's shell;bump-leg.pyhas no holder to
name, and prints<holder>(#1002, #927). - validate-family.py's remedies, typed as printed and in order. The
lines the gate prints for the states it names put a.gitmodulesthat is
gone from the work tree back once, and never over another line's write;
stage it before a gitlink'sgit rmand after a section is written back;
and name a checkout thatgit mvmoved, to delete by hand before the
update. So no remedy undoes another or exits 128 when typed (#889, #879).
A hand mount'sgit rmline isgit rm --cachedwhere a plaingit rm
refuses a staged gitlink, dies on a clone no section names or refuses
what is no checkout, with what it leaves on disk said, and the
.gitmodulesis put back before thegit rmthat takes a section out
(#1025, #936).gitmodules-path-unnamedreads the file git reads where
the work tree's.gitmodulesis gone, the index's and then HEAD's, and
its lines put that file back first, so a section that
git submodule addstaged is no longer committed past a passing gate
(#1069, #1036). - Paths this user cannot read, in the family. On Python 3.14 a path
this user cannot read was read as absent, and on 3.12 it ended the run in
a traceback. Each family tool now refuses it by name, exit 2, on every
Python.family.pyrefuses a holder, a member or its object store, as
family-root-unreadable,siblings-script-unreadableor
member-unreadable, where it saidfamily-root-missing,
member-uninitializedor "not on disk", and no rollback removes past one
(#955, part of #430). The holder'ssiblings.pyandbootstrap.py
refuse a member's path asmember-unreadable, so 3.14 no longer clones a
member on its remote's default branch, fetches into a clone it never
identified, or sayssiblings okandfamily bootstrap okover members
it skipped as absent (#951, part of #430).validate-family.pyrefuses a
member's checkout, a shape copy, the shape pin orfamily.yamlas
<path>: cannot be read (<reason>), with its cure, and a member's
project.yamlas the findingmember-manifest-unreadable, exit 1, so
there is no 3.12 traceback, and no 3.14member-uninitialized,pinned but absentordoes not exist, or line that updates a checkout that is
there (#956, #430). git-not-runnable, only where git could not be started.
validate-family.pycounts anOSErrorthat namesgit, or a
FileNotFoundErrororPermissionErrorthat names no file, as
validate-pins.pydoes, and only around the git calls. A member's
checkout that git could not enter, or a member check's own step, is no
longer told to put git on PATH (#983, #928). On Windows, agitthat
cannot be executed,[WinError 193], anOSErrorthat names no file, is
git-not-runnable, exit 2, with the put-git-on-PATH remedy, in all four
copies of the rule,validate-family.py,validate-pins.pyand both
bootstrap.py, where each ended in a traceback, exit 1; a test holds the
four to one answer and one text (#1028, #1001). So is agitWindows
cannot start for another reason that names no file:winerror216,
1260, 4551, 11 and 188 to 202 (#1062, #1040).- A holder's
make bootstrapfails, by name, where git's update
stopped. Where git's init in the holder stopped at a gitlink the holder
records with no url, or at an unmerged.gitmodules, with every member
checked out, the run fails, where it saidfamily bootstrap okbeside
git's exit 128 (#889, #879). So it does for a gitlink a member's own
index gives no url, and for a member's commit that a fetch reaching its
remote did not bring: each fails the run, naming where git stopped and
the cure, with no credential advice. They are told from a missing
credential by git's trace events and its exit code, never by its words
(#954, #892). A git that git's update ran dying of a signal, exit 128
plus the signal's number (129 to 128 plus the system's highest signal
number, 192 on Linux), or git itself killed, fails the run by name,
UPDATE KILLED, and not for want of a credential, with no credential
advice, where the run endedfamily bootstrap ok; a higher code, such as
git's 255, reads as before (#1045, #998). The signal is told wherever the
update met it, read from git's trace, and not only where its code is the
update's last, where no token is in use (see the known limits); where
git's update exited 0, nothing is said (#1076, #1056). - A token reaches no trace. With
SHAPE_LEGS_TOKENor
SHAPE_LEGS_APP_TOKENin use, git's update inmake bootstrap, and the
clone and the fetch that carry the token inmake siblingsand
family.py siblings, run with everyGIT_TRACE*variable removed and
trace2's three targets set to0, so a trace the caller asks git for no
longer holds the token. Where git's update exits 128 with a token in use
and a member's store lacks its recorded commit,make bootstrapredoes
the two fetches git's update makes there, in that same environment, and
fails by name where the commit is still absent,UPDATE STOPPED, where
it printed the advice to set a token that was already set, and
family bootstrap ok(#1045, #996; #1075, #1055). - A member's make under
make -jN. Undermake -j2 bootstrap,
validate,parkorresume, a member's make no longer warns
jobserver unavailable: using -j1. Add '+' to parent make rule.of a
rule in a Makefile the person does not own. The holder'sbootstrap.py
andsiblings.pytake the jobserver, and the-jthat comes with it,
out of theMAKEFLAGSa member's make inherits, so each member's make
runs its jobs one at a time and says nothing (#950, #888). Under GNU make
4.4's named-pipe jobserver, a member's make that says it is 4.4 or later
getsMAKEFLAGSas it is and runs its jobs together; every other make
still gets no jobserver and no-j, andsiblings.pytakes its one
member_make_environmentfrombootstrap.py(#1023, #987). - A member's own exit code, in the FAILED line. A holder's
make bootstrapnames the code of the member's innermost command. An
(ignored)line from make, for any code or signal, now resets the depth
fully, so a member's ownexit 2after a-make one or two levels
below, or after|| trueand a-command that failed or that a signal
killed, readsthe member's command exited 2, where it readexited 1
(#891, #890).
shape-doctor.py
- A path this user cannot reach is named, on every Python. A root this
user cannot search, or whose parent it cannot search, is refused before
any row:REFUSED shape-doctor-root-unreadable: --root <root>: cannot be read (Permission denied), exit 3, withchmod u+rxas the cure. It
ended in a traceback on 3.12, and on 3.14 saidNOT A SHAPE ROOTfor the
root andshape-doctor-root-missingfor a parent. A root it can search
and not list, mode 111, is not refused. A project's own gate that it
cannot reach, withscripts/at 444, is named on themanifest,pins
andfamilyrows and nothing runs in its place; on 3.14 the standard's
copy of the validator ran instead and passed. A leg's mount or a member's
that it cannot list or read is named by thelegsormembersrow:
spec/at 000 asspec: cannot be listed (Permission denied), so no HEAD in it is compared with the pin, andspec/at 444 asspec/.git: cannot be read (Permission denied), with the same ending. On 3.14 the one at
444 read aspopulated, but with no .git of its own, and a holder's
members/at 000 asmembers/IRRS: not populated; on 3.12 each was
could not be run. Theleg shape filesandplacementrows are notes
that say so, and a leg file it cannot read iscannot be read, never
absentwith acpNEXT. Over such a mount the verdict isINVALID,
notDRIFTED: no leg is off its pin because its pin could not be read,
and a row that could not ask is neverDRIFTED. A mount this user can
read whose git directory it cannot, such as.git/modules/specat 000,
still readsDRIFTED(see the known limits). A refused read out of any
other row is that row's FINDING in the gate's words, where it saidcould not be run.shape_advisory.pysaysit could not be read (PermissionError)of a record whosestatis refused, on every Python.
Withupdate-shape.pynaming the same paths, ascripts/nobody may
search isINVALID (manifest, pins)on 3.14, as on 3.12, where on 3.14
the doctor saidDRIFTED (5 copy-missing)(#975, #976, part of #430). - A
project.yamllinked into a folder this user cannot search. The
doctor askedis_file()of the root'sproject.yamlandfamily.yaml,
so such a link ended the doctor in aPermissionErrortraceback on
Python 3.12 and, under a shim that answers as 3.14 does, read
NOT A SHAPE ROOT, exit 2, withadopt-project.py planas its NEXT,
over a project. It is now themanifestrow's
REFUSED yaml-unreadable: <path>: cannot be read (Permission denied),
INVALID, exit 1, on every Python, as aproject.yamlat mode 000
already was (#1111, #430). - A
.gitmodulesthat holds no section. The doctor asked nothing only
of a.gitmodulesof no byte (#655). Now "empty" means no section: no
byte, or only what git's config reader passes over, a whole UTF-8
byte-order mark at the start, blank lines and comments opened by#or
;. In a leg that holds no submodule, such a file asks nothing, and the
placement row says nothing needs one and thatgit rm -f .gitmodulesin
its leg removes it. Before, a file of a newline, a CR LF pair, a mark,
spaces or a comment was asked on every run, no record could answer it,
and nothing said to delete it. A section, even one with no key, is still
asked, and so are part of a mark, a vertical tab and a form feed, which
git refuses withbad config line, exit 128 (#1073, #914). - A gitlink no section registers. A recursive clone of a leg with a
gitlink that no.gitmodulessection registers exits 0 and leaves the
gitlink's directory empty, andgit submodule statusthere exits 128
withno submodule mapping found in .gitmodules. The doctor said
nothing. Now the placement row names each such submodule, with its own
way out, in three states: beside a.gitmodulesof its leg that holds no
section (#1073), beside one whose sections register other paths, and in a
leg whose index tracks no.gitmodules, where the copy git reads
instead, an untracked file on this disk or else HEAD's, does not register
it either (#1092). The way out is a section for it with itspathand
url, or, once the.gitmodulesis staged (git add .gitmodules),
git rm --cached <its path in the leg>in its leg; where the index
tracks none,git rm --cachedalone, sincegit add .gitmodulesfails
with no file. A gitlink that git counts as registered is not named. It
is said, never a finding: the row stays thenoteorokit was. A
leg's--jsonentry carriesunregistered_submodules, and in the last
two statesunregistered_beside, which isother_sectionsor
no_gitmodules(#1073, #914; #1092, #1080). - A file's row with a
/. Ananswered_paths:row that names a file
with the/only a directory's row ends with answers nothing. The leg's
entry names it under the new--jsonkeyslashed, and the placement
row says it answers nothing. It is never a finding, and never read as the
file's row (#1073, #914).
Install, CI and the docs
- The pipx command refuses what the bash command refuses, wherever it
prints a value or hands one to git. The
openRepoShapethatpipx install openreposhapeplaces printed a
$OPENREPOSHAPE_REPOholding U+202E or U+200B as typed and exited 0 on
--version, and it refused a ref holding either with the value quoted.
It now refuses a repository, a ref or a--shape-refholding any of the
41 code points the bash command refuses (#837), exit 2, by code point, in
the bash command's words and without echoing the value, before
--versionanswers or anygitcall names it. So do--doctor, a
scaffold run and the package's four tool commands,scaffold-project,
adopt-project,update-shapeandshape-doctor: a value with such a
character makes nogitcall. The repository is asked first, as the
bash command asks it. The package holds a ref to git's rule part by part,
too:a.lock/b,a/.b,a//b,a/b/anda.are refused before the
standard is cloned, in the bash command's sentence, where the package
took them, cloned the standard in full and ended in git's own refusal at
checkout(#947, #939, #941). A run that never reads the values, such
as--doctorbeside a git checkout or--preflight, refuses neither. - A letter past ASCII under the
Clocale. Under theCor POSIX
locale, a$OPENREPOSHAPE_REPOURL or path, or a ref, with a letter past
ASCII, such as/srv/café/openRepoShape.git, was refused as "a control
character", exit 2, by the bashopenRepoShape, bysetup.sh's two
guards and byinstall.sh's repository, version and option checks,
which gave the same words underC. v1.1.0's launcher ran such a value,
a UTF-8 locale such asC.UTF-8takes it, and v1.1.1 named the refusal
a known limit. All seven checks now ask one rule, the byte rule that
holds$OPENREPOSHAPE_BIN_DIR, underLC_ALL=Cin every locale: every
control character, a C1 control included, is refused as before, and a
letter past ASCII is not one. So--version, a scaffold run,--install
from a file,--doctorand--preflighttake such a mirror in a shell
atC(cron, a CI step, a minimal container). No refusal's words
change, andsetup.shkeeps refusing U+2028 and U+2029, by their bytes.
A byte that is not UTF-8, such as a lone0x9Bor an overlong form of a
control, now passes in every locale, as it does for the directory (#867,
#858); a terminal that reads UTF-8 draws it as a character it cannot
read.install.shtakes the tag in the redirect's location only when it
is printable ASCII, so any other tag names no release andghis asked
next (#986, #940). --doctorchecks the ref before it clones. The bash
openRepoShape --doctorcloned the standard and then checked
$OPENREPOSHAPE_REF, so a ref it refuses (a.lock/b,a//b, one over
255 characters) cost a whole clone. Where the clone failed, offline or
with a mirror that is down, the run ended in "could not clone" and never
named the ref. A refused ref now makes no clone and no temporary folder,
and is refused in its own words even where$TMPDIRis not there. A ref
the doctor takes still clones once and checks out once. The refusal's
words are unchanged.tests.yml's comment on the Windows job's
full-depth checkout now says no test in its three files has cloned the
checkout since, and the setting stays (#967, #942).- The install check fails where the doctor's children cannot start
under-S.
shape-doctor.pystarts each child aspython -S, and the release's
install check is the one run of the real doctor on macOS and Windows. It
asserted the doctor's exit over an empty repository and where the
machine row foundgit-filter-repo, so a child that cannot start under
-Spassed. The step over the empty repository now prints the machine
row's preflight exit and every line it reported. It fails unless the
report holds the closing line of the exit the preflight chose,
[ok] this machine is ready.for 0 and
[!!] this machine is not ready yet (see above).for 1, and the
[ok] git,[ok] pythonand[ok] bootstrap runs aslines every runner
passes. A new step hasscaffold-project --local-remote-dirmake a root
on the runner's disk, with no network, noghand no token, and runs the
doctor over it: thenaming,manifestandpinsrows must each have
run a child, and none may read "crashed before reaching a verdict".
Whether the root is compliant is not asked. A dispatch of the workflow on
a branch, which uploads nothing, passed on all four legs, and a copy
whosesetup-project.pyimports a module onlysitereaches failed on
all four (#945, #894). - Runners.
ubuntu-latestnow gives out Ubuntu 26.04, which has no
Python 3.9 for setup-python.tests.yml'spackagejob, which runs on
3.9, failed there with "The version '3.9' with architecture 'x64' was not
found for Ubuntu 26.04", and the release's install-check row for 3.9
would have failed the same way. Both now run onubuntu-24.04, and that
row's job is namedinstall check (ubuntu-24.04, Python 3.9). So does
thetestsjob: on 26.04 the full suite ran out of disk under pytest's
basetemp in/tmp(Disk quota exceeded, 2436 failed and 5579 errors),
and it stays on 24.04 until the suite runs on 26.04 (#1096, open). The
other jobs keep their runners. A test holds everysetup-pythonstep of
both workflows to what 26.04 has: a step onubuntu-latestmay ask for
3.xor3.12only, and a Python 3.9 step must be onubuntu-24.04.
requires-pythonstays>=3.9(#1095, #1094). - The release notes are held to the one-line install.
tests/test_repo_hygiene.pyholds the one-line install byte for byte in
the notes of every release since v1.0.1. It named v1.0.1's notes alone,
so a mistyped copy in v1.1.0's passed. It now finds the notes in
docs/release-notes/when the tests are collected, so each release's
notes are held the day they are added. v1.0.0's, which predate the line,
are the one exception, and a.mdin that directory that is no
release's notes fails until it is named (#887, #885). And every line of
the notes that runs the install, one that names bothcurland the
release'sinstall.shURL, must hold the one line, with nothing after it
or the pinned form,-s -- v<major>.<minor>.<patch>. A| bshbeside a
correct line, or a| bash -s -- --yes, fails by file and line. The
failure's message offers the exception set only to a file that can use
it (#959, #931). - Path guards, held by a test. Eight pull requests above moved each
shipped tool's guards onto a local_is_a, which treats ENOENT, ENOTDIR,
EBADF and ELOOP (and Windows' 21, 123 and 1921) as absent and raises
every other error, so the caller refuses the path by name (#934, #976,
#975, #958, #956, #951, #955, #970, for #430).
tests/test_path_guard_hygiene.pynow holds that. A bare guard in any
shipped tool fails by file and line unless its_is_aor a reasoned
allowlist covers it; a second test holds every tool's_is_ato that one
set of absent errors and no other; and an allowlist entry that no longer
names a call fails as stale. It found one more site, the doctor's
project.yaml(above). Eleven sites that read such a link as absent are
listed as open, for #1110 (see the known limits) (#1111, #430). - Tests that passed when they should not, or failed in the wrong
place. None of these changes a shipped file.- Five adopt tests now fail when the thing they guard is removed. A
helper hands git a link's target as bytes, with a test over a target
holding a CRLF, a bare CR and a letter past ASCII. The stubghis
written as bytes, so a CRLF cannot break its#!line.
test_git_is_held_to_the_disk_when_it_asksclears
GIT_ALLOW_PROTOCOL, so a caller whose own environment saysfile
cannot hide a guard lost. Each guard of two covering pairs in
adopt-project.pyis reached alone. The naming test runs over three
name shapes (#1071, #916). That closes three test limits v1.1.0 named
(#457's L4-54, L1-60 and L1-62). - Three rows hold
--link-source's unchanged reading at the old
first-=split's corners: a keyed path that begins with=, a keyed
blank path and a key in capitals. Five mutants of the gate's reader
that passed its five closest test files now fail it (#1058, #1054). - The re-run the ruleset hint offers is dated later than the first run,
so a leg's seed made again, even in the same second, is another commit
and fails "kept at its commit", where it passed by the clock (#943,
#922). - The doctor's explanations are held clear of shell syntax over a root
that needs quoting,my atlasanddo ct'or; the old test held only
because pytest's temporary roots need none. The file now says the
PowerShell form of a line is checked as text, by two tests, and run by
no job (#899, #415). - A second
members:line above the block is refused by the YAML reader
as a key written twice since #974, so the two rows that expected
family-members-unrecognised, and had turned main'stestsjob red,
take that refusal (#1026, #1024). - The row that names both
bump-leg.pycommands pinsLANES_LANEblank
in its own process, asrun_scriptdoes for the doctor it runs, so a
shell that exportsLANES_LANEno longer puts a--traileron the
expectation alone (#1081, #1060).
- Five adopt tests now fail when the thing they guard is removed. A
- Docstrings that said what is not so. The docstrings of five adopt
test files andtests/gh_guard.pysaidcheckruns nogh, or
misstated when it does.checkasksgh repo viewonce of each leg once
the plan's names andorg:pass, which the session's guard answers off
Windows. They now say that, and whatexecute's in-process runs replace
(#881, #880).test_adopt_split_branch.pysays it too, and "every
machine" is said off Windows only (#966, #938). Two docstrings in
tests/test_family_bump_fetch_refspecs.pysaid #946's first head carried
a tag or mirror refspec intoorigin/, where only a tag refspec in a
folder was carried, and the mirror was already left out and had no test
(#1007, #997). - The docs. AGENTS.md and the handbook say step (3) of setup refuses
what stands at<into>/[<Family>/]<Project>: with--familythe clone
lands one folder deeper, except where the run already stands in the
family's own folder. They said<into>/<Project>alone. README.md, the
handbook andsetup-project.py's docstring say nothing that stays is
created before the typedyes, where they said nothing is created: the
run makes a few short-lived folders, such as
.openreposhape-write-probe-*above the clone andopenreposhape-seed-*
andopenreposhape-branch-*in the temporary folder, and takes each away
again, and the checkout--keep-shape-checkoutkeeps is the one
exception. The docstring ofpassed_valuessays what argparse takes as a
flag's value that setup does not read as one,-, a negative number such
as-5and a word with a space such as-x y, so step (4) reports from
fewer values than the scaffold reads (#1051, part of #921 and of #923).
v1.1.1's notes in the repository now say, as runs at v1.1.0 and v1.1.1
showed, what the doctor does over a tab line or a pattern that does not
compile in a leg clone, and name theC-locale refusal above as a known
limit (#893).
Known limits
v1.1.1's known limits, and the earlier ones they
carry, still hold where this release does not close them; this list is not
all of them. This release closes:
- v1.1.1's line on the pipx command, which had none of the bash command's
checks of$OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REFand refused only
..and a.lockending in a ref: wherever it prints a value or hands
one to git, it refuses what the bash command refuses, and it holds a ref
to git's rule part by part (#947, #939, #941). Two differences stay
(below). - v1.1.1's regression under the
Cor POSIX locale, tracked as #940: a
letter past ASCII in a repository, a ref, a version or an option is no
longer "a control character" (#986, #940). - v1.1.1's
--sourcedirectory this user cannot enter, #457's L1-105: it
issource-unreadable, exit 2, where it ended in aPermissionError
traceback (#970; #1072, #918). - v1.1.1's four lines for #879: with the work tree's
.gitmodulesgone,
the wrong-case move that commits a.gitmodulesholding one key, and
family bootstrap okbeside git's exit 128; a stray gitlink's
git rm --cachedthat exits 128 after a value finding's lines, and a
checkout moved bygit mvthat no finding named (#889, #879). - v1.1.1's
make bootstraptraceback on Python 3.12 and 3.13 over a leg
directory or agovernance/nobody may enter: the leg is named and left
as it is, and step (c) names the folder (#958, part of #430). The rest of
that line, on setup's step (7) andsiblings.py, still holds. - v1.1.1's "a handler that binds and re-raises", in
update-shape.py's
missing-name refusal: such a kept copy is refused (#935, #897). - v1.1.0's #457 L1-32: a failure of
check's probegit initother than
anOSErroris one NOTE in git's words, where it stoppedcheckwith "a
git or gh command failed" (#1031, #910). And three of v1.1.0's test
limits, #457's L4-54, L1-60 and L1-62 (#1071, #916). - v1.1.0's #457 L1-17: with
org:andadopt_branch:both outside the
alphabet,executenow refusesorgfirst, ascheckdoes (#1015,
#901). Where the plan is held to the source's recorded repository,
checkstill saysunsafe-valueandexecuteplan-not-the-source, as
v1.1.1 said (#1020). - Of v1.0.0's:
adopt-not-the-sourcecoming only after the clone for an
org/reposource (#963, #903); a YAML key written twice (#974, #917);
the plan naming neither the tracking branch nor, in local mode, the
visibility (#969, #913); a seed found only onseed/scaffold(#1052,
#921); a--pinproduct the two tools spelled differently, or that only
a standard YAML reader misreads, such as3dGlass,0x1F,yes,on
or.5(#995, #924; #1090, #912); a declared pin's file reached through
a root-kept link; a link at a path the shape writes into the clone's
.git, such asCLAUDE.md -> .git/refs/remotes/origin/main; one that
climbs back out of.git, such as.git/refs/../HEAD; one to
.git/reftable;plan-root-blocks-shape-copy's words for a link at a
copy (#1091, #904); and a hand-edited pin whose product holds a tab,
whichchecknamed in two findings,plan-pin-malformedand
plan-unwritable-value(#1015, #901).
And narrows:
- v1.0.0's line on a bare number or boolean in a hand-edited plan's text
field:checkandexecutenow refuse one inid:,tracking_branch:
oradopt_branch:(#1016, #902); inelected_by:,reference:or
org:it still passes (below). - v1.1.0's #459 L4-24,
setup-project.pystripping only C0 controls and
DEL from a value it prints: its self-bootstrap now refuses a
$OPENREPOSHAPE_REPOholding one of the launcher's 41 code points
(#1043, #953). A C1 control still passes setup's checks, and so does one
of the 41 code points in--into,--local-remote-diror an argument
after--, which onlyPATH_REorCONTROL_REasks (read from the
code); withOPENREPOSHAPE_REPO=acme/r<U+009B>31mforgedthe clone line
prints the raw bytes, as before. - v1.1.0's
bump-leg.py --dry-runexiting 0 over a pin its own validators
refuse: unchanged, but a dry run now refuses a validator it cannot read
(#934). - v1.1.0's line on the package on Windows: the release's install check now
also scaffolds a project root with the package'sscaffold-projecton
every leg, Windows and macOS included, and runs the doctor over it
(#945). - v1.1.1's line on a holder's FAILED line in nested makes: a member's own
exit 2 after a-make one or two levels below, or after|| trueand a
-command that failed or that a signal killed, now readsexited 2
(#891, #890). - v1.1.1's last line, on Windows and macOS: since #986, the checks of a
repository, a ref, a version and an option read bytes underLC_ALL=C,
as the checks of$OPENREPOSHAPE_BIN_DIRdo, but forinstall.sh's
read of the redirect's location; that read, and the multibyte re-read of
$OPENREPOSHAPE_BIN_DIRthroughlocale charmapandiconv, use the
person's locale.
Ruled by Brett Heap at 2026-10-22T12:01:22Z, "take your recommendations on
#979, #917 and #921":
adopt-project.py checkpasses a symlink target of 1024 to 4095 bytes.
checkholds the bound at Linux's, 4095 bytes, but macOS and XFS (the
default filesystem of RHEL-family systems) make at most 1023, so on a Mac
or an XFS hostexecute's first clone fails, after both leg repositories
exist. Both limits are read from XNU's and XFS's sources, not measured;
#979 stays open for a measurement on a Mac and on XFS (#979's part (a);
its part (b) landed in #1070).- The YAML reader is still a subset by design, as v1.1.0's notes say
(#459's L4-156). Measured at main038dafb:k: - a,k: [- ],
k: [e?],k: [|a],@a: 1andk: [?a]each read, where PyYAML
refuses them or reads another thing (#917's U123 and U155). - A holder made with
family.py initbefore v1.1.0, with a--familyand
a--namethat differ, keeps a READMEcdline that names the folder a
clone does not make. Edit it by hand, as "Install and upgrade" says;
update-shape.py applyleaves it, by design (#921's U097). family.py bumpasks only the mount's ownoriginwhether a branch
reaches the commit, throughrefs/remotes/origin/*. So a mount whose
originis a fork passes a commit only the fork has:bump --topins
it, exit 0,validate-family.pypasses, and a recursive clone of the
holder failsnot our ref(#921's U066, ruled 2026-10-16).- A holder pinned before #625 to a commit only its mount held stays
pinned: neithervalidate-family.pynor the doctor asks whether a branch
of the member's remote reaches it (#921's U068, ruled 2026-10-16). family.py add --at <tag id>pins a release commit, which may be on no
branch. Since #625,bump --tothat commit, a repeatedbumpand
validate-family.py's digest repair are each refused
member-to-local-only, where v1.0.0's notes say a repeatedbumpsays
nothing to do. As ruled (#613's D2, option 1), push the commit to a
branch of the member first;bump-leg.pyrefuses its twin alike (#921's
U071).
New in v1.2.0, each an open issue with no milestone, left out of v1.2.0 by
Brett Heap's ruling of 2026-10-21T10:32:04Z, "leave the follow-ups out of
v1.2.0"; most have a fix in review that lands after the tag:
- A name in a root that is a link into a folder this user cannot search is
still read as absent at eleven sites, silently on Python 3.14 and with a
PermissionErrortraceback on 3.12:family.py remove --dry-runsays no
section is removed, because the.gitmodulesreads as having none;
siblings.pyreads no url and clones the member from its row's default;
and the doctor'sthe way insends the person tosetup.sh, because the
.gitreads as no repository (#1110). A shape root whose own.gitis
such a link readsDRIFTED, its legs read as having no gitlink, and both
its validators crash (#1146). A project'sbootstrap.py, and a holder's
validate-family.py,bootstrap.pyandsiblings.py, over such a
root's.git, end in a traceback on 3.12, or saynot-a-git-repoon
3.14 (#1148). adopt-project.py checkcan sayplan okwhereexecutethen stops
after both legs exist: on git 2.43, over aninit.defaultBranchgit will
not take (#1083); and over a parent's-csetting, such as
core.protectHFS=true, orGIT_ATTR_SOURCE, which its throwaway
repositories leave out (#1136). For a source with a submodule, two of
check's steps still make their repositories inTMPDIR, run this
machine's hooks and use a plaingit init(#1083).checkwrites into a partial-clone source: over a--filter=blob:none
clone that lacks blobscheckreads, its reads fetch them, running the
upload-packprogram the source's config names and writing a promisor
pack into the source'sobjects/pack(#1145).- A recorded clone whose
.gitthis user cannot read is called "no longer
whole" and cloned again (#1099).planorcheckon a repository whose
.gitis at mode 000 says onlygit-failed, and a readable.gitwhose
HEAD, or whose git directory, is at mode 000 is cloned again (#1151). A
--sourcetoo long to name is refusedsource-unreadable, but its
remedy offers ahttps://github.com/<owner>/<repo>URL that can never
clone (#1003). - Under a one-byte locale such as Latin-1, a plan written in UTF-8 is asked
only as written, so a source that is there isplan-source-gone; #1072
mended theCand POSIX locales only (#1078). - Under a one-byte locale, with a plan written in UTF-8 whose record is a
cloneplanmade, and a work directory with a letter past ASCII,check
does not take its own clone for the record, so it clones again on every
run (#1112; its fix, #1153, lands after this release). checkandexecutecan still name different first problems for a bad
id beside an unanswered entry, a stale source or a refused name (#1020),
and for four pairs with no bad id (#1113). Apins:entry that is no pin
is spelled with Python'srepr, such as`pins:` entry 1 is 7
(#1020), and so is a pin field that is not text, such ascommit: 7or
True(#1114).elected_by:,reference:andorg:in a hand-edited
plan, read as a number or a boolean (elected_by: 007,org: true),
still givecheckplan ok, andexecutetakesstr()of what was
read, so007is7,trueisTrueand1.50is1.5(#1016's
body; #457).execute's NEXT printsmake bootstrapwhere GNU make at the root reads
a keptmakefileorGNUmakefile, which has nobootstraprule
(#1047). Two sentences, inshape_materialize.pyand in the pinned
AGENTS-shape.md, say a keptMakefileholds the shape's block back,
and leave out a keptmakefileorGNUmakefile, which GNU make reads
first (#1048).execute's own NOTE, that the shape's targets arrive only
once the follow-up merges the two, is untrue there too (#1048); and
beside a keptMakefileandGNUmakefileit namespython3 scripts/bootstrap.pyas whatmake bootstrapruns, wheremake bootstrapexits 2 for want of a rule (#1047). README'smakesentence
leaves them out too (#1061). On a disk that ignores case, a kept
makefilegets no merge follow-up, and a split made on a disk that keeps
case can commitMakefileandmakefileside by side (#1049, not
measured).- Names
adopt-project.pyprints raw, so a bidi control in them reaches the
terminal:plan'ssourceline's branch (#1035);plan written to's
--outpath and the clone line's--work-dir(#1115); and the--plan
path incheck's andexecute's finding lines and refusals, and
--local-remote-dirinexecute(#1152). Its spelling lets the
default-ignorable code points through, such as U+034F and U+3164, so a
branchma, U+034F,inprints asmain(#1128).plan's table pads a
name by code points, so a wide or combining name stands out of line
(#1034). - A case twin that sorts after a link at a shape copy, such as
shape/readme.mdbeside a link atshape/README.md, is not named while
the link sits there; a twin that sorts before the link is (#1138). - An
update-shape.py applydoes not ask again a copy an earlierapply --accept-localkept, where its row readsunchanged, so it can write a
siblings.pythat imports a name the keptbootstrap.pylacks. Nor does
it ask the other way round: a kept copy that imports a name the module
this run writes drops. Neither reaches a root pinned at v1.1.1 or
earlier, whose kept copies readboth(#1082; see "Install and
upgrade"). - A pin's
commit:ortree_sha256:that ends in a line break passes as
well formed, becauseCOMMIT_REandSHA256_REinrepo_shape.py, and
COMMIT_40in the assembly root'sbootstrap.py, end in$and are
read with.match:validate-manifest.pysaysmanifest ok,
validate-pins.pygivespin-commit-unresolvableor
pin-digest-mismatchrather than a malformed finding, and the doctor
reads the commit with its line feed;adopt-project.py checkpasses such
a pin's row, the assembly root'sbootstrap.pyprints its upstream
notice, and a holder'svalidate-family.pypasses afamily.yaml
shape.digests.tree_sha256that ends in one (#1017). The same$lets
validate-family.pypass anid:that ends in a line feed, both
validators a date, andvalidate-manifest.pya leg'srepository:;
update-shape.pyrefuses a pin'ssource_repository:that ends in one,
but in words that name--upstream(#1139). Only hand-written YAML holds
such a value; no tool writes one, and the limit is older than v1.2.0. - After
bump-leg.pyorfamily.py bumpfetches in a repository whose
originhas an unforced refspec that puts a branch under another name,
the repository's owngit fetch originexits 1, non-fast-forward;
git fetch --force origin, once, puts the ref back and loses no commit.
A refspec such as+refs/heads*:refs/remotes/origin/h*beside a branch
h/x, or+refs/pull/7/head:refs/remotes/origin/main, still clashes:
the tools' fetch exits 128 where the repository's own exits 0. These two
are older than v1.2.0 and unchanged by #1087, and the docstring of
gives_a_branch_another_namesays no such shape can clash (#1100). - Where a leg's mount has no remote named
origin,
bump-leg-commit-not-on-remote, for a--totag the leg holds without
its commit, says the tag "is not in the leg's own object store", which is
untrue; the code is right. The same sentence answers a tag that leads
through a tag the leg lacks. A tag the leg holds of a tree or a blob it
lacks is not refused by name asbump-leg-target-not-a-commit: with no
originit gets that sentence and is told to push a commit the tag never
named, and with anoriginit is told<repository> has no branch containingit (#1103). family.py'smember-duplicate-idandmember-unknownremedies name
family.py removeandfamily.py addwithout--family-root, so each
fails as printed (#1005). A failedbump's by-hand lines quote a leg
mounted at a path that is not UTF-8 as Python's\udcXX, which names no
path (#1086).validate-family.py'sgitmodules-path-twice, and its check for values
git dies on, read the work tree's.gitmodules, or HEAD's, where git
reads the index's (#1077). So a bad value staged in the index alone is
family ok, a plain commit then records it, and a clone's
git submodule update --initexits 128; with the value in both files,
the printed--unset-allline has no stage line; and an index
.gitmoduleswith a line git cannot parse after such a value is
family oktoo, thoughgit submodule statusexits 128 (#1150).- Where git's update is itself killed after a git it ran died of a signal,
a holder'smake bootstrapsays that git's update "went on and ended
with exit -N". No row reaches that line; an OOM killer that took a
checkout and then the topgitwould (#1085). - The scaffold's ruleset hint sends a person into the
--reuse-empty-repo
re-run without #915's sentence on the prompts that can still wait, and a
push refused for its ssh host key gets that hint alone (#1108). A re-run
whose probe ssh refused for GitHub's host key is told "Let git read it"
and sent tossh -T, with no word on checking the key against GitHub's
published fingerprints (#1137). - Beside a
.gitmodulesthat a leg's index tracks but this disk lacks, the
doctor does not name a gitlink that no section of the index's copy
registers, though git reads that copy andgit submodule statusexits
128 (#1093). refused_pathswithcore.ignorecase=trueand case twins on a disk that
ignores case is untested, and not known to fail (#1033).- CI's
testsjob does not run on Ubuntu 26.04, since the full suite there
fills/tmpunder pytest's basetemp (#1096).
New in v1.2.0, from the pull requests that landed it:
- The package's
--versionasks a ref its shape, where the bash command's
--versionasks only its characters (#866), so a ref such asv1café
prints from the bash command and is refused by the package. The
package's--doctorbeside a git checkout, and--preflight, read
neither variable, so they refuse neither (#947). setup.shhas no copy of the 41 code points, so U+202E, U+200B and the
rest, but U+2028 and U+2029, pass its two guards in every locale; the
bash command,install.shand the package refuse them first where they
are the way in, and README'scurl … setup.sh | bash -s --is the way in
without them (#986).- A byte that is not UTF-8 (a lone
0x9B,0x85or0xE9, or an
overlong form of a control such asC0 9B), U+0378 and U+FFFE pass the
seven checks of a repository, a ref, a version and an option in every
locale, and the line after the check prints the value, as v1.1.1 said of
a lone0x9Bin$OPENREPOSHAPE_BIN_DIR. A multibyte locale that is not
UTF-8, such as GBK, reads0xC2 0x80to0xC2 0x9Fas letters, and
these checks refuse such a value as a C1 control: unlike the directory's
check (#858), they do not read it again in the locale's charset (#986). - The release's install check does not start
validate-family.py, or the
doctor's fallback to the standard's template copies, under-S, so those
children are unmeasured on macOS and Windows; and it does not ask whether
the root it scaffolds is compliant (#945). - The short-lived folders setup and the scaffold make are removed on a best
effort: a killed run can leave one, and a failed removal is a warning
(the write probe), silent (the branch folder) or a traceback (the seed
folder). The printed--dry-run: nothing was created.and each
refusal'sRemediation: nothing was createdstill say "nothing was
created" (#1051, #1052). - A hand edit that leaves
family.yamlwith a secondmembers:line is
now the reader's one-lineyaml-unparsable, with no remedy, where
family-members-unrecognisedsaid to fix it by hand, commit and run
again (#1026, #1024). - After #974 and #1021, the YAML reader still reads
'a\n'fork: |over
awhere the file's last line has no line break, and PyYAML reads
'a'; it refuses a block scalar as a sequence item (- |) as
unexpected indentation; and it takes1and"1"for one key, so a
mapping holding both is refused as a key written twice, where YAML reads
two keys (#974). planstill asks some refusals only after the source is read, for an
org/reposource too:naming-unclassified,naming-role-mismatch, an
empty--elected-by, a--projectof 251 to 255 characters and a
missing--path-policy. With aghthat cannot clone, each is
source-unclonablefor anorg/reposource. A local source's
--work-diris still made, and refused where it cannot be (#963).- A root-kept link to a name inside the object store, a leg's store or the
reftable tables, or underrefs/heads,refs/remotesor their reflog
folders inlogs/where the source's HEAD is on no branch, is refused as
onecheckcannot tell about, though some such links would adopt. The
reftable answers were measured on git 2.47.1 only; git 2.43 reads neither
GIT_DEFAULT_REF_FORMATnorinit.defaultRefFormat(#1091). checkstill treats every root link as giving no.gitattributesrule,
though git reads a link's text as a rule line where it holds a space, a
tab or a carriage return; whatcheck's findings say for such a link is
not measured (#964).- A
--work-dirloop on Windows is named only whereos.statof the link
reports ELOOP; Windows was not measured (#1065). - The setup preflight's credential-helper check reads the starting folder's
.git/config, which the scaffold's pushes do not, so a rule kept only
there silences both warnings for pushes it does not reach. A rule keyed
to one repository, an organisation spelled in another case than
--org's, and aninsteadOfrule that rewrites to anhttps://URL
carrying its own login still warn. A helper keyed to another
organisation beside an emptystorenow stays silent. Emptyhelper =
lines are dropped, not read as git's list resets, and beside a helper
other thanstore,cacheamong them, the check stays silent, since it
cannot read what such a helper holds. Git for Windows' home from
HOMEDRIVEandHOMEPATHwas not measured (#1008, #1022). - A push refused over HTTPS with 403 keeps its words, since its cure is a
right and not a login;family.py init's andadopt-project.py's pushes
keep their own ruleset hints; and only OpenSSH's words are read, not
another client's such as plink's (#1012). Git's own prompt is off for the
scaffold's git alone, and ssh, a credential helper or askpass can still
wait, as the help says (#971, #915; #1101);family.py's git and the
clone setup makes of the standard were outside #915. - Before Python 3.13, a
--pin-sourcethat runs round a symlink loop is
named as typed by the scaffold and joined but not resolved by step (4),
and on Python 3.9 for Windows the two can name a relative path
differently (reasoned, not run). Under self-bootstrap a relative
--pin-sourceis resolved against the temporary checkout of the
standard, not the folder the run started in, in step (5) and now in
step (4) (#949). - A holder's
make bootstrap: the FAILED line still names another
command's code in the shapesMakeErrors' docstring lists, one more
since #891: undermake -k, an inner make that failed with 1 and a later
-make with 3 readexited 3. A recipe that lets an inner make fail in
the shell (|| true, or;), or with a-in a make run with-s,
which prints no(ignored)line, and then exits 2 itself with no
(ignored)line between, still names the inner command's code, such as
exited 1(#891). Under GNU make 4.4's named-pipe jobserver, a member's
make running its jobs together is read from 4.4.1's NEWS and shown with a
stand-inmake; no GNU make 4.4 or later ran (#1023). With a token in
use the run is untraced, so a signal is told only from git's own exit
code, and a signal before a later failure is not told (#1045, #1076).
Past a member at another commit than its pin, a checkout the walk cannot
stator run git in, or a gitlink of a member whose own.gitmodules
git cannot read whole, the run reads as before (advice, andfamily bootstrap ok); a git before 2.22 has no trace2, so there it reads as
before too; and aGIT_TRACE2_EVENTthe person sets for one command is
overridden (#954). An unmerged.gitmodulesnow failsmake bootstrap
where the gate passes (#889). - Where the work tree has no
.gitmodules,validate-family.py's value
finding puts back HEAD's, not the index's, so a fix staged in the index's
is lost (#889). A hand mount whose.gitfile names a git directory
outside the holder's store, a linked worktree of an outside clone, gets a
plaingit rmthat exits 0 and moves nothing, and--unset-allof every
key can leave an empty[submodule "<name>"]header (#1025). family.py bump's fetch no longer names a mount's refspec that maps
tags intoorigin/, so its--prunedrops a tag's copy underorigin/
that the person's owngit fetch originwrote, in the dry run too, and a
+refs/*:refs/remotes/origin/*mount loses its mirror the same way; the
nextgit fetch originwrites them back (#946). In both tools, a full
ref name that is no pattern can still bring a tag, since git tries
refs/tags/<name>(#1027, #1068); a refspec that puts pull requests in a
folder of their own, such as
+refs/pull/*/head:refs/remotes/origin/pr/*, beside a fetch ofmain
alone, still clashes with a remote branchpr/7: the tools' fetch exits
128 where the repository's own exits 0 (#1027, #1087); and with a leg's
own refspecs carried,bump-leg.py's fetch exits 128 where one names a
ref the remote no longer has, as the person's owngit fetch origindoes
(#937, #1068).bump-leg.pyreads a leg'sremote.origin.fetcha line
at a time, so a value holding a line break is misread (#1068).family.py add's take-back does not compare the gitlink's commit with
the pin, which the gate'smember-gitlink-mismatchdoes; a misplaced
gitlink under--dry-runexits 0; andmembers: null # none yetis
still refused (#957).- A file whose
statworks and whose open is refused, such as a member's
project.yamlat mode 000 in a folder this user can search, is read by
the holder'ssiblings.pyandbootstrap.pyas before; in
validate-family.py, a member's.gitfile at mode 000 still reads
member-commit-unresolvablewith a fetch remedy, and a file at
members/<Project>still readsmember-uninitialized(#951, #955,
#956). ThePermissionErrorcases of #430's sweep were measured on
Python 3.12 and under a shim that answers as 3.14 does; CI'stestsjob
runs the newest Python 3 (3.x), on Linux alone. bump-leg.pyover aproject.yamlat mode 000 in a root it can search
refusesyaml-unreadablewith no remedy, and over a validator at mode
000 alone it isbump-leg-root-dirty, since git sees the mode change; its
validator check runs after the fetch, and afternothing to do(#934).update-shape.py applyrolls back a refused read after its first write,
but any otherOSErrorthere, a file gone betweenstatand read or a
full disk, still escapes with nothing put back;checkdoes not read a
manifest at mode 000, whichapplythen refuses asyaml-unreadable
with its absolute path; andupdate-unreadablenames only the first path
it cannot read, wherevalidate-pins.pynames each (#976). A project's
validate-manifest.pyprintsproject.yaml's absolute path raw in
manifest-missing,manifest-unreadableand its findings count (#958,
read from the code).update-shape.py's kept-copy check still reads as binding a handler
whoseraiseis under anifor inside awith, and one that binds and
then callssys.exit(); a copy whose ownraise Xan outer
except ImportError:would catch, which Python starts, is now refused
(#935).make bootstrap's pasteable lines keep the text they had for a leg path
that ends in a line feed or holds a NUL, and git ignores a
submodule.<name>.paththat begins with-, so no command initializes a
leg there (#944, #973). The case-twin rule compares withcasefold(), so
ssand a sharp s are twins though a disk may not fold them, and a
reftable leg is left whereMainandmainare two refs;bump-leg.py
andfamily.pycompare a root's or a holder's branch with the tracking
branch byte for byte, so on a disk that folds case a root or a holder on
Mainis not refusedbump-leg-on-tracking-branchor
family-on-tracking-branch(reasoned, not measured). Twins are named one
at a time, one rename a run, and a twin whose last path component is 246
bytes or more, or a stale reflog directory under the chosen name, is not
handled (#948, #984, #1006).- The doctor names no gitlink whose path is not UTF-8 or holds a control
character, nor one beside a.gitmodulesthat is unmerged, that git
refuses, or that is a link or a directory on this disk (#1073, #1092). - The doctor over a leg at its pin whose git directory this user cannot
read,.git/modules/specat 000 under a mount it can read: thepins
row is right,pin-submodule-unreadable, but thelegsrow saysspec: populated, but git cannot read a HEAD in it, theplacementrow says
spec: it is not a git repository, and the verdict isDRIFTED, on 3.12
and under a shim that answers as 3.14 does, as before #975. A leg's shape
file this user cannot read, such asspec/CLAUDE.mdat 000, is theleg shape filesrow's noteCLAUDE.md cannot be read (Permission denied),
with no WHY and no NEXT, and the verdict isCOMPLIANT, where every
other path #975 names carries the gate's advice; before #975 the row
crashed,INVALID (leg-shape-files)(#975). - Five branches of the doctor's
legsandmembersrows for a mount this
user cannot reach have no test, and are right by reading (#975). #1050's
rows read and write through no tool's own path, so a reader that opens a
file asutf-8-sigwould pass them, and none holds that U+FEFF alone is
cut (#1050). - Windows and macOS: CI runs on Linux only (#597), but for
tests-git-bash, which runs the launcher's andinstall.sh's suites on
Windows; the release's install check is the one run of the package there
and on macOS (#945). A disk that folds case (#948, #984), Windows'
CreateProcesserrors (#1028, #1062) and a Windows--work-dirloop
(#1065) are emulated in the tests, or reasoned, not run there.
Full list
- Merged pull requests: https://github.com/opensoft/openRepoShape/pulls?q=is%3Apr+is%3Amerged
- The
v1.2.0milestone: https://github.com/opensoft/openRepoShape/milestone/5 - Every commit since v1.1.1: v1.1.1...v1.2.0