Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

slab/menuband: harden mirror sync — pre-flight unlanded contributor commits + add mirror-pull.sh

mirror-sync.sh now refuses to push if the mirror has contributor commits
since the last "Mirror of <hash>" snapshot that aren't reflected in the
monorepo's slab/menuband history (override: SYNC_FORCE=1). Without this,
a snapshot from monorepo silently regresses contributor work in the
mirror's tip tree — exactly what happened with Esteban's PR #2 before it
was pulled back via format-patch + git am --3way.

Adds bin/mirror-pull.sh as the inverse path: fetch the mirror, walk
non-snapshot commits since the last marker, apply via
`git am --3way --directory=slab/menuband/`. Skips subjects already
landed (idempotent). 3-way merge handles the case where a contributor
has manually merged main into their branch and re-adds files the
monorepo already has.

Also: refuse sync on dirty PREFIX (would push uncommitted files).

MIRROR_README updated with the new contributor flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+219 -10
+20 -9
slab/menuband/MIRROR_README.md
··· 23 23 24 24 ## Mirror notice 25 25 26 - This repository is a **read-only mirror** of `slab/menuband/` from the 26 + This repository is a **snapshot mirror** of `slab/menuband/` from the 27 27 [aesthetic.computer monorepo](https://tangled.org/@aesthetic.computer/core). 28 - The monorepo is canonical; this mirror is force-pushed by a `git subtree split` 29 - hook every time the upstream Menu Band code changes. 28 + The monorepo is canonical; the maintainer pushes new "Mirror of `<hash>`" 29 + commits onto `main` whenever upstream code changes. 30 30 31 - Force-push only affects `main`. Your forks and feature branches are not 32 - disturbed by upstream sync. 31 + Your forks and feature branches are not disturbed by upstream sync — only 32 + `main` advances. 33 33 34 34 ### Submitting changes 35 35 36 36 Forks + pull requests work normally on GitHub. When the maintainer accepts 37 - your PR, the changes get applied back to the monorepo via `git format-patch` 38 - + `git am`, preserving your authorship in the commit log. The next mirror 39 - sync after that lands those commits here too — so your hash on the mirror 40 - will eventually match a hash in the upstream monorepo's history. 37 + your PR: 38 + 39 + 1. The maintainer runs `slab/menuband/bin/mirror-pull.sh` from the monorepo. 40 + That script walks every contributor commit on `main` since the last 41 + "Mirror of …" snapshot and applies them via 42 + `git format-patch | git am --3way --directory=slab/menuband/`, 43 + preserving your authorship. 44 + 2. Then `slab/menuband/bin/mirror-sync.sh` snapshots back to `main`. The 45 + sync script **refuses to push** if any contributor commit on the mirror 46 + hasn't been landed in the monorepo first — so your work can't get 47 + silently regressed by an out-of-order sync. 48 + 49 + The mirror's commit hash for your work won't match the monorepo's (git am 50 + rewrites hashes), but the author and subject do. After the next sync, the 51 + mirror's tip tree will contain your changes. 41 52 42 53 ## Acknowledgements 43 54
+136
slab/menuband/bin/mirror-pull.sh
··· 1 + #!/usr/bin/env bash 2 + # mirror-pull.sh — Pull contributor commits from the mirror back into the monorepo. 3 + # 4 + # After someone lands a PR on github.com/whistlegraph/menuband, run this from 5 + # anywhere inside the monorepo to apply their commits onto slab/menuband/, 6 + # preserving their authorship. mirror-sync.sh will then snapshot back to the 7 + # mirror cleanly without regressing their work. 8 + # 9 + # How it works: 10 + # 1. Fetches the mirror remote (auto-adds it via HTTPS if missing). 11 + # 2. Finds the last "Mirror of <hash>" snapshot commit on the mirror — that 12 + # marks the previous sync point. 13 + # 3. Walks every non-merge, non-mirror commit since then and skips any whose 14 + # subject already appears in monorepo's slab/menuband history (treats 15 + # those as already landed — git am rewrites hashes, so subject is the 16 + # stable identity). 17 + # 4. For each unlanded commit, runs `git format-patch -1` then 18 + # `git am --3way --directory=slab/menuband/`. The 3-way merge cleanly 19 + # handles the case where a contributor has manually merged main into 20 + # their branch (their commit may "re-add" files that the monorepo 21 + # already has — 3way reconciles instead of failing on "file already 22 + # exists in index"). 23 + # 5. On conflict, stops and prints how to resume. 24 + 25 + set -euo pipefail 26 + 27 + CYAN=$'\033[1;36m' 28 + GREEN=$'\033[1;32m' 29 + YELLOW=$'\033[1;33m' 30 + RED=$'\033[1;31m' 31 + RESET=$'\033[0m' 32 + say() { printf "%s• %s%s\n" "$CYAN" "$1" "$RESET"; } 33 + ok() { printf "%s✓ %s%s\n" "$GREEN" "$1" "$RESET"; } 34 + warn() { printf "%s! %s%s\n" "$YELLOW" "$1" "$RESET"; } 35 + err() { printf "%s✗ %s%s\n" "$RED" "$1" "$RESET" 1>&2; } 36 + 37 + REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" 38 + if [[ -z "${REPO_ROOT}" ]]; then 39 + err "not inside a git repository" 40 + exit 1 41 + fi 42 + cd "${REPO_ROOT}" 43 + 44 + PREFIX="slab/menuband" 45 + MIRROR_REMOTE="menuband-mirror" 46 + MIRROR_URL="https://github.com/whistlegraph/menuband.git" 47 + MIRROR_BRANCH="main" 48 + 49 + if [[ ! -d "${PREFIX}" ]]; then 50 + err "${PREFIX}/ not found in ${REPO_ROOT}" 51 + exit 1 52 + fi 53 + 54 + # Reject if the working tree has any modifications under PREFIX — git am 55 + # would refuse anyway, and an unrelated dirty file could mask a real conflict. 56 + if ! git diff --quiet -- "${PREFIX}" || ! git diff --cached --quiet -- "${PREFIX}"; then 57 + err "${PREFIX}/ has uncommitted changes — commit or stash first" 58 + git status -s -- "${PREFIX}" 1>&2 59 + exit 1 60 + fi 61 + 62 + # Add or refresh the remote (HTTPS so gh credentials work, mirroring sync.sh). 63 + if ! git remote get-url "${MIRROR_REMOTE}" >/dev/null 2>&1; then 64 + say "adding remote ${MIRROR_REMOTE} → ${MIRROR_URL}" 65 + git remote add "${MIRROR_REMOTE}" "${MIRROR_URL}" 66 + fi 67 + 68 + say "fetching ${MIRROR_REMOTE}" 69 + git fetch --quiet "${MIRROR_REMOTE}" 70 + 71 + MIRROR_REF="${MIRROR_REMOTE}/${MIRROR_BRANCH}" 72 + 73 + # Find the last "Mirror of <hash>" snapshot on the mirror — that's our sync point. 74 + LAST_MIRROR_COMMIT="$(git log --format='%H' --grep='^Mirror of [0-9a-f]\{7\}:' -1 "${MIRROR_REF}" || true)" 75 + if [[ -z "${LAST_MIRROR_COMMIT}" ]]; then 76 + err "no 'Mirror of <hash>' commit found on ${MIRROR_REF} — refusing to guess sync point" 77 + exit 1 78 + fi 79 + say "last sync point: $(git log -1 --format='%h %s' "${LAST_MIRROR_COMMIT}")" 80 + 81 + # Collect contributor commits in chronological order. Skip merges (format-patch 82 + # can't represent them anyway) and our own snapshot commits. 83 + CONTRIB_SHAS=() 84 + while IFS=$'\t' read -r contrib_sha contrib_subj; do 85 + [[ -z "${contrib_sha}" ]] && continue 86 + [[ "${contrib_subj}" == "Mirror of "* ]] && continue 87 + # Skip if subject already exists in monorepo's slab/menuband history. 88 + if git log --format='%s' -- "${PREFIX}" | grep -Fxq "${contrib_subj}"; then 89 + continue 90 + fi 91 + CONTRIB_SHAS+=("${contrib_sha}") 92 + done < <(git log --no-merges --reverse --format='%H%x09%s' \ 93 + "${LAST_MIRROR_COMMIT}..${MIRROR_REF}" 2>/dev/null) 94 + 95 + if [[ ${#CONTRIB_SHAS[@]} -eq 0 ]]; then 96 + ok "monorepo is already up-to-date with mirror contributors" 97 + exit 0 98 + fi 99 + 100 + say "${#CONTRIB_SHAS[@]} contributor commit(s) to land:" 101 + for sha in "${CONTRIB_SHAS[@]}"; do 102 + printf ' %s\n' "$(git log -1 --format='%h %an: %s' "${sha}")" 103 + done 104 + 105 + # Stage the patches under one numbered series so `git am` consumes them in 106 + # order. Using --start-number to avoid filename collisions when the same 107 + # default name (0001-…) repeats. 108 + PATCH_DIR="$(mktemp -d)" 109 + trap "rm -rf ${PATCH_DIR}" EXIT 110 + i=1 111 + for sha in "${CONTRIB_SHAS[@]}"; do 112 + git format-patch --quiet -o "${PATCH_DIR}" --start-number=$i -1 "${sha}" >/dev/null 113 + i=$((i + 1)) 114 + done 115 + 116 + say "applying via git am --3way --directory=${PREFIX}/" 117 + # Glob expansion gives us patches in numeric (sorted) order. 118 + shopt -s nullglob 119 + PATCHES=( "${PATCH_DIR}"/*.patch ) 120 + shopt -u nullglob 121 + 122 + if ! git am --3way --directory="${PREFIX}/" "${PATCHES[@]}"; then 123 + err "git am stopped on conflict" 124 + echo " resolve the conflict, then run:" 1>&2 125 + echo " git am --3way --continue" 1>&2 126 + echo " to skip this patch:" 1>&2 127 + echo " git am --skip" 1>&2 128 + echo " to abort the entire pull:" 1>&2 129 + echo " git am --abort" 1>&2 130 + exit 1 131 + fi 132 + 133 + ok "landed ${#CONTRIB_SHAS[@]} commit(s) onto ${PREFIX}/" 134 + echo 135 + echo " Next: re-snapshot to mirror so contributor branches see their work as base:" 136 + echo " ${REPO_ROOT}/${PREFIX}/bin/mirror-sync.sh"
+63 -1
slab/menuband/bin/mirror-sync.sh
··· 33 33 warn() { printf "%s! %s%s\n" "$YELLOW" "$1" "$RESET"; } 34 34 err() { printf "%s✗ %s%s\n" "$RED" "$1" "$RESET" 1>&2; } 35 35 36 + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 37 + 36 38 REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" 37 39 if [[ -z "${REPO_ROOT}" ]]; then 38 40 err "not inside a git repository" ··· 49 51 exit 1 50 52 fi 51 53 54 + # Refuse if the working tree under PREFIX has uncommitted changes — otherwise 55 + # we'd push files to the mirror that aren't yet committed in the monorepo, 56 + # making the mirror tip ahead of (and inconsistent with) the canonical source. 57 + if ! git diff --quiet -- "${PREFIX}" || ! git diff --cached --quiet -- "${PREFIX}"; then 58 + err "${PREFIX}/ has uncommitted changes — commit them before syncing" 59 + git status -s -- "${PREFIX}" 1>&2 60 + exit 1 61 + fi 62 + 52 63 # Identify the most recent mono commit that touched the prefix — 53 64 # its hash + subject become the mirror's snapshot label, and its 54 65 # author lands on the mirror commit so contributors see their ··· 67 78 68 79 if git ls-remote --heads "${MIRROR_URL}" "${MIRROR_BRANCH}" 2>/dev/null | grep -q "refs/heads/${MIRROR_BRANCH}"; then 69 80 say "cloning mirror" 70 - git clone --depth=1 --branch="${MIRROR_BRANCH}" "${MIRROR_URL}" "${WORK}" >/dev/null 2>&1 81 + # Need enough depth to walk back to the previous "Mirror of <hash>" commit 82 + # for the unlanded-contributor pre-flight check below. Shallow=50 is plenty; 83 + # snapshots happen often enough that the last marker is always near the tip. 84 + git clone --depth=50 --branch="${MIRROR_BRANCH}" "${MIRROR_URL}" "${WORK}" >/dev/null 2>&1 71 85 else 72 86 say "initialising empty mirror" 73 87 git init -q -b "${MIRROR_BRANCH}" "${WORK}" 74 88 git -C "${WORK}" remote add origin "${MIRROR_URL}" 89 + fi 90 + 91 + # Pre-flight: refuse to snapshot if the mirror has contributor commits since 92 + # the last "Mirror of <hash>" point that haven't been landed in the monorepo 93 + # yet. Without this check, the snapshot's tree would silently regress that 94 + # contributor work — exactly what happened with Esteban's PR #2: the mirror 95 + # had his consolidated palette merged, the monorepo didn't yet, and a sync 96 + # from monorepo blew his work out of the tip tree. 97 + # 98 + # Override with SYNC_FORCE=1 if you really mean it (e.g. you've decided to 99 + # revert a contributor change). Don't make this a habit. 100 + LAST_MIRROR_COMMIT="$(git -C "${WORK}" log --format='%H' --grep='^Mirror of [0-9a-f]\{7\}:' -1 || true)" 101 + if [[ -n "${LAST_MIRROR_COMMIT}" ]]; then 102 + LAST_MIRROR_MONO_HASH="$(git -C "${WORK}" log -1 --format='%s' "${LAST_MIRROR_COMMIT}" \ 103 + | sed -nE 's/^Mirror of ([0-9a-f]+):.*/\1/p')" 104 + 105 + UNLANDED_COUNT=0 106 + UNLANDED_LINES="" 107 + while IFS=$'\t' read -r contrib_sha contrib_subj; do 108 + [[ -z "${contrib_sha}" ]] && continue 109 + # Skip future "Mirror of …" snapshots — those are our own. 110 + [[ "${contrib_subj}" == "Mirror of "* ]] && continue 111 + # Has this subject appeared in the monorepo's slab/menuband history 112 + # since the last sync? If yes, treat as landed (authorship/hash differ 113 + # because of git am rewrites, but the content is what matters). 114 + if ! git log --format='%s' -- "${PREFIX}" | grep -Fxq "${contrib_subj}"; then 115 + UNLANDED_COUNT=$((UNLANDED_COUNT + 1)) 116 + UNLANDED_LINES+=" ${contrib_sha:0:9} ${contrib_subj}"$'\n' 117 + fi 118 + done < <(git -C "${WORK}" log --no-merges --format='%H%x09%s' \ 119 + "${LAST_MIRROR_COMMIT}..HEAD" 2>/dev/null) 120 + 121 + if [[ ${UNLANDED_COUNT} -gt 0 ]]; then 122 + err "mirror has ${UNLANDED_COUNT} contributor commit(s) not landed in monorepo:" 123 + printf '%s' "${UNLANDED_LINES}" 1>&2 124 + echo 1>&2 125 + echo " Land them first (preserves authorship, handles overlap):" 1>&2 126 + echo " ${SCRIPT_DIR}/mirror-pull.sh" 1>&2 127 + echo 1>&2 128 + echo " Override (DANGEROUS — will regress contributor work in mirror tip):" 1>&2 129 + echo " SYNC_FORCE=1 $0" 1>&2 130 + if [[ "${SYNC_FORCE:-}" != "1" ]]; then 131 + exit 1 132 + fi 133 + warn "SYNC_FORCE=1 set — proceeding despite unlanded commits" 134 + else 135 + ok "no unlanded contributor commits on mirror" 136 + fi 75 137 fi 76 138 77 139 # Replace the mirror's working tree with the current snapshot of