clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

Deploy a commit, not the working tree #2

commits3oldest firstcombined diff 7 files, +61 -8
revisions3r3: message only
r3cat4383583what changed since r2 message only
r2cate6d3bd1what changed since r1 tests only
r1cat5903f32
discussion0 comments

No comments yet: forge comment 2 adds one.

1/3 Write the stand-in rclone's listing whole in scenarios

Three scenarios wait for the stand-in rclone's file to appear, then read
it; the shell creates it empty before ls or find writes, so under a full
parallel run private-flip read an empty listing and failed. The stand-in
now writes to a temporary file and renames it, or appends each run's
listing at once.
3 files changed, +6 -3
Mtest/scenarios/insights-private-pages.sh3++-
Mtest/scenarios/private-flip.sh3++-
Mtest/scenarios/release-private-web.sh3++-
M test/scenarios/insights-private-pages.sh +2 -1
M test/scenarios/private-flip.sh +2 -1
M test/scenarios/release-private-web.sh +2 -1

2/3 Give each removal of a repo its own place in the trash

The trash name has the time to the second, so two removals of one repo
in the same second gave the same name, and mv put the second repo inside
the first one's directory, where restore and gc never saw it. A name
taken gets .2, .3 and so on, and restore picks the newest by the time
and that suffix alone, since repo names may hold dots.
2 files changed, +22 -2
Mbin/forge11+++++++++--
Mtest/scenarios/repo-rm.sh13+++++++++++++
M bin/forge +9 -2
M test/scenarios/repo-rm.sh +13 -0

3/3 Deploy a commit, not the working tree, and only with a CI pass

deploy.sh rsynced the checkout as it was, so uncommitted edits went to
slate with it; with two sessions in one checkout that nearly shipped
another's unfinished work. It now exports a commit with git archive,
HEAD by default, and wants a verified passing CI attestation for it, as
main on the forge does with forge.requireci; --force ships one without.
The commit deployed is written to /srv/forge/src/DEPLOYED.
2 files changed, +33 -3
MREADME.md8+++++++-
Mdeploy/slate/deploy.sh28++++++++++++++++++++++++++--
M README.md +7 -1
M deploy/slate/deploy.sh +26 -2
@@ -1,9 +1,33 @@
11 #!/bin/sh2-# Ship this checkout to slate and rebuild the forge container there.2+# Ship a commit of this repo to slate and rebuild the forge container there.33 # Data in /srv/forge/data is untouched.4+#5+# deploy/slate/deploy.sh [--force] [<commit>] HEAD by default6+#7+# What ships is the commit, exported with git archive, never the working8+# tree: uncommitted edits, whoever made them, stay here. It needs a9+# verified passing CI attestation (forge verify), as main on the forge10+# does; --force ships it anyway. The commit deployed is written to11+# /srv/forge/src/DEPLOYED.412 set -eu513 here=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)614 host=${FORGE_HOST:-slate}15+force=016+[ "${1:-}" = --force ] && { force=1; shift; }17+rev=$(git -C "$here" rev-parse --verify "${1:-HEAD}^{commit}")18+short=$(git -C "$here" rev-parse --short "$rev")19+if ! git -C "$here" diff --quiet HEAD 2>/dev/null; then20+ echo "deploy: the working tree has uncommitted changes; they stay here, $short ships" >&221+fi22+if ! "$here/bin/forge" verify "$rev" >/dev/null 2>&1; then23+ [ "$force" = 1 ] || { echo "deploy: $short has no verified passing CI attestation (forge attest $short), or pass --force" >&2; exit 1; }24+ echo "deploy: shipping $short without a verified CI pass (--force)" >&225+fi26+tmp=$(mktemp -d "${TMPDIR:-/tmp}/forge-deploy.XXXXXX")27+trap 'rm -rf "$tmp"' EXIT28+git -C "$here" archive "$rev" | tar -x -C "$tmp"29+printf '%s\n' "$rev" >"$tmp/DEPLOYED"730 rsync -a --delete --exclude node_modules --exclude demo/root --exclude demo/work \8- --exclude .git --exclude .env --exclude _site --exclude .cache "$here/" "$host:/srv/forge/src/"31+ --exclude .env --exclude _site --exclude .cache "$tmp/" "$host:/srv/forge/src/"932 ssh "$host" 'cd /srv/forge/src/deploy/slate && docker compose up -d --build'33+echo "deploy: $short is on $host"