git.lucas.co / cce
cce workspace root: member list, lockfile and dependency pins
git clone https://git.lucas.co/cce.git

commitce183165b0d4604565d256854c6f2c121b87754b
parent311c1168f1
authorLucas Galante <[email protected]>
date2026-09-19 23:35
Add bump-revs.sh: repoint the pins after a shared crate is pushed

The git pins that let an app be cloned and built on its own are invisible
inside this workspace, because the [patch] block redirects those sources back
to the local crates. A stale pin therefore never fails a build here -- it
shows up only as a standalone build elsewhere quietly compiling an old copy
of the toolkit. That needs a tool, not vigilance.

It pins to the bare repo's HEAD rather than the work tree's, because a rev
that exists only in a work tree is fetchable by nobody and would produce
manifests that resolve on this machine alone. A dependency with unpushed
commits or uncommitted changes blocks the run and names the push that would
unblock it, rather than pinning something already stale. A manifest that
should have changed but did not fails the run too -- silently skipping is
what let 21 repos sit unpushed for a day.

Co-Authored-By: Claude Opus 5 <[email protected]>

 CLAUDE.md    |   5 +-
 bump-revs.sh | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 189 insertions(+), 1 deletion(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index a39d1a5..367e9eb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -12,7 +12,10 @@ This directory **is** now a git repository, but a deliberately narrow one: it
 versions only what ties the crates together — `Cargo.toml` (the member list and
 the `[patch]` block), `Cargo.lock`, `.cargo/config.toml` and this file. Its
 `.gitignore` excludes every subdirectory by glob, so no crate can be swallowed
-as an embedded repo and adding a crate needs no change here.
+as an embedded repo and adding a crate needs no change here. It also holds
+`bump-revs.sh`: after pushing a shared crate, run it to repoint the git pins in
+the crates that depend on it, because a stale pin never fails a build here --
+only a standalone build elsewhere.
 
 Three rules are repeated here, and only these three, because acting against any
 of them before reading the guide does damage that is annoying to undo:
diff --git a/bump-revs.sh b/bump-revs.sh
new file mode 100755
index 0000000..b6f5eab
--- /dev/null
+++ b/bump-revs.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+# Bump the pinned revs that let a single crate be installed on its own.
+#
+# Each app declares its workspace-internal dependencies as git dependencies on
+# git.lucas.co, pinned to an exact rev, while the workspace root patches those
+# sources back to the local crates. That split is what lets one app be cloned
+# and built alone -- and it is also exactly why the pins rot silently: inside
+# this workspace the [patch] block always wins, so a stale rev never fails a
+# build here. It surfaces only as a standalone build of an app quietly
+# compiling an old copy of the toolkit. Nothing warns you. Hence this script.
+#
+# Run it after pushing a shared crate (cce-ui, cce-window-manager).
+#
+#   bump-revs.sh [--dry-run] [--commit] [dep...]
+#
+# With no dep named, every dependency that appears in a git pin is considered.
+#
+# WHAT IT PINS TO: the BARE repo's HEAD under ~/git -- not the work tree's. A
+# rev that exists only in a work tree is fetchable by nobody, so pinning it
+# would write manifests that resolve on this machine and nowhere else. If a
+# dependency has unpushed commits or uncommitted changes, that is reported and
+# the run fails rather than pinning something stale; push it first. A bare repo
+# ahead of its work tree is fine and is pinned as-is -- it is what others can
+# actually fetch.
+#
+# git.lucas.co lags the bare repos by up to an hour (gitsite.timer), so a rev
+# pinned immediately after a push is correct but not yet fetchable from the
+# site. That resolves itself and is not an error.
+#
+# A manifest that should have changed but did not fails the run. Silently
+# skipping is what let 21 repos sit unpushed for a day; the same rule applies
+# here.
+#
+#   env: GIT_BARE_ROOT (default ~/git)
+
+set -eu
+
+BARE_ROOT="${GIT_BARE_ROOT:-$HOME/git}"
+ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+DRY=""
+COMMIT=""
+WANT=""
+
+for arg in "$@"; do
+    case "$arg" in
+        --dry-run) DRY=1 ;;
+        --commit)  COMMIT=1 ;;
+        -h|--help) sed -n '2,34p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
+        -*) echo "unknown option: $arg" >&2; exit 2 ;;
+        *)  WANT="$WANT $arg" ;;
+    esac
+done
+
+[ -f "$ROOT/Cargo.toml" ] || { echo "no Cargo.toml beside $0" >&2; exit 1; }
+grep -q '^\[workspace\]' "$ROOT/Cargo.toml" || {
+    echo "$ROOT/Cargo.toml is not a workspace root" >&2; exit 1; }
+
+say() { printf '%s\n' "$*"; }
+
+# Every git pin in the workspace, as "crate dep rev". The manifests are written
+# by this script and by hand in one uniform shape, so one pattern covers them;
+# a line that drifts out of that shape stops matching and is reported below as
+# a manifest that did not change, rather than being quietly left behind.
+pins() {
+    for m in "$ROOT"/*/Cargo.toml; do
+        crate=$(basename "$(dirname "$m")")
+        sed -n 's|^\([a-z0-9-]*\) = { git = "https://git\.lucas\.co/\1\.git", rev = "\([0-9a-f]\{40\}\)" }$|'"$crate"' \1 \2|p' "$m"
+    done
+}
+
+ALL_PINS=$(pins)
+[ -n "$ALL_PINS" ] || { say "no git pins found under $ROOT"; exit 0; }
+
+DEPS=$(printf '%s\n' "$ALL_PINS" | awk '{print $2}' | sort -u)
+if [ -n "$WANT" ]; then
+    for w in $WANT; do
+        printf '%s\n' "$DEPS" | grep -qx "$w" || {
+            echo "$w is not pinned by any crate here" >&2; exit 1; }
+    done
+    DEPS=$(printf '%s' "$WANT" | tr ' ' '\n' | sed '/^$/d')
+fi
+
+[ -n "$DRY" ] && say "DRY RUN -- nothing will be written"
+
+# Resolve each dependency to the rev that others can actually fetch, refusing
+# to pin anything that is only local. Collected first, so a blocked dependency
+# stops the run before any manifest is touched.
+TARGETS=""
+BLOCKED=""
+for dep in $DEPS; do
+    bare="$BARE_ROOT/$dep.git"
+    [ -d "$bare" ] || { say "!! $dep: no bare repo at $bare"; BLOCKED="$BLOCKED $dep"; continue; }
+    new=$(git -C "$bare" rev-parse HEAD 2>/dev/null) || {
+        say "!! $dep: bare repo has no HEAD commit"; BLOCKED="$BLOCKED $dep"; continue; }
+
+    wt="$ROOT/$dep"
+    if [ -d "$wt/.git" ]; then
+        if [ -n "$(git -C "$wt" status --porcelain)" ]; then
+            say "!! $dep: uncommitted changes -- commit and push before pinning"
+            BLOCKED="$BLOCKED $dep"; continue
+        fi
+        wt_head=$(git -C "$wt" rev-parse HEAD)
+        if [ "$wt_head" != "$new" ] && git -C "$wt" merge-base --is-ancestor "$new" "$wt_head" 2>/dev/null; then
+            ahead=$(git -C "$wt" rev-list --count "$new..$wt_head")
+            say "!! $dep: work tree is $ahead commit(s) ahead of $bare"
+            say "     push it first, or the pin misses that work:"
+            say "     git -C $wt push origin \$(git -C $wt symbolic-ref --short HEAD)"
+            BLOCKED="$BLOCKED $dep"; continue
+        fi
+    fi
+    TARGETS="$TARGETS $dep=$new"
+done
+
+if [ -n "$BLOCKED" ]; then
+    say ""
+    say "blocked:$BLOCKED -- nothing written"
+    say "name the other dependencies explicitly to bump them anyway, e.g."
+    say "    $(basename "$0")$(printf '%s\n' "$DEPS" | grep -vx "$(printf '%s' "$BLOCKED" | tr -d ' ')" | tr '\n' ' ' | sed 's/ $//' | sed 's/^/ /')"
+    exit 1
+fi
+
+# Apply. A crate already at the target rev is left alone and reported as such,
+# so the output distinguishes "nothing to do" from "did nothing".
+CHANGED=""
+UNCHANGED=0
+for t in $TARGETS; do
+    dep=${t%%=*}
+    new=${t#*=}
+    say "$dep -> $(printf '%.8s' "$new")"
+    printf '%s\n' "$ALL_PINS" | while read -r crate d old; do
+        [ "$d" = "$dep" ] || continue
+        [ "$old" = "$new" ] && { echo "SAME $crate"; continue; }
+        echo "EDIT $crate $old"
+    done > "${TMPDIR:-/tmp}/bump-revs.$$"
+
+    while read -r verb crate old; do
+        case "$verb" in
+            SAME) UNCHANGED=$((UNCHANGED + 1)) ;;
+            EDIT)
+                m="$ROOT/$crate/Cargo.toml"
+                say "    $crate"
+                if [ -z "$DRY" ]; then
+                    sed -i "s|^$dep = { git = \"https://git.lucas.co/$dep.git\", rev = \"$old\" }$|$dep = { git = \"https://git.lucas.co/$dep.git\", rev = \"$new\" }|" "$m"
+                    grep -q "rev = \"$new\"" "$m" || {
+                        say "!! $crate: manifest did not change -- pin format drifted?"; exit 1; }
+                else
+                    printf '    would: %s %s -> %s\n' "$crate" "$(printf '%.8s' "$old")" "$(printf '%.8s' "$new")"
+                fi
+                CHANGED="$CHANGED $crate"
+                ;;
+        esac
+    done < "${TMPDIR:-/tmp}/bump-revs.$$"
+    rm -f "${TMPDIR:-/tmp}/bump-revs.$$"
+done
+
+CHANGED=$(printf '%s' "$CHANGED" | tr ' ' '\n' | sed '/^$/d' | sort -u)
+COUNT=$(printf '%s' "$CHANGED" | grep -c . || true)
+
+say ""
+if [ "$COUNT" = 0 ]; then
+    say "every pin already current ($UNCHANGED) -- nothing to do"
+    exit 0
+fi
+if [ "$UNCHANGED" -gt 0 ]; then
+    say "$COUNT crate(s) repinned, $UNCHANGED already current"
+else
+    say "$COUNT crate(s) repinned"
+fi
+
+if [ -n "$COMMIT" ] && [ -z "$DRY" ]; then
+    say ""
+    say "committing:"
+    for crate in $CHANGED; do
+        ( cd "$ROOT/$crate" && git add Cargo.toml && git commit --quiet -m "Repin workspace dependencies to their published revs
+
+The pinned revs only affect builds outside this workspace, where an app is
+cloned on its own, so a stale pin never fails a build here. Bumped by
+bump-revs.sh after the dependency was pushed." ) && say "    $crate"
+    done
+    say ""
+    say "committed, not published -- push when ready:"
+    say "    git-bare-sync.sh"
+else
+    say "review, then commit in each crate (or re-run with --commit)"
+fi