Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
026280c
openRepoShape: `--install` records the ref it runs at in `openRepoSha…
Oct 14, 2026
d0cdbf4
install.sh: one line, `curl -fsSL https://github.com/opensoft/openRep…
Oct 14, 2026
319b005
openRepoShape and install.sh, the fix round on #577: `--install` reco…
Oct 15, 2026
322a305
README.md, docs/handbook.html, AGENTS.md and the v1.0.1 release notes…
Oct 15, 2026
868c9bf
docs/release-notes/v1.0.1.md: the "Before tagging" steps are safe to …
Oct 15, 2026
892a041
Merge remote-tracking branch 'origin/l3-564-one-line-install' into l3…
Oct 15, 2026
b2f0be3
tests/readme-cap.d/564.txt: README.md's cap is 1449, raised by 22 for…
Oct 15, 2026
68b999f
docs/handbook.html: the consent rules say every action the tool OFFER…
Oct 15, 2026
53416db
Merge remote-tracking branch 'origin/main' into l3-564-carry-to-main
Oct 15, 2026
a155945
Merge remote-tracking branch 'origin/main' into l3-564-carry-to-main
Oct 15, 2026
c1e6b82
README.md: the NOTE says the preflight installs nothing without the t…
Oct 15, 2026
5191f0e
docs/handbook.html: the consent rules say install.sh asks nothing bef…
Oct 15, 2026
25b2aee
Merge branch 'main' into l3-564-carry-to-main
brettheap Oct 15, 2026
3ca0852
install.sh: git-filter-repo is installed with the uv already there wh…
Oct 15, 2026
1a44805
README.md, docs/handbook.html: step 3 says install.sh installs git-fi…
Oct 15, 2026
274dc07
install.sh: every next step it prints pastes and runs as printed, so …
Oct 15, 2026
f03a192
docs/handbook.html: step 3 says "install.sh itself never runs sudo" a…
Oct 15, 2026
b23b245
Merge remote-tracking branch 'origin/main' into l3-564-carry-to-main
Oct 15, 2026
d5011b1
Merge branch 'main' into l3-564-carry-to-main
brettheap Oct 15, 2026
f9b07c9
Merge remote-tracking branch 'origin/main' into l3-564-carry-to-main
Oct 15, 2026
3d8fa05
Merge branch 'main' into l3-564-carry-to-main
brettheap Oct 15, 2026
09edc24
Merge #587's final head (3d8fa05e) into l3-595-installer-uv: docs/han…
Oct 15, 2026
a307230
Merge main (22eaceed, #587's squash) into l3-595-installer-uv: #587's…
Oct 15, 2026
9ccc29f
Merge main (55faad26) into l3-595-installer-uv: no conflict, and none…
Oct 15, 2026
eda2654
Merge main (fbbbc8d0) into l3-595-installer-uv: no conflict, and none…
Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/in
`install.sh` is attached to every release and installs the newest, with no `gh`
and no login. Its command is PINNED to the release tag, recorded in
`openRepoShape.ref` beside it: `setup.sh` and the standard are fetched at that
tag, not at main. It installs `git-filter-repo` with `pipx` where it can, or
prints the command, and ends with `openRepoShape --preflight`. `install.sh`
itself never runs `sudo`; the preflight it ends with offers what is missing,
each behind a typed `yes` at your terminal. `| bash -s -- v1.0.1` names a
release; the same line again upgrades.
tag, not at main. It installs `git-filter-repo` with `pipx`, or `uv`, where it
can, or prints the command, runs `openRepoShape --preflight`, and says last if
the machine is ready to adopt or only to scaffold. `install.sh` never runs
`sudo`; the preflight offers what is missing, each behind a typed `yes` at your
terminal. `| bash -s -- v1.0.1` names a release; the same line again upgrades.

By hand, the same command at main, which records no ref and follows main:

Expand Down
33 changes: 18 additions & 15 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@

<!-- reads README.md § preamble: blob ddadd23a162129f7e64fefce899105661440c05c -->

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

Expand All @@ -498,7 +498,8 @@ <h4>The fastest path &mdash; macOS, Linux, WSL2</h4>
openRepoShape &lt;Project&gt; --org &lt;your-org&gt;</code></pre></div>
<p class="sub">That first line installs ONE command into <code>~/.local/bin</code>: the
newest release, pinned to its tag, with <code>git-filter-repo</code> where <code>pipx</code>
can install it, and it ends with <code>openRepoShape --preflight</code>. The
or <code>uv</code> can install it, and it ends with <code>openRepoShape --preflight</code>
and whether the machine is ready to adopt or only to scaffold. The
estate verbs <code>park</code> and <code>resume</code>, for <a href="#park">carrying
in-flight work</a> to another workstation, are
<a href="https://github.com/opensoft/openRepoTools">opensoft/openRepoTools</a>&rsquo; and have a one-liner of
Expand Down Expand Up @@ -609,7 +610,7 @@ <h4>The one thing to read before electing it</h4>

<section id="start">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<p class="eyebrow">Setup</p>
<h2>Start here</h2>
Expand Down Expand Up @@ -780,11 +781,12 @@ <h3 data-platform="win">Fetch the flow, in the directory the new project should
installs the newest, with no <code>gh</code> and no login. Its command is PINNED to the
release tag, recorded in <code>openRepoShape.ref</code> beside it: <code>setup.sh</code>
and the standard are fetched at that tag, not at main. It installs
<code>git-filter-repo</code> with <code>pipx</code> where it can, or prints the command,
and ends with <code>openRepoShape --preflight</code>. <code>install.sh</code> itself never
runs <code>sudo</code>; the preflight it ends with offers what is missing, each behind a
typed <code>yes</code> at your terminal. <code>| bash -s -- v1.0.1</code> names a
release; the same line again upgrades.</p>
<code>git-filter-repo</code> with <code>pipx</code>, or <code>uv</code>, where it can, or
prints the command, runs <code>openRepoShape --preflight</code>, and says last if the
machine is ready to adopt or only to scaffold. <code>install.sh</code> itself never runs
<code>sudo</code>; the preflight offers what is missing, each behind a typed
<code>yes</code> at your terminal. <code>| bash -s -- v1.0.1</code> names a release; the
same line again upgrades.</p>
<p data-platform="linux mac" class="sub">By hand, the same command at main, which records no ref and follows main:</p>
<div data-platform="linux mac" class="code" data-lang="sh"><pre><code>gh api repos/opensoft/openRepoShape/contents/openRepoShape \
-H 'Accept: application/vnd.github.raw' | bash -s -- --install</code></pre></div>
Expand Down Expand Up @@ -969,8 +971,9 @@ <h4>Consent rules, on every platform</h4>
<li>Every action the tool offers that changes the machine or the account is a typed
<code>yes</code>, at the same prompt: <code>Type yes to continue:</code>. <code>install.sh</code>
asks nothing before its own two acts, because running it is your act: it places the
command and installs <code>git-filter-repo</code> with <code>pipx</code> where it can, and itself
never runs <code>sudo</code>. The preflight it ends with asks before each offer, as above.</li>
command and installs <code>git-filter-repo</code> with <code>pipx</code> or <code>uv</code>
where one is there, and itself never runs <code>sudo</code> or installs either of them. The
preflight it ends with asks before each offer, as above.</li>
<li><code>--yes</code> skips the scaffold question only. It does not answer an install
offer, <code>gh auth login</code> or <code>gh auth setup-git</code>.</li>
<li>With no terminal attached there is no offer at all.</li>
Expand Down Expand Up @@ -1042,7 +1045,7 @@ <h4>What you have afterwards &mdash; the same on every platform</h4>

<section id="partway">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<p class="eyebrow">Setup</p>
<h2>If it fails part-way</h2>
Expand Down Expand Up @@ -1077,7 +1080,7 @@ <h2>If it fails part-way</h2>

<section id="ci">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

Expand Down Expand Up @@ -1135,7 +1138,7 @@ <h2>Reading private legs in CI</h2>

<section id="legs">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

Expand Down Expand Up @@ -1371,7 +1374,7 @@ <h3>The InkRouter example</h3>

<section id="doublepin">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<!-- reads README.md § The double pin, and the lockstep invariant: blob 57fffa08ffad5476f8eae73dd3e8494a02e050c9 -->

Expand Down Expand Up @@ -1668,7 +1671,7 @@ <h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>

<section id="park">

<!-- reads README.md § Starting a project in a new organisation: blob d77a1e59fe4951aa2a6f96808725d0506d4ce357 -->
<!-- reads README.md § Starting a project in a new organisation: blob d1666fa6ce9176f71a0541d3c3b55d4bf6508d6a -->

<!-- reads README.md § Carrying in-flight work to another workstation: blob b84512ad39c09085dfe6bf7ea694017f5e28c1dd -->

Expand Down
157 changes: 116 additions & 41 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
# the command it places; the command then stays on it.
# 3. GIT-FILTER-REPO, which `adopt-project.py` needs and nothing else does:
# said when it is there; installed with `pipx` when pipx is there and it
# is not; otherwise the one command that installs it, printed.
# 4. THE INSTALLED COMMAND'S `--preflight`, LAST, and its verdict.
# is not, or with `uv` when uv is there and pipx is not (#595);
# otherwise the one command that installs it, printed.
# 4. THE INSTALLED COMMAND'S `--preflight`, LAST, and its verdict, then the
# two readinesses: `adoption:` and `done:` (#595).
#
# install.sh ITSELF NEVER RUNS sudo, AND INSTALLS NOTHING BUT THESE TWO: the
# command and, through pipx, git-filter-repo. The preflight it ends with
# command and, through the pipx or uv already there, git-filter-repo. It
# never installs pipx, uv or a system package. The preflight it ends with
# offers what is missing, each behind a typed `yes` at the person's own
# terminal, exactly as `openRepoShape --preflight` always has, and some of
# those offers are `sudo` commands: that is the preflight's, asked first.
Expand All @@ -41,6 +44,9 @@
# release found, a fetch that failed, or a file that is not the command.
# git-filter-repo never changes it: only adopting needs it, and
# `adopt-project.py` refuses with its own install command where it is missing.
# What it changes is the summary (#595): `adoption:` says whether it is there
# and, where it is not, the command to run next, and `done:` says the machine
# is ready only when it is, and "ready to scaffold, not yet to adopt" when not.
#
# EVERYTHING RUNS FROM `main`, CALLED ON THE LAST LINE, INSIDE BRACES. Under
# `curl … | bash` bash reads this script from the pipe as it goes, so a
Expand All @@ -60,6 +66,10 @@
#: installs is what the adoption suites ran against.
FILTER_REPO_PIN="git-filter-repo==2.47.0"

#: Where uv says how to install uv itself, named for a machine with neither
#: pipx nor uv and no package manager `filter_repo_command` knows (#595).
UV_INSTALL_PAGE="https://docs.astral.sh/uv/getting-started/installation/"

WORKDIR=""

#: What `latest_from_redirect` and `latest_from_gh` found, and when they did
Expand All @@ -68,6 +78,12 @@
FOUND=""
FAULT=""

#: What step 3 found, for the summary `main` ends with (#595): 1 when
#: `adopt-project.py` will find git-filter-repo, and what the `adoption:`
#: line says, which names the command to run next where it will not.
ADOPTION_READY=0
ADOPTION=""

say() { printf '%s\n' "$*"; }
die() { printf '\nREFUSED: %s\n' "$1" >&2; exit "${2:-2}"; }

Expand All @@ -81,15 +97,18 @@

With no <version> it installs the newest release. It fetches that release's
`openRepoShape`, installs it into ~/.local/bin pinned to the release tag,
installs git-filter-repo with pipx when pipx is there and git-filter-repo is
not (or prints the command that installs it), and ends with `openRepoShape
--preflight`. install.sh itself never runs sudo; the preflight it ends with
offers what is missing, each behind a typed `yes` at your terminal. Run the
same line again to upgrade.
installs git-filter-repo with pipx, or with uv where there is no pipx, when
git-filter-repo is not there (or prints the command that installs it), and
ends with `openRepoShape --preflight` and two lines: `adoption:`, whether
git-filter-repo is there, and `done:`. install.sh itself never runs sudo and
never installs pipx or uv; the preflight it ends with offers what is missing,
each behind a typed `yes` at your terminal. Run the same line again to
upgrade.

Exit 0: installed, pinned, and the preflight passed. 1: installed, but the
preflight did not pass, or the command does not stay on the release tag. 2:
refused, and nothing was installed.
refused, and nothing was installed. git-filter-repo, which only adopting
needs, never changes it.

$OPENREPOSHAPE_REPO owner/name to install from (default opensoft/openRepoShape)
$OPENREPOSHAPE_BIN_DIR where the command goes (default ~/.local/bin)
Expand Down Expand Up @@ -216,60 +235,106 @@
return 1
}

# THE ONE COMMAND THAT INSTALLS IT HERE, for a machine with no pipx, PRINTED
# AND NEVER RUN, in POSIX shell: Homebrew's where `brew` is; where `apt-get`
# is, pipx from apt and then git-filter-repo through it, because Debian 12
# and Ubuntu 23.04 on refuse `pip install --user` into the system Python
# (externally-managed-environment); and otherwise pip, under whichever Python
# name this machine has. Always at the version CI runs, except brew's.
# THE ONE COMMAND THAT INSTALLS IT HERE, for a machine with neither pipx nor
# uv, PRINTED AND NEVER RUN, in POSIX shell, and nothing but the command, so
# it pastes and runs as printed in any shell (#595, the review on #598: an
# interactive zsh hands a `# comment` to the command as arguments). Never
# `pip install --user`: Debian 12, Ubuntu 23.04 and Arch, among others, mark
# the system Python externally managed (PEP 668) and refuse it.
#
# * brew's, where `brew` is;
# * pipx from apt or dnf, which takes `sudo` and is the person's to type,
# then git-filter-repo through it at the version CI runs, then `pipx
# ensurepath`, which exits 0 whether or not it had a directory to add;
# * Arch's own package where `pacman` is, which lands on PATH in one step.
# pacman cannot pin a version: Arch's `extra` holds 2.47.0, CI's, as of
# 2026-10-15;
# * and with no package manager this knows, uv's, once uv itself is
# installed, which the caller says first: the one command here that
# needs a tool that is not on this machine.
filter_repo_command() {
local candidate
if command -v brew >/dev/null 2>&1; then
printf '%s' "brew install git-filter-repo"
return 0
elif command -v apt-get >/dev/null 2>&1; then
printf '%s' "sudo apt-get install -y pipx && pipx install $FILTER_REPO_PIN && pipx ensurepath"
elif command -v dnf >/dev/null 2>&1; then
printf '%s' "sudo dnf install -y pipx && pipx install $FILTER_REPO_PIN && pipx ensurepath"
elif command -v pacman >/dev/null 2>&1; then
printf '%s' "sudo pacman -S --needed git-filter-repo"
else
printf '%s' "uv tool install $FILTER_REPO_PIN"
fi
if command -v apt-get >/dev/null 2>&1; then
printf '%s' "sudo apt-get install -y pipx && pipx install $FILTER_REPO_PIN"
return 0
}

adoption_ready() {
ADOPTION_READY=1
ADOPTION="git-filter-repo is installed, which adopt-project.py needs beyond what the preflight checks."
}

# `$1` says why it is not ready, and `$2` what to do first, in the caller's
# own words: a command comes after `run: `, at the end of the line, or between
# backticks, so it pastes as printed.
adoption_not_ready() {
ADOPTION_READY=0
ADOPTION="NOT READY: adopt-project.py needs git-filter-repo, and $1. Before adopting a repository, $2"
}

# ONE INSTALL, WITH THE pipx OR uv ALREADY HERE, into an environment of its
# own and at the version CI runs. Installed is not yet found: either tool puts
# the command in a directory of its own (`~/.local/bin` by default) that this
# shell may not have on PATH, and `adopt-project.py` looks on PATH, so the
# install is checked by looking where it will. `$1` names the tool, `$2` is
# what puts its directory on PATH, and the rest is the install.
filter_repo_install() {
local tool="$1" ensure="$2" status=0
shift 2
say "git-filter-repo: not installed; installing it with $tool, because adopt-project.py needs it: $*"
"$@" || status=$?
if [ "$status" -ne 0 ]; then
say "git-filter-repo: $tool could not install it (exit $status). Before adopting a repository, install it: $*"
adoption_not_ready "$tool could not install it (exit $status)" "run: $*"
elif command -v git-filter-repo >/dev/null 2>&1; then
say "git-filter-repo: installed with $tool"
adoption_ready
else
say "git-filter-repo: installed with $tool, into a directory that is not on PATH yet: run \`$ensure\` and open a new shell"
adoption_not_ready "$tool installed it into a directory that is not on PATH yet" "run \`$ensure\`, then open a new shell"
fi
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1; then
printf '%s' "$candidate -m pip install --user $FILTER_REPO_PIN"
return 0
fi
done
printf '%s' "python3 -m pip install --user $FILTER_REPO_PIN"
}

filter_repo_step() {
local found status=0
local found how="run:"
if found="$(command -v git-filter-repo 2>/dev/null)" && [ -n "$found" ]; then
say "git-filter-repo: already installed ($found)"
adoption_ready
return 0
fi
if command -v git >/dev/null 2>&1 &&
git filter-repo --version >/dev/null 2>&1; then
say "git-filter-repo: already installed (git filter-repo --version answers)"
adoption_ready
return 0
fi
if command -v pipx >/dev/null 2>&1; then
say "git-filter-repo: not installed; installing it with pipx, because adopt-project.py needs it: pipx install $FILTER_REPO_PIN"
pipx install "$FILTER_REPO_PIN" || status=$?
if [ "$status" -ne 0 ]; then
say "git-filter-repo: pipx could not install it (exit $status). Before adopting a repository, install it: pipx install $FILTER_REPO_PIN"
elif command -v git-filter-repo >/dev/null 2>&1; then
say "git-filter-repo: installed with pipx"
else
say "git-filter-repo: installed with pipx, into a directory that is not on PATH yet: run \`pipx ensurepath\` and open a new shell"
fi
filter_repo_install pipx "pipx ensurepath" pipx install "$FILTER_REPO_PIN"
return 0
fi
if command -v uv >/dev/null 2>&1; then
filter_repo_install uv "uv tool update-shell" uv tool install "$FILTER_REPO_PIN"
return 0
fi
say "git-filter-repo: not installed, and there is no pipx to install it with. Only adopt-project.py needs it; before adopting a repository, run:"
say " $(filter_repo_command)"
found="$(filter_repo_command)"
case "$found" in

Check failure on line 327 in install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoShape&issues=AaFAfJ0KrKpiHhFp1iRW&open=AaFAfJ0KrKpiHhFp1iRW&pullRequest=598
"uv "*) how="install uv ($UV_INSTALL_PAGE), then run:" ;;
esac
say "git-filter-repo: not installed, and there is neither pipx nor uv to install it with. Only adopt-project.py needs it; before adopting a repository, $how"
say " $found"
adoption_not_ready "it is not installed" "$how $found"
}

main() {
local version="" asked launcher_url bin_dir target shown status=0 pinned=1
local machine="this machine is ready"

# NOTHING A CHILD READS IS THIS SCRIPT. Piped, stdin is the pipe the
# script came down; a terminal stays a terminal, which is where the
Expand Down Expand Up @@ -379,6 +444,16 @@
OPENREPOSHAPE_REF="$version" "${BASH:-bash}" "$target" --preflight || status=$?
fi

# THE TWO READINESSES, LAST (#595). The preflight checks what scaffolding
# and the doctor need, and its verdict is the exit status below.
# git-filter-repo is needed to adopt and for nothing else, so it never
# changes the exit status: `adoption:` says where it stands, with the
# command to run next where it is missing, and `done:` says "ready" only
# when it is there as well.
say "adoption: $ADOPTION"
[ "$ADOPTION_READY" -eq 1 ] ||

Check failure on line 454 in install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoShape&issues=AaFAWUrP9a5qKZxxMoZ3&open=AaFAWUrP9a5qKZxxMoZ3&pullRequest=598
machine="this machine is ready to scaffold, not yet to adopt"

# EXIT 1 IS THE PREFLIGHT'S "not ready", which names what is missing; any
# other status is a run that did not get that far (setup.sh could not be
# fetched, or there is no Python to run it), and its REFUSED line says so.
Expand All @@ -394,10 +469,10 @@
;;
esac
if [ "$pinned" -eq 0 ]; then
say "done: openRepoShape $version is installed at $target and this machine is ready, but the command does not stay on $version (see above)."
say "done: openRepoShape $version is installed at $target and $machine, but the command does not stay on $version (see above)."
exit 1
fi
say "done: openRepoShape $version is installed at $target, pinned to the release tag, and this machine is ready. Run the same line again to upgrade to the newest release."
say "done: openRepoShape $version is installed at $target, pinned to the release tag, and $machine. Run the same line again to upgrade to the newest release."
exit 0
}

Expand Down
Loading
Loading