Install without root, and keep the commit in the container #96

Merged
a73x merged 1 commit from worktree-agent-ac43519e3623ff416 into main 2026-08-13 15:06:25 +00:00
a73x commented 2026-08-13 15:01:13 +00:00 (Migrated from git.a73x.sh)

Fixes 4876fba0 and 7156e09a — two packaging defects that both make the
documented path the wrong one.

4876fba0 — make install needed root

PREFIX now defaults to $HOME/.local. sudo make install PREFIX=/usr/local
is still the system-wide install, and the README documents the command that
works with the system one as a named variant rather than the default.

uninstall now removes the man pages as well as the binaries: git-collab.1
and git-collab-*.1 under the same prefix, which is the whole set install
writes, plus pages an older version installed for subcommands that no longer
exist — precisely the litter worth clearing.

Verified against a throwaway prefix:

$ make install PREFIX=$TMP
install -Dm755 target/release/git-collab $TMP/bin/git-collab
install -Dm755 target/release/git-collab-server $TMP/bin/git-collab-server
installed man pages to $TMP/share/man/man1
$ find $TMP -type f | wc -l
65
$ make uninstall PREFIX=$TMP
$ find $TMP -type f | wc -l
0

Only empty directories are left behind; nothing removes $PREFIX/bin itself,
which would be an unfriendly thing to do to a directory that was there first.

Man pages no longer gate the install. They were a prerequisite of the
install target, so a prefix with an unwritable share/man aborted before a
single binary was written — the convenience blocking the thing being installed.
The binaries go in first now and the man pages are attempted after, warning and
exiting 0 if they cannot be written:

$ make install PREFIX=$RO      # $RO/share is mode a-w
install -Dm755 target/release/git-collab $RO/bin/git-collab
install -Dm755 target/release/git-collab-server $RO/bin/git-collab-server
warning: could not write $RO/share/man/man1 — binaries are installed, man pages are not
$ echo $?
0

make install-man on its own stays strict. The asymmetry is deliberate: asking
for man pages explicitly and silently not getting them is the worse failure,
while asking for an install and not getting the binaries over a man directory is
the one that leaves you with nothing.

7156e09a — container lost --version provenance

The commit is passed in rather than the repository shipped: --build-arg GIT_COMMIT, which make docker fills from git rev-parse HEAD, plus a
GIT_DIRTY argument so an image built from a work-in-progress tree says so
instead of claiming to be the commit. TAG now defaults to the short commit
from the same git rev-parse, so the tag and the stamp cannot disagree.
.dockerignore still excludes .git/, and I confirmed by listing /build in
the builder stage that nothing git-related reaches the image.

$ podman run --rm registry.a73x.sh/git-collab-server:236a3a8 --version
git-collab-server 0.1.0 (236a3a876f607100748819039685778cf3f7b569)
$ git rev-parse HEAD
236a3a876f607100748819039685778cf3f7b569

and the degraded path, built with no arguments at all:

$ make docker GIT_COMMIT= TAG=noprovenance
$ podman run --rm registry.a73x.sh/git-collab-server:noprovenance --version
git-collab-server 0.1.0

A passed commit overrides a discovered one and bypasses the same-checkout guard
from 5519937d rather than being filtered by it. The guard exists because
discovery can pick up a stranger's repository when this crate is vendored into
another tree, and nobody chose that; passing a value is a choice made by whoever
is in a position to know. The price of that trust is validation: an unexpanded
GIT_COMMIT=HEAD fails the build loudly rather than quietly producing the
untraceable binary the mechanism exists to prevent. A discovered value that
looks wrong still degrades to silence, so the tarball case is unaffected.

The bug the container found

The first image I built reported the commit it was given, and -dirty, from a
spotlessly clean tree. ENV FOO=$BAR with an unset ARG BAR does not leave
FOO unset — it sets it to the empty string — and option_env! reads the
compiler's whole environment, not only what the build script emitted. So both
variables arrived as Some("") however little the build had been told, which
would also have rendered an argument-free container as 0.1.0 (). src/cli.rs
now asks what a variable says rather than whether it exists.

Every unit test passed while that was broken. Worth recording, because the fix
for a provenance bug that only appears in a container is not provable from
outside one.

Tests

tests/version_test.rs covers the resolution rules directly — passed beats
discovered, empty is not a decision, malformed-passed errors, malformed-
discovered degrades — and builds a throwaway crate whose build script is
src/build_provenance.rs to check what a real cargo build emits with a commit
passed, with neither a commit nor a repository, and with the variables set but
empty. Those are the paths a binary built from this checkout can never exercise
itself. The decision logic moved into src/build_provenance.rs, included by
build.rs the way src/cli.rs already is, so it could be tested at all.

Full suite green (53 suites, 0 failures); cargo clippy --all-targets silent.
tests/tui_review_test.rs run 11 times across the change with no failures and
no meaningful variance (2.84–2.94s).


Migrated from git-collab patche b7480bbbd59fa81b7cb3d4d5d37dc0971cda95b2.

Original author: a73x dev@a73x.sh.

Fixes #91

Revision 1: a1ed85e91cf36657bd2ab8595fef1f3b8529365e; base 9a13fc36a85adc6fc8bf9fcd05de2f842ca6e0cf

Revision 2: 969a798355aeb5b0569464ab0659cfbabeb924ba; base 690c31f46ee29825da4f366bb0f707038b00c944

Rebased onto main@690c31f (governance bootstrap). No content change; full suite green on the new base.

Fixes 4876fba0 and 7156e09a — two packaging defects that both make the documented path the wrong one. ## 4876fba0 — `make install` needed root `PREFIX` now defaults to `$HOME/.local`. `sudo make install PREFIX=/usr/local` is still the system-wide install, and the README documents the command that works with the system one as a named variant rather than the default. `uninstall` now removes the man pages as well as the binaries: `git-collab.1` and `git-collab-*.1` under the same prefix, which is the whole set `install` writes, plus pages an older version installed for subcommands that no longer exist — precisely the litter worth clearing. Verified against a throwaway prefix: ``` $ make install PREFIX=$TMP install -Dm755 target/release/git-collab $TMP/bin/git-collab install -Dm755 target/release/git-collab-server $TMP/bin/git-collab-server installed man pages to $TMP/share/man/man1 $ find $TMP -type f | wc -l 65 $ make uninstall PREFIX=$TMP $ find $TMP -type f | wc -l 0 ``` Only empty directories are left behind; nothing removes `$PREFIX/bin` itself, which would be an unfriendly thing to do to a directory that was there first. **Man pages no longer gate the install.** They were a prerequisite of the `install` target, so a prefix with an unwritable `share/man` aborted before a single binary was written — the convenience blocking the thing being installed. The binaries go in first now and the man pages are attempted after, warning and exiting 0 if they cannot be written: ``` $ make install PREFIX=$RO # $RO/share is mode a-w install -Dm755 target/release/git-collab $RO/bin/git-collab install -Dm755 target/release/git-collab-server $RO/bin/git-collab-server warning: could not write $RO/share/man/man1 — binaries are installed, man pages are not $ echo $? 0 ``` `make install-man` on its own stays strict. The asymmetry is deliberate: asking for man pages explicitly and silently not getting them is the worse failure, while asking for an install and not getting the binaries over a man directory is the one that leaves you with nothing. ## 7156e09a — container lost `--version` provenance The commit is passed in rather than the repository shipped: `--build-arg GIT_COMMIT`, which `make docker` fills from `git rev-parse HEAD`, plus a `GIT_DIRTY` argument so an image built from a work-in-progress tree says so instead of claiming to be the commit. `TAG` now defaults to the short commit from the same `git rev-parse`, so the tag and the stamp cannot disagree. `.dockerignore` still excludes `.git/`, and I confirmed by listing `/build` in the builder stage that nothing git-related reaches the image. ``` $ podman run --rm registry.a73x.sh/git-collab-server:236a3a8 --version git-collab-server 0.1.0 (236a3a876f607100748819039685778cf3f7b569) $ git rev-parse HEAD 236a3a876f607100748819039685778cf3f7b569 ``` and the degraded path, built with no arguments at all: ``` $ make docker GIT_COMMIT= TAG=noprovenance $ podman run --rm registry.a73x.sh/git-collab-server:noprovenance --version git-collab-server 0.1.0 ``` A passed commit overrides a discovered one and bypasses the same-checkout guard from `5519937d` rather than being filtered by it. The guard exists because *discovery* can pick up a stranger's repository when this crate is vendored into another tree, and nobody chose that; passing a value is a choice made by whoever is in a position to know. The price of that trust is validation: an unexpanded `GIT_COMMIT=HEAD` fails the build loudly rather than quietly producing the untraceable binary the mechanism exists to prevent. A *discovered* value that looks wrong still degrades to silence, so the tarball case is unaffected. ## The bug the container found The first image I built reported the commit it was given, and `-dirty`, from a spotlessly clean tree. `ENV FOO=$BAR` with an unset `ARG BAR` does not leave `FOO` unset — it sets it to the empty string — and `option_env!` reads the compiler's whole environment, not only what the build script emitted. So both variables arrived as `Some("")` however little the build had been told, which would also have rendered an argument-free container as `0.1.0 ()`. `src/cli.rs` now asks what a variable says rather than whether it exists. Every unit test passed while that was broken. Worth recording, because the fix for a provenance bug that only appears in a container is not provable from outside one. ## Tests `tests/version_test.rs` covers the resolution rules directly — passed beats discovered, empty is not a decision, malformed-passed errors, malformed- discovered degrades — and builds a throwaway crate whose build script *is* `src/build_provenance.rs` to check what a real `cargo build` emits with a commit passed, with neither a commit nor a repository, and with the variables set but empty. Those are the paths a binary built from this checkout can never exercise itself. The decision logic moved into `src/build_provenance.rs`, included by `build.rs` the way `src/cli.rs` already is, so it could be tested at all. Full suite green (53 suites, 0 failures); `cargo clippy --all-targets` silent. `tests/tui_review_test.rs` run 11 times across the change with no failures and no meaningful variance (2.84–2.94s). --- Migrated from git-collab patche `b7480bbbd59fa81b7cb3d4d5d37dc0971cda95b2`. Original author: a73x <dev@a73x.sh>. Fixes #91 Revision 1: `a1ed85e91cf36657bd2ab8595fef1f3b8529365e`; base `9a13fc36a85adc6fc8bf9fcd05de2f842ca6e0cf` Revision 2: `969a798355aeb5b0569464ab0659cfbabeb924ba`; base `690c31f46ee29825da4f366bb0f707038b00c944` Rebased onto main@690c31f (governance bootstrap). No content change; full suite green on the new base.
a73x commented 2026-08-13 15:04:26 +00:00 (Migrated from git.a73x.sh)

Note on the transcript in the body: the image was rebuilt at r2's commit after the rebase, so the tag in the body (236a3a8) has been superseded. Current evidence, at HEAD 969a798355:

$ podman run --rm registry.a73x.sh/git-collab-server:969a798 --version
git-collab-server 0.1.0 (969a798355aeb5b0569464ab0659cfbabeb924ba)

Exactly git rev-parse HEAD. The superseded images have been removed so no tag names a commit that no longer exists.

Note on the transcript in the body: the image was rebuilt at r2's commit after the rebase, so the tag in the body (236a3a8) has been superseded. Current evidence, at HEAD 969a798355aeb5b0569464ab0659cfbabeb924ba: $ podman run --rm registry.a73x.sh/git-collab-server:969a798 --version git-collab-server 0.1.0 (969a798355aeb5b0569464ab0659cfbabeb924ba) Exactly `git rev-parse HEAD`. The superseded images have been removed so no tag names a commit that no longer exists.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
a73x/git-collab!96
No description provided.