Repository navigation
openRepoShape v1.1.1
openRepoShape v1.1.1
v1.1.1 is a patch of v1.1.0. It adds no command and no flag. It fixes text
that a terminal acts on reaching it, names that are not ASCII or not UTF-8,
.gitmodules files read otherwise than git reads them, and adoptions and
scaffolds that failed late, after the plan was built or a repository was
made. 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
cut from release/v1.1 (#850).
At main 5017830, on 2026-10-18, 70 pull requests had landed on main since
v1.1.0's commit, 190133d. At 14:17Z, 71 of the v1.1.1 milestone's 71
issues were closed, #709 among them, closed as not planned.
What 1.1.1 fixes
- Text a terminal acts on stays off it.
install.shand the bash
openRepoShape, the oneinstall.shplaces, refuse a
$OPENREPOSHAPE_BIN_DIRholding a control character, a C1 control
included, or one of 41 code points a terminal may draw as something
other than itself (a bidi control, a character of no width, a soft
hyphen, U+2028, U+2029), before any request and without echoing it
(#760, #837); where the bytes refuse a name, they read it again in a
multibyte locale's own charset, so that locale's letters are placed
(#858). The bash command checks$OPENREPOSHAPE_REPOand
$OPENREPOSHAPE_REFthe same way before--versionanswers (#866); the
pipx command does not (see the known limits).validate-pins.pyspells
every name and path it prints (#712), andfamily.py init's plan rows,
seven refusals andbump-leg.py's refusals that name the root spell the
paths they print (#766). - Names that are not ASCII, or not UTF-8. The doctor keeps such a
character where the report can show it, so a NEXT or keep line naming a
path such ascaféruns as printed, and escapes a command's value whole
where it cannot (#785); it finds a member whose.gitmodulessection name
is not UTF-8 (#732).make bootstrapmoves back a leg's tracking branch
whose upstream is not UTF-8 (#710);update-shape.py,family.pyand
bump-leg.pyread the branch HEAD is on as git's bytes (#765);
setup-project.pyand a project'sbootstrap.pytell each Python child
the encoding it is read in (#836); and a byte-order mark no longer breaks
a YAML file or the upstream notice (#855). .gitmodules, read as git reads it. The doctor and both
validate.ymltemplates read only the sections, keys and values git
reads, so the doctor's NEXT lines name no url, and CI no App-token
repository, that git would not (#733, #741, #786);make siblingsand
the doctor'smembersrow read the same sections, keys and values, but
still read an unmerged file (#733). A holder'smake bootstrapand its
validate-family.pyname a value that git dies on, where bootstrap said
family bootstrap ok(#768), and bootstrap reads an index or HEAD copy
with a line git cannot parse up to that line, as git does (#856).adopt-project.pyasks before it makes anything.--source,--out,
--pin-source, the source's default branch and both leg repositories are
asked about before any leg exists, and refused there (#796, #799, #800,
#797, #798).checkholds a plan to the repository it records (#716),
the line-ending block governs a copy written through a symlinked
directory (#807), the recorded answers cover a directory split by hand
(#801), and the cloneplanandcheckmake is removed only while it is
still the one they made (#803).- One reading of
legs:,members:and a branch name. Alegs:that
is not a list, or an entry that is not a mapping, is said in one sentence
by the naming gate,validate-manifest.py,validate-pins.py, the
doctor, a project'sbootstrap.py,bump-leg.pyand
update-shape.py --pr(#714, #832). A holder'smembers:that is not a
list, or an entry that is not a mapping, is said in its
validate-family.py's words by the doctor,family.pyand the holder's
make bootstrapandmake siblings(#864). The YAML reader keeps
Unicode spaces as text, and a branch name holding a character that does
not print or is white space is refused (#715). - Git Bash gets
sh's quoting. Under Git Bash, the lines the tools
quote for the host are quoted forsh, where they were quoted for
PowerShell (#711). - Names GitHub cannot hold.
scaffold-project.py,
adopt-project.py execute,family.py initand setup's plan refuse a
repository name over 100 characters, an id whosexf-project-<id>topic
is over 50, and an--orgover 39 or one GitHub gives no organisation,
before any repository is made, where an id over 39 failed after the
repositories existed;adopt-project.py checkfinds each of them as
executerefuses it (#838, #863). A project name typed at
setup's prompt is checked as--projectis, a refusal names the check
the value failed, and a name too long for GitHub's repository names is
refused before step (4) (#729, #777). - setup-project.py's rehearsal, and a ref git refuses. A run passed
-- --dry-runmakes none of the preflight's offers, so it no longer
writes git's global config or signsghin, and--shape-refis held to
git's rule part by part (#857), as is a ref given tosetup.shor
install.sh, and one the bashopenRepoShaperecords, reads from its
record or checks out for--doctor(#874). - A project's
make bootstrap. A validator's refusal ends the run
bootstrap REFUSED, exit 2, where it read as a finding; an upstream
path, cache or pin it cannot read is no answer, where the run ended in a
traceback; and a cherry-pick or a revert stopped in a leg whose refs are
a reftable is left alone, where bootstrap ended it (#764). - The doctor. Every git it starts runs with
GIT_NO_LAZY_FETCH=1, so
on a git that reads the variable a read a partial clone cannot answer
fails rather than fetches (#787). In a root that lost
scripts/repo_shape.py, thelegsandmembersrows' NEXT puts the file
back, and theagent-filesrow's WHY puts it back before it names a gate,
where each named a gate that dies on the import (#750, #776). Outside a
shape root, a naming policy in the doctor's own checkout that does not
load, or cannot be used, is ann/arow onthe way inand, in a
repository,the other way in, not a finding or a traceback (#707). - Family holders. More of
family.py add's andvalidate-family.py's
remedies type as printed in the states they name, for a member under
another spelling (#761, #762, #839, #840) and for a gitlink outside
members/(#878); a holder'smake bootstrapno longer prints the
SHAPE_LEGS_TOKENadvice where git fetched nothing, and tells a
member's finding, its refusal and make's own failure apart, through a
member's own nested make too (#839, #884);add,bumpandremove
see a cherry-pick or a revert stopped in a reftable holder (#684); and
make siblingsreports a slot that is not a clone, a link to nothing
among them and, on Python 3.12 and later, a junction to nothing, and
places the members after it (#735, #784, #793). - CI on a release branch.
tests.ymlruns on a push to
release/v<X.Y>as on a push to main, andrelease.yml's header says
how a fix is back-ported (#852, for #850).
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.1.1names this release.openRepoShape --version
printsopenRepoShape (opensoft/openRepoShape @ v1.1.1). -
From PyPI:
pipx install openreposhape==1.1.1for this release, and
pipx upgrade openreposhapeto upgrade one already installed.
openRepoShape --versionprints
openRepoShape 1.1.1 (opensoft/openRepoShape @ 50178305e4229275d18e71c2152403b4e9d3004e).
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.1.1 changes six of the copies an assembly root
pins:scripts/validate-pins.py,scripts/validate-manifest.py,
scripts/bootstrap.py,.github/workflows/validate.yml,
scripts/repo_shape.pyandscripts/validate-repository-naming.py. It
changes six of the copies a family holder pins:
scripts/validate-family.py,scripts/bootstrap.py,
scripts/siblings.py,.github/workflows/validate.yml,
AGENTS-shape.mdandscripts/repo_shape.py. From a checkout at the
v1.1.1 release commit,update-shape.py check --root <path>reports each
asupstream-changedin a root pinned at v1.1.0 that has not edited it,
and
update-shape.py apply --root <path> --at 50178305e4229275d18e71c2152403b4e9d3004e --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.0's. -
The rendered READMEs. A project's or holder's
README.mdis rendered
once, when it is made, andupdate-shape.py applyrewrites only a
holder's closingShape:line. So the new sentence on a.gitmodules
that git refuses, in the root's README and in the holder's (#790), and
the holder's new sentences on the one spelling ofmembers/and on its
folder's name (#738, #822), reach new roots only. An existing holder
reads the last two in its re-pinnedAGENTS-shape.md; #790's sentence
reaches no existing project or holder at all. -
What a script sees. Runs that passed on v1.1.0 and fail now, among
them:- a holder's
make bootstrapover a.gitmodulesvalue that git dies
on, exit 1, where it saidfamily bootstrap ok; and
validate-family.py --no-members, or any mode in a holder whose members
are checked out, over the same value, over a member's staged removal,
or over a section with no url, exit 1, where it saidfamily ok(in a
plain clone, v1.1.0's members modes refusedmember-uninitialized,
exit 2) (#819, #768; #820, #762); - the same
validate-family.pyruns over a gitlink outsidemembers/
that no section of.gitmodulesgives,gitlink-unnamed, exit 1,
where they saidfamily ok(#882, #878); family.py add,bumpandremove, and a holder'smake bootstrap
andmake siblings, over amembers:that is not a list, or holds an
entry that is not a mapping, exit 2, where some such values passed,
make bootstrapsayingfamily bootstrap ok; and
validate-family.py --no-membersover such an entry, exit 1, where it
saidfamily ok(#870, #864);family.py addbeside a section of.gitmodulesthat gives a case
variant of a row's path, or beside a case variant of a row's path
undermembers/,family-members-dir-variant, exit 2, where it went
on, and committed such a section beside the new member (#865, #840);- where a GitHub repository will be made,
scaffold-project.py --dry-run
andfamily.py init --dry-runover a name over 100 characters, an id
whosexf-project-<id>topic is over 50, an--orgover 39 or one
GitHub gives no organisation (., a trailing hyphen,--), and
setup's step (5) over such an id or--org, exit 2; setup refuses a
name over 95 characters before step (4) (#859, #838; #871, #863; #811,
#777); - a project's own gates over a branch name that does not print or is
white space, such asmain<U+00A0>, once its copies are re-pinned
(#823, #715); - CI over a
.gitmodulesthat git refuses, whatever the credential,
where with no credential the run went green (#790, #786); install.shand the bashopenRepoShape --installover a
$OPENREPOSHAPE_BIN_DIRholding a control character or a character
drawn as another, exit 2, where the command was placed (#804, #760;
#841, #837), and the bashopenRepoShape --versionover such a
$OPENREPOSHAPE_REPOor$OPENREPOSHAPE_REF, exit 2 (#872, #866);
and the bashopenRepoShape --installwith
OPENREPOSHAPE_RECORD_REF=1over a ref such asa.lock/b, exit 2,
where it recorded it (#875, #874);adopt-project.py checkover a plan it now finds a fault in, such as
plan-pin-source-not-a-clone(#842, #800),plan-not-the-sourcefor a
plan whose source names no repository (#825, #716), a leg repository
that already exists (#861, #798), or a name, anorg:or a topic
executerefuses (#871, #863);adopt-project.pygiven a--work-dirthat is, or is under, a
directory every user can write in without the sticky bit, or one a user
other than this one or root owns, or whosesource-readonlyis such a
directory,adopt-work-dir-shared, exit 2; or one whose
source-readonlyis a symlink, or already holds the clone's directory,
even empty,adopt-work-dir-occupied, exit 2 (#843, #803);update-shape.py apply --branchrun from a branch whose name is not
UTF-8, exit 2 (#849, #765).
adopt-project.py planprintsplan written to <out>, after a blank
line, as soon as the plan is on disk, before its report, where it came
after the table, the totals, the follow-ups and any WARNING or NOTE, just
before the questions. It refuses--out /dev/null(#833, #799). A
project'sbootstrap.pyrun by itself, as setup's step (7) runs it, ends
a validator's refusalbootstrap REFUSED: <name> (exit 2), exit 2, where
it saidbootstrap FAILEDand exited 1;make bootstrapexits 2 either
way, now with make'sError 2(#815, #764). - a holder's
-
A project's pin on the standard works as before. Follow
How to pin 1.0.0 with the v1.1.1 release
commit. A scaffold run through the package pins that commit and digest by
itself.
What changed since v1.1.0
v1.1.0 was tagged on 190133d on 2026-10-17. Everything below landed on
main after that commit, by area; release/v1.1, which is fast-forwarded to
main after each landing, holds the same commits. The list runs to the
commit v1.1.1 is tagged on.
adopt-project.py
--source, a repository's top level. A--source, or a plan's
recordedsource.local_path, that is a file, a directory inside a
repository or a directory inside a git directory is refused as
source-not-top-level, naming what was given, before git reads anything
else in it; the way out is the top level, which
git rev-parse --show-toplevelprints, or itsorg/repo. So a file no
longer ends in a traceback, and a directory inside a repository no longer
plans that subtree alone under the repository's name. A directory in no
repository is stillgit-failed, in git's own words. An empty value and
an unknown~useraresource-unresolvable,owner/.gitis refused
with noghcall,www.github.comis read asgithub.com, and a source
that lost a blob isgit-failed, naming the blob andgit fsck(#831,
#796).--out, asked first.planasks whether it can write--outbefore
it reads or clones the source or makes a work directory, and refuses a
folder, a pipe or a device, a missing folder or one nobody can write in
asadopt-out-unwritable, where a mistyped--outended in a traceback
after the whole plan was built.--out /dev/null, which exited 0 and
threw the plan away, is refused too.plan written to <out>is printed,
after a blank line, as soon as the plan is on disk, before its report,
where it came after the table, the totals, the follow-ups and any WARNING
or NOTE, just before the questions (#833, #799).--pin-source, a clone's top level.planandscaffold-project.py,
--dry-runalike, refuse a--pin-sourcethat is not a clone's top
level, or a bare clone's, aspin-source-not-a-clone, naming the top
level git finds.checkfinds such a path in a plan as
plan-pin-source-not-a-clone, before a partial clone's NOTE, and
executerefuses it before any leg. So a folder inside a clone no longer
records the digest of part of its tree, or of nothing. What a top level
digests is unchanged, so existing pins stay valid (#842, #800).- The source's default branch, by its full name.
plan,checkand
executereadorigin/HEADand HEAD by full name and whole, and read the
source only at a branch of its own, byrefs/heads/<branch>. A
git clone -bsource, whoseorigin/HEADnames a branch with no local
copy, is read at--tracking-branch, or refused
source-default-branch-not-localwith ways through that work, where it
wasgit-failedwith no remedy. A HEAD onmain<U+00A0>, or on a local
branch namedorigin/x, no longer adopts another branch's commit; an
origin/HEADnamingmainbeside a tagmainno longer adopts the
tag's commit; and a HEAD beside such a tag no longer recordsheads/main
asdefault_branchand astracking_branch, whichcheckthen refused
asplan-tracking-branch-missing. A default branch outside the
alphabet, such ascafé, is still refused, and the refusal now says the
way past it (#830, #797). - Both leg repositories, asked about first.
executeasks about both
leg repositories, under--local-remote-diror on GitHub with
gh repo view, before it makes either, and refuses everything in their
way at once, saying that no leg repository was created. A code leg that
already exists no longer leaves the spec leg made.checkasks the same,
so a--local-remote-dirthat is a file or cannot be written in is a
finding, whereexecuteended in a traceback afterplan ok. Aghthat
cannot answer isadopt-remote-unreadableinexecute, and a NOTE in
check, which stays usable offline (#861, #798). - The clone
planandcheckmake, held to what the run made. The
clone's directory is claimed with the run's ownmkdirbeforegh
runs, and removed only while it is still that directory, never through a
symlink, so asource-readonlyswapped for a link or for another
directory no longer turns the removal onto that directory, and two runs
in one--work-dirno longer remove each other's clone. A--work-dir
that another user can rename in, or rename from above, is refused before
anything is made in it, asadopt-work-dir-shared; the sticky bit
counts, so/tmpstays open, and neither Windows nor the default work
directory is asked. A link or a file atsource-readonlyis
adopt-work-dir-occupied, where a file ended inFileExistsError, and
so is a clone's directory already there, even empty; a skipped removal
says so in a NOTE (#843, #803). - A plan held to its recorded repository.
checkprints the
rename-first remedy for a plan in the source's own name that the naming
policy refuses, asexecutedoes. Where the live source names no
repository, a plan is held to its own recordedsource: repository:, so
a plan foracme/Atlasedited toAtlsa, withoriginthen removed from
the source, isplan-not-the-sourceincheckandexecutealike, where
checksaidplan ok(#825, #716). - The line-ending block, by the path git tracks. #471's block in a
split's.gitattributesnames each pinned copy by the path git tracks it
at, and again by its pinned path where the two differ, so a copy written
through a root-kept.github -> cino longer checks out with CRLF under
* text=auto eol=crlfand failsvalidate-pins.pyafter
adoption verified.checkreports a nestedci/.gitattributesthat
outranks the root's rule asplan-nested-attributes, and passes such a
source undercore.safecrlf=true(#844, #807). - Answers recorded for a hand split.
execute'sanswered_paths:also
records each plan entry that the doctor's walk of the leg it lands in
would put in another leg, or leave as a question, so a directory split by
hand out of an extension-majority fold no longer reads MISPLACED on the
doctor's first run afteradoption verified. A plan left asplanwrote
it gets v1.1.0's record, byte for byte (#829, #801). - GitHub's limits, before either leg. Where the legs go to GitHub,
executerefuses, before its confirmation and before either leg, a leg
name over GitHub's 100 characters asadopt-name-too-long, which named
only the disk's limit, anorg:over 39 asadopt-org-too-long, and an
id:whosexf-project-<id>topic is over GitHub's 50 as
adopt-topic-too-long, where an--idof 40 characters pushed both legs
and opened the pull request, then could not set the topic. A leg named
after a source of 96 to 100 characters is told to rename the source on
GitHub first, to at most 95 characters, and plan again.plan --help
states the bounds of--project,--idand--org(#859, #838). checkfinds whatexecuterefuses.checkasks the names, the
organisation and the topic from the same listexecuteasks
(_what_cannot_be_held), and finds each as a FINDING underexecute's
code and with its remediation, where it saidplan okover a plan
executerefused;executestill refuses the first before either leg.
Where the legs go to GitHub, anorg:GitHub gives no organisation, one
holding a character other than an ASCII letter, a digit,_or-, one
ending in a hyphen, or one with two hyphens in a row, is
adopt-bad-org(#871, #863).
update-shape.py, bump-leg.py and a project's own scripts
- The pins gate, on what it cannot read. A project's
validate-pins.py
refuses by name a pin, the manifest, a shape copy or a leg checkout that
it cannot reach or open, named relative to the root, once, with advice
for the cause (chmod u+rfor a permission, another sentence otherwise),
where it ended in aPermissionErrortraceback, or on Python 3.14 said a
present file did not exist. A copy it cannot open is
shape-copy-unreadable, and a leg checkout it cannot search
pin-submodule-unreadable. Every name and path it prints is spelled as
reprspells a character that does not print, so a newline, a bidi
control or an ESC in a workflow name or a pin entry no longer reaches the
terminal raw, and--pin-source openss=no longer answers for anopenß
pin (#827, #712). - A
legs:that is not a list. In the naming gate,
validate-manifest.pyandvalidate-pins.py, alegs:that is not a
list is the findingmanifest-legs, and an entry that is not a mapping
manifest-leg, exit 1, wherelegs: 5ended in aTypeErrorand a
scalar was walked a letter at a time. Under--projectthe naming gate
never reads stdin, so nolegs:can make it wait, and--explainno
longer ends in aUnicodeEncodeErroron a name that is not UTF-8. A
chain link or a declared pin that is not a name is a finding (#826,
#714). The doctor and a project'sbootstrap.pysay the same sentences;
bump-leg.pyrefuses them asbump-leg-manifest-legs, exit 2, and
update-shape.pysays them only under--pr, which refuses them as
update-pr-no-repository, where each ended in aTypeErroror read the
project as one with no legs (#862, #832). - The YAML reader, and a branch name that does not print.
repo_shape.py's reader strips only YAML's space and tab, so a value
that a Unicode space opens reads as PyYAML reads it.- :,: a,
{?: 1}and a quoted value followed by a Unicode space are refused, and
[?: 1], refused before as a single pair, is refused as an explicit key.
validate-manifest.py,make bootstrap,bump-leg.pyand
update-shape.pyrefuse a branch name holding a character that does not
print or is white space, such asmain<U+00A0>, which the reader used to
read asmain.bump-leg.pyrefuses it asunseen-branchbefore any
fetch or write.update-shape.py applycopies in v1.1.1's
validate-manifest.py, so it refusesupdate-validators-red
(manifest-tracking-branch) after its writes, before--branchreads
the branch, and rolls them back (#823, #715). - A byte-order mark. A YAML file, or a
.shape-upstream-tip, that a
Windows editor saved with a byte-order mark reads as the text it holds,
in every reader, the doctor's included: a markedproject.yamlis no
longerschema_version is None, and the upstream notice names the tip
(#868, #855). make bootstrapand a branch that is not UTF-8. Step (a) decodes
git's output as the system decodes a name it is given, so a tracking
branch whose upstream is not UTF-8 is moved back, where it ended in a
UnicodeDecodeError, and a good branch this system's encoding cannot
write gets a line of its own,did not try branch, where it ended in a
traceback (#824, #710).make bootstraptells the validators their encoding. A project's
bootstrap.pystarts each validator with the encoding its words are read
in, somake bootstrappasses a project whosename:the Windows code
page cannot hold, wherevalidate-manifest.pyended in a
UnicodeEncodeError(#851, #836).make bootstrap's ending, its notice and a reftable leg. A
validator's exit 2 ends the runbootstrap REFUSED: <name> (exit 2),
exit 2, where it saidbootstrap FAILEDand exited 1, a finding's code,
as a plain clone'spin-submodule-uninitializeddid; any other exit is a
finding, as before, and where one validator finds and another refuses,
both lines are said, the finding first, and the run exits 2. A
SHAPE_UPSTREAM_PATHnobody may enter, a.shape-upstream-tipnobody
may read or that is not UTF-8, acontracts/nobody may enter and a
shape pin that is no mapping are no answer to the upstream notice, where
each ended the run in a traceback. A cherry-pick or a revert stopped in a
leg whose refs are a reftable is read from its ref store and left alone,
nothing moved, where bootstrap ended it and carried its staged change
onto the branch.docs/cli.mdsays bootstrap places a leg at its pin
only where that loses no commit (#815, #764).- update-shape.py's
git showlines and its kept-copy readers. The
git show <target>:<source>a remedy prints is shell-quoted, so a source
path with a blank or a'pastes, andupdate-add-would-overwritenames
the run's target where it printed<commit>. The missing-name refusal
says the validators pass only where none of them meets the line. The
readers skip anif False:,if 0:orif None:body and a statement
after an unconditionalraise, as Python does, and the binding reader
anif TYPE_CHECKING:body, which the import reader already skipped
(#802, #675). - The branch HEAD is on, as git's bytes.
update-shape.py,family.py
andbump-leg.pyread the branch HEAD is on as the bytes git wrote, so a
branch ending in U+00A0, U+2028 or U+0085 is no longer read as its
neighbour:family.py removeno longer prints a push line for the
neighbour, andupdate-shape.py applyno longer reports a commit and a
push on it, nor switches HEAD to it in a rollback. A name that is not
UTF-8 is refused by name before anything is written, as
update-branch-not-utf8,family-branch-not-utf8or
bump-leg-branch-not-utf8, with agit branch -mremedy (#849, #765). validate.yml, reading.gitmodules. Bothvalidate.ymltemplates,
the assembly root's and the family root's, read each submodule url by its
exact key, so a section namedmembers/My IRRSgives the url git gives,
where it left that repository out of the App token and listed it wrong
(#782, #741). Their three url-reading steps read.gitmodulesby git's
rules, as the doctor's NEXT lines do: a section git ignores, a section
with nopathand an unmerged file name no repository, and apathor
urlvalue beginning with-is passed over, that value alone, so an
earlier one stands.siblings.pyreads by the same rules but for the
unmerged file, which it still reads. A file git refuses whole fails the
names step and the fetch step with an::error::naming the cause,
whatever credential is set, where with no credential the run went green
(#790, #786).- The workflow-reference note. A project's
validate-pins.pynote
<repository>: N workflow @<sha> reference(s) agree with the gitlink
counts only the references at the gitlink, saysN of Mwhere some are
not, and is not printed where none is, so no reference is both a
pin-workflow-ref-mismatchfinding and one that agrees (#769, #739).
scaffold, setup, family and the shared readers
-
Paths, spelled.
family.py init'sfolderandlandingplan rows,
seven refusals offamily.pyandscaffold-project.py, and the
bump-leg.pyrefusals that name the root, name each path as
shown_pathdoes, so an ESC or a line break in a holder, a folder, a
lock, a local remote or a root no longer reaches the terminal from those
lines, nor prints a forged line there.init's closing NEXT STEPS block
and the scaffold'sWorking trees are atline still print a path as it
is (see the known limits) (#814, #766). -
Git Bash's quoting. Under Git Bash (
MSYSTEMset and not empty),
the lines the tools quote for the host are quoted forsh, not
PowerShell, through one question,host_shell_dialectin
repo_shape.py: the doctor's lines, setup's family lines and
family.py'sNEXT,switch -c,add,cdand
member-path-taken'sgit rm -r --cachedlines paste whole in the shell
that printed them, and the doctor gives Git Bashcpand
env PYTHONPATH=..., where it gaveCopy-Itemand$env:PYTHONPATH
(#821, #711). -
setup-project.py's prompt. A project name typed at the prompt is
checked as--projectis, before step (3) builds a path from it, so
../x,a/b,..and--helpare refused by name, exit 2, where
../xanda/bmet an--idhint,--helpa help page and..
naming-unclassifiedat step (4), and../xor an absolute name had a
probe folder made outside--into; an empty line says that no name was
typed (#756, #729). -
setup-project.py's refusals and GitHub's limit. A refused value is
told the first check it failed, in a sentence of its own (empty, a
leading-, a control character, or the pattern, in words and as the
pattern), where every refusal explained only a leading-. Where a
GitHub repository will be made, a project name over 95 characters is
refused before step (4), since<Project>-specand<Project>-codeadd
five characters to GitHub's 100, and under--local-remote-dirit is
not asked. The prompt takes the spaces off a typed name, soAtlasno
longer reaches step (4) with a space nobody can see (#811, #777). -
setup-project.py's children. Every Python child that setup starts is
told the encoding it is read in, so a cp1252 stdout gets one encoding
from setup, where a measured run printed 9 of its 129 stdout lines in
cp1252 among UTF-8 ones (#851, #836). -
GitHub's limits in the scaffold and setup. Where GitHub repositories
will be made, the scaffold's plan,--dry-runalike, refuses a name over
100 characters (scaffold-name-too-long), an--orgover 39
(scaffold-org-too-long) and an id whosexf-project-<id>topic is over
50 (scaffold-topic-too-long, naming an id derived from--projectas
derived), before any repository is made, where a--projectof 40
characters made threegh repo createcalls and then failed at
--add-topic.--helpstates each bound. Setup's step (5) is that plan,
so setup refuses the same before step (6) asks for the yes; under
--local-remote-dirnone is asked (#859, #838). An--orgGitHub gives
no organisation isscaffold-bad-org(#871, #863). -
family.py init and GitHub's limits.
initasks GitHub's limits on
the topic, the--organd the holder's name before anyghcall, where
GitHub is asked, and--dry-runrefuses the same:
family-name-too-long,family-org-too-long,family-bad-organd
family-topic-too-long. It set the topic last, aftergh repo create
and the push, so a family of 40 characters made and pushed its holder,
then failed at the topic (#871, #863). -
setup-project.py's rehearsal. A run passed
-- --dry-run, or
-- --dry, makes none of the preflight's three offers, an install,
gh auth loginandgh auth setup-git: each is markednot offered,
and the plan's last line says a run without--dry-runmakes them,
where a typedyesletgh auth setup-gitwrite a credential helper into
the person's global git config, orgh auth loginsignghin.
--preflightstill makes every offer, and a run with no terminal prints
what it printed.--shape-refis held to git's rule part by part: no part
empty, none starting with.or ending in.lock, no..and no.at
the end, soa.lock/bis refused before the clone, where it cloned the
standard and then failed in git, exit 1 (#873, #857). -
family.py in a reftable holder.
add,bumpandremoverefuse a
cherry-pick or a revert stopped in a holder whose refs are a reftable,
asking its ref store forCHERRY_PICK_HEADandREVERT_HEADby their
exact names, where they went on to their commit. With #816 in v1.1.0,
that closes #684 (#846, #684). -
family.py add's move back. The move back that
addprints for a
member's gitlink under another spelling of its row's path points the
member's section of.gitmodulesback too, sets aside first a file or a
link the index holds in its way, and puts back a member whose removal is
staged. Its closingmvis guarded, moves nothing where nothing is
there, never moves a checkout and leaves one to delete by hand; the
update comes last; and--sparseis printed where the sparse rules leave
an entry out, not only where its bit marks it. So, in the states it
names, no printed line leaves a section naming another spelling, or
un-registers a member (#828, #761). -
family.py add, the states #840 names. Where
addrefuses a member
recorded in another case, it names first, with a remedy that types as
printed:- a section of
.gitmodulesthat gives a case variant of a row's path
beside the member's gitlink: the section named after the row is
pointed back where no section gives the row's path, and any other is
taken out with the gate's own--unset-alllines; - a second gitlink at such a variant, taken out of the index with the
gate's owngit rm -r --cached, with--sparseand-fwhere git
needs them, its checkout named to delete by hand before the update; - a case variant of a row's path under
members/itself, whereadd
went on; - a checkout inside a directory that a
git mvor anmvmoves, named
to delete by hand, where the move left it with a.gitfile that
points nowhere; - a symbolic link the index holds entries under, moved out first;
- an unmerged entry at or under a row's path, set aside, where it kept
#574's remedy and the commit failed; - a
.gitmodulesthat is unmerged, deleted or left untracked, asked
first.
Its closing guard takes
validate-family.py's form (#876), so a link at
the variant is moved, the link and never what it points at, where it was
kept (#865, #840). - a section of
-
validate-family.py's remedies. The wrong-case move back mends
.gitmodules, adds--sparsewhere a sparse checkout needs it, and moves
aside first an entry in its way. A row's staged removal is a finding
before the commit that makes it, a section with no url is
member-gitmodules-url-absent,member-uninitializednames the gitlink
where git's init stops and the cure for it, and a pin whose commit the
member lacks names the fetch, or thefamily.py bump, that cures it
(#820, #762). -
A
.gitmodulesvalue git dies on. Where.gitmodulesgives a value
that git dies on as it reads the file (apath,url,update,
ignoreorbranchkey with no value,update = None, ashallowthat
is no boolean, and more), a holder'smake bootstrapfails, naming the
key, where it saidfamily bootstrap ok, andvalidate-family.py
reportsgitmodules-value-refusedin every mode, with the line that
takes the key out. Where the work tree has no.gitmodules, bootstrap
reads the index's, then HEAD's, as git does (#819, #768). -
A
.gitmodulescopy with a line git cannot parse. Where the index's
or HEAD's.gitmodulesholds such a line, a holder'smake bootstrap
reads the sections before it, as git's submodule reader does, where it
read none and a run with no credential failed wrongly. A row the read
did not reach is told to put the file back, mend the line or the key it
names, and commit it:git checkout --ignore-skip-worktree-bits -- .gitmodulesfor the index's copy, which keeps what was staged, and
git checkout HEAD -- .gitmodulesfor HEAD's. A parity test holds the
value rules that bootstrap andvalidate-family.pyeach write to one
answer (#877, #856). -
make siblingsand a slot that is not a clone. A file, a folder that
make siblingscannot list or enter, or a link to nothing at
../<Project>is NOT A CLONE, reported and left as it is, and the members
after it are placed, where the run ended in a traceback, or the dry run
saidwould clonewhere the real run endedCLONE FAILED. On Python
3.12 and later a junction to nothing there is NOT A CLONE too.make park
andmake resumeskip such a member asSKIPPED no clone, and the
doctor'smembersrow calls each such slotnot-a-clone, as
make siblingsdoes (#772, #735; #788, #784; #794, #793). -
A
members:that is not a list. A holder'smembers:that is not a
list, or that holds an entry that is not a mapping, is refused in its
validate-family.py's words,family-members-unreadable, exit 2, by
family.py add,bumpandremove, their dry runs included, before
they fetch, mount or rewrite anything, where they ended in aTypeError,
saidmember-unknownof a member mounted there, or committed a
family.yamlwithout the entry; and by the holder'smake bootstrap,
make siblingsandfamily.py siblings, where they ended in a
TypeError, read no members (0 member(s), andmake bootstrap's
family bootstrap ok), or counted the rows beside the entry and said
nothing of it.validate-family.py --no-membersfinds such an entry,
member-row, exit 1, where it counted it as a member and said
family ok. An absent or nullmembers:, or[], is still a family
with no members;0,falseand''are now refused (#870, #864). -
A holder's
make bootstrap, on what failed. TheSHAPE_LEGS_TOKEN
advice is no longer printed where git fetched nothing: where its init
stopped at a gitlink with no url, or.gitmoduleswas unmerged, gave a
value git dies on or could not be parsed. There it stood above "not for
want of a credential". Anywhere else a fetch failed it stands, and beside
a denial it is for the members the denial does not name. The closing
FAILED line takes the code from make's ownError <code>line, for a
member'smake bootstrapandmake validatealike, so a member's
finding readsthe member's command exited 1, its refusal
the member's command exited 2, and make's own failure says make printed
no such line for a member's command, where all three read
make bootstrap exited 2(#876, #839). -
A member's make, read a line at a time. A holder's
make bootstrap
reads each member's make on stderr a line at a time and names the
innermost command's code: a member whosebootstraptarget runs another
make readsthe member's command exited 1for an inner finding, where it
readexited 2, an inner make's own failure is said as make's own, and a
make -krun with more than 4 KiB on stderr after itsErrorline keeps
that line, where the run said make printed none.run_make's docstring
says what passing make's stderr on changes for a member (#886, #884). -
validate-family.py's lines, typed as printed. A
git rm --cachedof
a staged gitlink whose checkout has moved on, or holds a file git does
not track, takes-f, which git otherwise refuses, in the finding for a
second path to a member and in the wrong-case move. The wrong-case move's
guardedmvmoves nothing where nothing is at the variant, where it
exited 1, and moves a symbolic link there wherever it points, the link
and never what it points at. Where the work tree's.gitmodulesis gone,
the firstmember-gitmodules-url-absentormember-gitmodules-absent
finding puts it back from where git reads it, and no later one does, so
the absent finding no longer makes a file holding its own section alone,
and a second finding no longer puts the file back over the url the first
wrote (#876, #839). -
A gitlink outside
members/.validate-family.pyasks about every
gitlink, not only those inmembers/. For a section at a gitlink outside
members/,gitmodules-path-unnamedprints that gitlink's
git rm --cachedand its commit before the section's own lines, and a
gitlink there that no section gives isgitlink-unnamed, with
bootstrap's cure. Each suchgit rm --cachedtakes-fwhere git
refuses it without, and comes aftergit add -- .gitmoduleswhere the
lines before it may leave that file unstaged. So the remedy typed as
printed no longer leaves a gitlink at which a plain clone's
git submodule update --initexits 128 while the gate passes (#882,
#878).
shape-doctor.py
.gitmodules, as git reads it. The doctor andmake siblingsread
only whatgit submodule initreads: a section whose name is empty or
holds a..component is skipped, apathorurlbeginning with-is
skipped, that key alone, a section with nopathnames no mount, and a
key with no value, or a value git dies on, gives no url where git refuses
the file. The doctor's NEXT lines also read no url from an unmerged
.gitmodules, whichmake siblings, and the doctor's ownmembersrow,
still read (see the known limits). So the doctor's NEXT lines name no url
where git saysNo url foundor refuses the file (#775, #733).- Section names, as git's bytes. A
.gitmodulessection name keeps
git's own bytes and is handed back to git as them, so, off Windows, the
doctor finds the url the root's config keeps and the kept git directory
of a member namedcafand the byte 0xE9, gives its detached commit a
keep line under any locale, and no longer ends--rootin a
UnicodeEncodeErrortraceback on a name that is not ASCII under
LC_ALL=C PYTHONUTF8=0(#774, #732). - Paths that are not ASCII. The doctor keeps each character that is not
ASCII where the report's encoding can show it, where it printed every one
as?, so a NEXT or keep line naming a membercaféruns as printed.
Where the report cannot show a character, a command's value is escaped
whole ($'...'forsh, a[char]concatenation for PowerShell), and
other text, a WHY sentence's among it, still shows?.--jsonis
judged as UTF-8, whatever stdout holds, and written as ASCII, each such
character a\uescape its reader decodes (#792, #785). - Conflicted shape pins. A conflicted shape pin's NEXT takes the side
whose pin reads, and names no line where no side's pin reads, so running
it no longer leaves the doctor naming the same NEXT again (#779, #752). A
shape copy renamed on one side of a merge, conflicted under a name no pin
lists, is named in thewhyand inleft_for_a_personas renamed from
that copy, for a person to settle, and never taken (#781, #753). A copy
renamed on both sides,DDat its old name, is named gone on both sides,
with each side's new name where that side's own commit proves it, and
never as a merge of hunks git did not merge (#789, #783). - A partial clone of the standard. The partial-clone row's NEXT fetches
by id each commit the clone lacks objects at,
git -C <shape> fetch --refetch --no-filter <remote> <commit>..., from a
promisor remote whose branches hold it, one remote to a run, or from the
first promisor remote where none does, so it no longer loops where the
pinned commit sits only on another remote or another branch. Itswhy
says where each commit is, or that the clone does not hold it at all, and
from git 2.45 a commit the clone does not hold is no longer counted as
one object lacked (#773, #754). - A root that lost
repo_shape.py. Thelegsandmembersrows'
lockstep NEXT is thegit -C <root> checkout HEAD -- scripts/repo_shape.py
that thepinsandfamilyrows print, or none where HEAD no longer
holds the file, where it named the root's own gate, which dies on the
import (#771, #750). - A pin this user cannot read. The
legs,manifest kindsand
shape currencyrows name a pin or acontracts/this user cannot read
invalidate-pins.py's words,cannot be read (Permission denied), or
contracts: cannot be listed (Permission denied)for a folder it cannot
list, with its advice, and nevergit checkout HEAD --, which discards
what the file holds. The verdict isINVALID, notDRIFTED(#860,
#847). - A
legs:that is not a list (#862, #832), above: thelegs,
leg shape filesandplacementrows aren/aand wait on the
manifestrow, and--placement-planrefuses by name and writes
nothing. - A slot that is not a clone (#788, #784; #794, #793), above: the
membersrow calls a link to nothing, or on Python 3.12 and later a
junction to nothing,not-a-clone, where it called the slotabsent. - A
members:that is not a list (#870, #864), above: themembers
row isn/aand waits on thefamilyrow, wheremembers: 5ended the
whole report in aTypeErrorand other values read asno members yet,
or as members mounted at their pins beside an entry5. - No lazy fetch. The doctor runs under
GIT_NO_LAZY_FETCH=1, so every
git it starts, and every git thatupdate-shape.py's reads and the
validators it runs start, fails a read of an object a partial clone
lacks rather than fetching it. git 2.45 and later reads the variable, as
do the security releases of 2024-05-14 back to 2.39.4. No printed line
carries it, so a pasted fetch still fetches. Where a pin that does not
read is put back from history a partial clone of the root lacks, the
shape currencyandmanifest kindsrows' NEXT is
git -C <root> fetch --refetch --no-filter <first promisor remote>, and
theirwhyspells the same fetch from each other promisor remote, where
the doctor fetched as it walked the pin's history (#791, #787). - The
agent-filesrow's gate. Theagent-filesrow's WHY for a
deleted pinned copy ends by naming the root kind's own gate to confirm the
digest,validate-pins.pyin an assembly root andvalidate-family.pyin
a holder, which has novalidate-pins.py, or the standard's copy of it
where the root has none. In a root that lostscripts/repo_shape.pyit
puts that file back first, or, where HEAD no longer holds it, says why no
command does. It named the root's ownvalidate-pins.pyeverywhere, which
exits 1 withModuleNotFoundErrorin such a root. In an assembly root
that keeps its gate andrepo_shape.pythe WHY is unchanged (#780,
#776). the way in, beside a policy that does not load. In a directory that
is not a shape root, a fault in the doctor's own checkout's
contracts/repository-naming.yamlmakesthe way in, and in a
repositorythe other way in, ann/aenvironment row: it quotes the
fault, says the file is that checkout's and not this directory's, and
prints no NEXT. A tab line gaveFINDING the way in refused, and a
pattern that does not compile ended the doctor in a traceback, exit 1. A
policy that loads and then raises when asked "cannot be used". A leg
clone whose ownAGENTS.mdsays so keeps its leg row and its Triad row
over each such fault. Over a tab line its leg row was
FINDING the way in refused, and over a pattern that does not compile,
or a policy that raised on its name, the doctor ended in a traceback,
exit 1. A leg known only by its name is read through that policy, so
over a tab line or a pattern that does not compile it gets a
repository's twon/arows and a Triad row that calls it a single
repository. The verdict is NOT A SHAPE ROOT, exit 2. The doctor's EXIT
CODES list says what INVALID andmanifest-kindsname (#778, #707).
Install, CI and the docs
$OPENREPOSHAPE_BIN_DIRand control characters.install.shand
the bashopenRepoShape --installrefuse a$OPENREPOSHAPE_BIN_DIR
holding a control character, a tab, DEL and a C1 control included, before
they name it, exit 2, andinstall.shrefuses one holding:too;
install.shrefuses each before any request.--installused to place
the command under such a directory and print a carriage return or an
escape as typed (#804, #760).- Characters a terminal draws as another.
install.shand the bash
openRepoShaperefuse a$OPENREPOSHAPE_BIN_DIR,install.sha
repository, a version or an option, and the command a repository or a
ref, that holds one of 41 code points,MISDRAWN_CHARACTERSin the
launcher: U+00AD, the twelve bidi controls, U+2028, U+2029, and the
format characters of no width that nothing needs, U+2065 among them,
which Unicode leaves unassigned. Each is refused by its code point,
before any request, and never echoed, where~/U+202E.local/bin
could read as another directory. The newest release's tag, which
install.shreads from the redirect, or fromgh apiwhere the redirect
names none, is refused the same way once that request has answered, and
a redirect's location holding one is left unnamed in the message that
says it names no release. U+200C and U+200D stay allowed (#841, #837). - A name in a multibyte locale. Where the bytes refuse a
$OPENREPOSHAPE_BIN_DIR, both files read it again in the locale's own
charset, aslocale charmapnames it, throughiconv, so a GB18030, GBK
or Shift_JIS name that holds no control character and nothing drawn as
another is placed, a default$HOME/.local/binunder such a user name
among them. Where nothing can read it, the bytes decide as before (#867,
#858). $OPENREPOSHAPE_REPOand$OPENREPOSHAPE_REF. The bash command
checks both once, before--versionanswers and before any fetch, so a
control character or a character a terminal draws as another in either
is refused, exit 2, and never printed or sent, where--versionprinted
both as typed.--helpstill answers first, and names the variables,
never their values (#872, #866).- A ref git refuses part by part.
setup.shandinstall.shhold a
ref to git's rule part by part, assetup-project.pydoes (#873,
#857), and so does the bashopenRepoShapewhere it records a ref,
reads its record, or checks one out for--doctor: a part after a/
that starts with., a part before a/that ends in.lock, an empty
part, a/at the end and a name that ends in.are refused, beside
..and the.lockending as before, and each refusal states the whole
rule. Sosetup.sh --shape-ref a.lock/bno longer clones the standard and
ends in git'spathspec 'a.lock/b' did not match,install.sh a.lock/b
no longer asks the network for the launcher there, and
openRepoShape --installwithOPENREPOSHAPE_RECORD_REF=1no longer
writes such a ref to the record every later fetch reads (#875, #874). - CI on a release branch.
tests.ymlruns on a push to a
release/v<major>.<minor>branch that holds this change as on a push to
main, so a patch's commit there gets its own push run with no dispatch.
Its concurrency group includes the ref, so a fast-forward of that branch
to main's commit no longer cancels main's run on it.release.yml's
comments say how a patch is cut fromrelease/v<X.Y>and how a fix is
back-ported; its YAML is unchanged.release/v1.0does not hold the
change and still needs a dispatch (#852, part of #850). - Tests. Tests append what they tell git to the
GIT_CONFIG_COUNT
entries the environment holds, throughappended_to_command_scope, so a
pasted line keeps the session's maintenance settings and the Windows
job's two entries. A scan test,
test_no_test_sets_a_git_config_count_of_its_ownin
tests/test_no_auto_maintenance.py, fails where a test sets a count of
its own, but for the filesOWN_COUNTSlists, which build one on purpose
(#818, #758; #848, #835). The doctor tests'paste()gives a pasted
git submodule updatetheinsteadOfrewrite, so it reads a test's bare
remote through git's transport (#848, #835). The quoting-sentence
inventory sees a quoter handed on as a value, and the launcher guard
readsrun_childandrun([PYTHON, ...]), with rows for
src/openreposhape/cli.pyandsetup-project.py(#808, #759). Four
tests,directory_namedand its file twin ask one helper whether a name
that is not UTF-8 can be made, and skip on its two answers only, so a
full disk or a folder nobody can write in fails them, where they read as
SKIPPED; three tests skip where an ASCII file-name encoding cannot carry
their text (#806, #757). Three more tests ask that helper before they
make their own name,make_fileskips on EILSEQ or aUnicodeError
only, and the U+202E directory oftest_adopt_template_values.pyhas no
guard, so a full disk or a read-onlytmp_pathfails those tests, where
they read as SKIPPED (#845, #834). Six skips on a failed git call ask the
system first, whether the file-name encoding can spell the name and, for
a name that is not UTF-8, whether the disk can hold it, so a failed git
call fails the test; the U+202E tests, and six template-value tests,
skip where a C locale's encoding cannot spell their text, where they
raisedUnicodeEncodeError(#869, #853, #854).
Thepinsrow's merged-away check is tested wherecheckrefuses the
standard, and beside aboth, anupstream-removedand anunmapped
row (#770, #755). None of these changes a shipped file. - The docs. README.md and the handbook say that
family.py init's
--intorefuses its default inside a checkout of the standard
(family-landing-in-standard), and describemake siblingsas it stops
before cloning; one sentence in five places, AGENTS.md and the holder's
README andAGENTS-shape.mdamong them, says that a holder keeps
members/in one spelling (#738, #695). That closes a known limit
v1.1.0 named. The holder's README andAGENTS-shape.mdsay its parent
folder is named EXACTLY after its repository, case included, and
AGENTS.md andAGENTS-shape.mdsay the validator may name a hand repair
of amembers:row, whose commit is then the person's (#822, #763).
README.md and the handbook also say that Git Bash is handedsh's
quoting (#821, #711), that a path the doctor cannot print as itself is
written$'...', which dash does not run (#792, #785), and that a
.gitmodulesgit refuses fails CI whatever the credential (#790, #786).
Known limits
v1.1.0's known limits that this release does not
close still hold; this list is not all of them. Of the two that work on
v1.1.0 named and left open, this release closes the README's and the
handbook's --into sentence (#738, #695). The other,
bump-leg.py --dry-run exiting 0 over a pin its own validators refuse, is
unchanged but for one case: a legs: entry that is not a mapping is now
refused by the dry run as by the real run (#862, #832). And #457's L1-17,
which v1.1.0 names, is narrower: where the source has lost its origin and
the plan records its repository, execute now compares the plan with that
record before it reads adopt_branch:, so an org: that differs from it,
as one outside the alphabet does unless it casefolds to the recorded
owner, is plan-not-the-source there first (#825, #716).
New in v1.1.1, from the pull requests that landed it:
- A project or a holder keeps v1.1.0's copies until
update-shape.py apply
re-pins it, and until then its own gates andmake bootstrapbehave as
v1.1.0's: for example, they read atracking_branch:ending in U+00A0 as
the branch without it (#823, #715). - The pipx command, the
openRepoShapethatpipx install openreposhape
places, is unchanged since v1.1.0, and none of the bash command's new
checks is in it. Measured on the package at mainc5359df: its
--versionprints a$OPENREPOSHAPE_REPOholding U+202E as it is, exit
0, and it refuses a$OPENREPOSHAPE_REFholding one by its pattern, exit
2, with the character as it is in the refusal. A control character in
either is refused, as in v1.1.0 (#837, #866). Nor does it hold a ref to
git's rule part by part: itsref_faultrefuses only..and a.lock
ending (#875, #874). - The bash
openRepoShapeholds a ref to git's rule part by part only
where it records a ref, reads its record, or checks one out for
--doctor. WithOPENREPOSHAPE_REF=a.lock/bset for one run, a
scaffold still asks GitHub forsetup.shat that ref, and--version
exits 0 (#875, #874). - Under the
Cor POSIX locale, a$OPENREPOSHAPE_REPOURL or path, or a
ref, with a letter past ASCII, such as/srv/café/openRepoShape.git, is
refused as "a control character", exit 2, by the bashopenRepoShapeand
bysetup.sh's two guards;install.sh's three checks, which refuse
such a value in both locales, give the same words underC. It is a
regression from v1.1.0: #872 moved the[![:print:]]check up front, and
underCit matches each byte past ASCII, so--version,--install
from a file,--doctor <dir>beside a checkout and--preflightthrough
asetup.shin a checkout now refuse a repository that v1.1.0 ran. Only
a mirror at such a URL or path, in a shell atCor POSIX (cron, a CI
step, a minimal container), meets it; an owner/name never does. It
refuses and does nothing else, and a UTF-8 locale such asC.UTF-8takes
the URL or path. It is tracked as #940, for v1.2.0 (#872, #866). - A tracked name that the doctor sets aside before it walks a leg, one
holding a control character or a byte that is not UTF-8, still counts in
plan's andexecute's extension-majority fold. Soengine/holding
a.py,b.py,x.mdandt<TAB>b.pyfolds to code, 3 of 4, and the
doctor, which sees 2 of 3, readscode/engine/x.mdas MISPLACED, with the
plan exactly asplanwrote it. It is on #457's checklist as L1-104
(#829, #801). - A
--sourcedirectory this user cannot enter, or one under a directory
they cannot enter, still endsadopt-project.pyin aPermissionError
traceback, exit 1. It is on #457's checklist as L1-105 (#831, #796). - A
--pin-sourceinside a non-bare clone's git directory, such as
glass/.git, is refusedpin-source-not-a-clone, naming the git
directory, although its digest would have been the whole tree's. A git
that refuses a clone it finds (safe.directory, "dubious ownership") is
"git finds no clone there", followed by git's own reason (#842, #800). - A plan read at
--tracking-branch, because the source's default branch
had no local copy, records that branch assource.default_branch, and
goes stale once the default branch is made local:checkthen reads the
source at it, findsplan-stalewhere its commit is not the plan's, and
planis run again. The printedgit branch <b> origin/<b>can bring
back a branch the remote has since deleted, where the clone was fetched
without--prune(#830, #797). - Whether
--local-remote-dircan be written in is asked withos.access,
which tells root yes whatever a folder's mode says, and tells yes of any
folder on Windows; an ACL that says otherwise is met at
themkdirorgit init, after nothing, or after the spec leg, was
made. A private repository the token cannot see answers as not found, so
executekeeps its notice for what can fail after the first create. A
name that GitHub only redirects, after a rename or a transfer, is free,
andgh repo createtakes it over, which ends the redirect; neither
checknorexecutesays so.checkwithout--local-remote-dirneeds
ghfor a clean read; offline it passes, with a NOTE (#861, #798). adopt-work-dir-shareddoes not guard against another user who can
write, through a group, in the work directory, itssource-readonly/or
a directory above it, since group write is not asked; Windows, where
nothing is asked and the removal walks by path; root, as the other user;
this user's own other processes; or the default work directory, which is
not asked, and which only aTMPDIRevery user can write in without the
sticky bit would open (#843, #803).adopt-work-dir-sharedrefuses some--work-dirthat v1.1.0 used: one
that is, or is under, a directory a user other than this one or root
owns, a run as root over another user's directory included (such as a
host directory mounted intodocker runas root), or a directory every
user can write in without the sticky bit, such as a shared folder at
0777; and one on a drive mounted without ownership, which reads as 0777
(on macOS an exFAT, FAT or NTFS drive, on Linux ntfs-3g by default, and
under WSL a Windows drive). The way out is a--work-dirthis user owns
on a disk that keeps owners,chmod +ton such a directory that is this
user's, running as the directory's owner, or no--work-dir(#843,
#803).update-shape.py applyrefuses asupdate-rewrite-symlink, before any
write (#286), a root that keeps a symlinked directory above a pinned
copy, such as.github -> ci, so it never extends the line-ending block
with the tracked spelling; the link is replaced first, as the remedy says
(#844, #807).update-shape.py's missing-name refusal asks one hop: a validator that
reaches a line's importer through another copy is not found, so the
refusal can say the validators pass where one does meet the line. What
its readers cannot tell runs or not they read as running, so a copy can
be refused though Python starts it; a handler that binds and re-raises
is a line on #458 (#802, #675).- On Python 3.12 and 3.13, a project's
make bootstrapstill ends in a
PermissionErrortraceback over a leg directory nobody may enter (step
(a)) or agovernance/nobody may enter (step (c)): #815's body
measured it on Python 3.12, and 3.13 is read, not run. Beneath a
bootstrap REFUSEDline,setup-project.py's step (7) still says
bootstrap failed (see above), andsiblings.pythat the member's own
bootstrap exited 2 (#815, #764). update-shape.py apply --branch <new>, run from a branch whose name is
not UTF-8, is refused, exit 2, before anything is written, where v1.1.0
committed and pushed the new branch, or saidnothing to do;apply
without--branchruns as before, and the way out is onegit branch -m.
family.py's dry runs on such a branch print their plan and then refuse,
exit 2 (bump --dry-runhas fetched in the member by then), and no test
holds that refusal; their real runs refuse before anything is fetched or
written. A push line naming a branch that holds U+2028 or U+0085 names it
exactly, inside single quotes, so a viewer that breaks lines there shows
the line broken (#849, #765).- The commands a remedy prints for a person to paste, such as
family.py'sswitch -c,addandrm -flines, are quoted for a
shell, and still hold a name holding a control character raw inside the
quotes; the follow-ups are on #459 and #458.family.py init's closing
NEXT STEPS block andscaffold-project.py'sWorking trees are atline
still print a path as it is, so a line break in it prints a line of its
own there (#814, #766). - A path the doctor cannot print as itself is written
$'...'off Windows
and in Git Bash, which bash, zsh and ksh run and dash does not; README.md,
the handbook and AGENTS.md say so. Its PowerShell spelling is held as
text, and was not run in PowerShell (#792, #785). - A PowerShell started from inside Git Bash inherits
MSYSTEM, so it is
handed Git Bash's quoting; the handbook says so (#821, #711). - On Windows, a
.gitmodulessection name that is not UTF-8 is still not
found by the doctor (#774, #732). - On a git older than 2.45 without the security releases of 2024-05-14,
which ignoresGIT_NO_LAZY_FETCH, the doctor's reads of a partial clone
are held only by the guards each read had before (#791, #787). - Before Python 3.12, which has no
os.path.isjunction, a junction to
nothing at../<Project>still reads as an empty slot to
make siblingsand to the doctor. No Windows run was made: the tests
stand in for the junction (#794, #793). make siblingshas no unmerged check: with.gitmodulesunmerged, and
resolved in the work tree but not added, git saysNo url foundwhile
make siblingstakes the work tree's url, and so does the doctor's
membersrow, which reads the file asmake siblingsdoes. Where git
finds no url,make siblingsstill clones the member from
family.yaml'srepository:(#775, #733).- A holder's
make bootstrapreads each member's make through a pipe on
stderr, to read make'sError <code>line, asrun_make's docstring
says: a member's recipe has no terminal on stderr, its stdout and stderr
lines can come out of order, a process a command leaves running with
stderr open holds the run until it closes it or exits, and on Windows a
member's text that is not ASCII comes out garbled (read, not run). The
run's last line names the innermost command's code only as far as
make's lines tell it: in the nested-make casesMakeErrors' docstring
lists, such as a recipe that lets an inner make fail and then exits 2
itself, it names another command's code, or none; and a command a
signal ends under dash,/bin/shon Debian and Ubuntu, reads as exiting
128 and the signal's number, 137 for SIGKILL. Make before 4.0 was not
run (#876, #839; #886, #884). - Where the work tree's
.gitmodulesis gone,validate-family.py's
wrong-case move writes the file with one key, and typed as printed it
commits a.gitmodulesthat holds that key alone. That line, and
member-gitlink-removal-staged'sgit checkout HEAD -- .gitmodules, are
not ordered with the url-absent and absent findings that put the file
back (#876, #839; #879). - In the same state, with every member checked out and a line git cannot
parse after the first section of HEAD's.gitmodules, git's update exits
128 while a holder'smake bootstrapprints theSHAPE_LEGS_TOKENadvice
andfamily bootstrap ok(#876, #839; #879). - Beside a committed value git refuses, a stray gitlink in
members/
under another spelling of a row's path is still told agit rm --cached
that exits 128 after the value finding's lines
(please stage your changes to .gitmodules),-for not: #882 stages the
file first only for a gitlink outsidemembers/(#882, #878; #879). - After the lines for a member moved by
git mvto another spelling, the
checkout stays at that spelling, untracked: the gate passes,family.py addthen refuses it asfamily-members-dir-variant, and no finding says
what the directory is or that its commits are in the member's store
(#876, #839; #879). - A
pathkey with no value in HEAD's.gitmodulesis reported by
validate-family.pytwice, asgitmodules-value-refusedand as
member-gitmodules-absent; both lines write the same path back, so
typing both is harmless (#819, #768). validate-family.py's reading of wheregit submodule initstops is
simpler than a holder bootstrap's: for a section name with a..
component, a url beginning with-or an unmerged.gitmodules, its
refusal keeps the plain update, which dies. Itsgit rm --cachedline
for a stray gitlink that a sparse checkout keeps off disk has no
--sparse, so it exits 1, with git's own hint (#820, #762).family.py add's case-spelling remedies leave three states unruled. A
second gitlink at a case variant of a row's path, where the row's path
holds index entries under it rather than a gitlink, keeps #574's
remedy. An unmerged entry the sparse rules leave out may need
--sparse, which is not printed (not measured). And the move back of a
member whose gitlink is at a variant points back every section that
gives a variant of its path, so one that is another submodule's would be
pointed back too and give the path twice (read from the code, not
measured) (#865, #840).- GitHub's limits are asked only where a GitHub repository will be made,
and none was measured against GitHub: a repository name's 100 and a
topic's 50 are from GitHub's documentation, and an organisation's 39 is
its documented limit on a user's name, since it documents none for an
organisation (#859, #838). The characters an--orgmay hold are from
its documentation too, and_is let by, since a GitHub Enterprise
Managed User's login holds one, so an organisation's name holding_is
let by too, though GitHub gives no organisation one (#871, #863). A leg
the plan was edited to name, over a limit, is told
"Use a shorter --project", which does not shorten it (#859, #838). - In
setup-project.py,--project 'Atlas ', given as an argument, still
reaches step (4) with a space nobody can see, and under
--local-remote-dira name of 247 to 255 characters prints step (4)
before step (5) refuses it (#811, #777). A refused name ends the run,
with no second prompt (#756, #729). - A lone
0x9Bbyte in$OPENREPOSHAPE_BIN_DIRis placed: a terminal that
reads UTF-8 draws it as a character it cannot read, and only one set to
an 8-bit charset acts on it (#867, #858). - A multibyte locale that is not UTF-8, left in a terminal that reads UTF-8,
is handed the C1 bytes that the locale reads as letters, and that terminal
acts on them. No GB18030, GBK or Shift_JIS locale was built: the tests
stand in alocalethat answerslocale charmapas glibc does (#867,
#858). - Windows and macOS: CI runs on Linux only (#597), but for the launcher's
andinstall.sh's suites, which it also runs on Windows in Git Bash. A
disk that folds case (#820, #828), Git Bash's and PowerShell's quoting
(#821, #792) and a Windows disk refusing a name (#806) are emulated in the
tests, not run there. No CI job runs the bashopenRepoShapeor
install.shon macOS, nor under its bash 3.2:release.yml's macOS leg
runs only the pipx wheel's commands. Their control-character match on
$OPENREPOSHAPE_BIN_DIRand their search for the 41 code points read
bytes underLC_ALL=C(#804, #760; #841, #837); the[![:print:]]
checks of a repository, a ref, a version and an option, and the
multibyte re-read throughlocale charmapandiconv(#867, #858), use
the person's locale. - The tests' scan for a
GIT_CONFIG_COUNTset by hand does not read a key
name imported from another module, a value built byformat(),
repr()oros.fsdecode(), a conditional value,setenvwith
keywords, an annotated subscript, a value held in a variable and set
later, or an indexedGIT_CONFIG_KEY_<n>name; not a product bug (#848,
#835).
Full list
- Merged pull requests: https://github.com/opensoft/openRepoShape/pulls?q=is%3Apr+is%3Amerged
- The
v1.1.1milestone: https://github.com/opensoft/openRepoShape/milestone/4 - Every commit since v1.1.0: v1.1.0...v1.1.1