Two fixes to push-work and the policy that goes with them.
**Tags.** The port's exporter depends on sylpheed-formats BY REVISION, so a
commit of ours is part of its build -- and the commit it pinned lived on one
auto/* branch and nowhere else. Deleting that branch orphans it; squash-merging
it is worse, because squash creates NEW commits, so main appears to contain the
work while the pinned sha becomes unreachable and the port stops building for a
fresh checkout. Silently, at their build, long after the breakage.
push-work now pushes with --follow-tags, which publishes annotated tags
reachable from the pushed commits, and MISSION.md says to tag whatever the port
needs. formats-pin-2026-08-29 at 7eeae30 is the first, created after the fact.
**Credentials.** This script set credential.helper as --local config, which
PERSISTS in the repository. The repo is a bind mount the host also uses, so the
host's git inherited a path that exists only inside the container and every host
push failed with "unable to get credential storage lock". Now applied with -c to
the single push. Same fix already landed on the port side; this copy still had
the bug and would have re-leaked on the next push.
Committed with a pathspec so the agent's in-flight work is untouched.
83 lines
3.3 KiB
Bash
Executable File
83 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Push the current topic branch to origin — the ONLY sanctioned way out of the
|
|
# container.
|
|
#
|
|
# Why a wrapper instead of plain `git push`:
|
|
#
|
|
# * **`main` and shared branches are refused.** The agent commits to
|
|
# `auto/<topic>`; a human merges. A token that can push anywhere is one
|
|
# confused iteration away from rewriting the consolidated line.
|
|
# * **Force-push is refused**, always. Nothing here needs it, and history
|
|
# rewriting is the one mistake that cannot be undone by merging.
|
|
# * It pushes the CURRENT branch only, by name, so a stray `--all` cannot
|
|
# publish another agent's worktree branch mid-experiment.
|
|
#
|
|
# Credentials come from a file mounted read-only at ~/.git-credentials (see
|
|
# `sylph-agent`). They are never printed, never logged, and never passed on a
|
|
# command line.
|
|
#
|
|
# push-work push the current branch, and any annotated tags on it
|
|
#
|
|
# --follow-tags publishes ANNOTATED tags reachable from the pushed commits. That
|
|
# is what makes a pinned decoder state durable: the port depends on commits of
|
|
# ours by revision, and a commit reachable only from a topic branch is orphaned
|
|
# by a squash-merge. Lightweight tags are deliberately not pushed.
|
|
# push-work --dry-run say what it would do
|
|
set -euo pipefail
|
|
|
|
DRY=0
|
|
[ "${1:-}" = "--dry-run" ] && DRY=1
|
|
|
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
|
echo "push-work: not inside a git repository" >&2; exit 1; }
|
|
cd "$repo_root"
|
|
|
|
branch=$(git rev-parse --abbrev-ref HEAD)
|
|
if [ "$branch" = "HEAD" ]; then
|
|
echo "push-work: detached HEAD — check out a branch first" >&2; exit 1
|
|
fi
|
|
|
|
case "$branch" in
|
|
auto/*) ;;
|
|
*)
|
|
echo "push-work: refusing to push '$branch'." >&2
|
|
echo " Only auto/* topic branches may leave the container; a human merges" >&2
|
|
echo " them into main. Move your work: git switch -c auto/<topic>" >&2
|
|
exit 1 ;;
|
|
esac
|
|
|
|
if [ ! -s "$HOME/.git-credentials" ]; then
|
|
echo "push-work: no credentials mounted at ~/.git-credentials." >&2
|
|
echo " The host must start the container with SYLPH_GIT_CREDENTIALS pointing" >&2
|
|
echo " at a file containing one line:" >&2
|
|
echo " https://<user>:<token>@git.mc02.dev" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# `store` reads the file we mounted; nothing is written back (it is read-only).
|
|
# Applied to THIS COMMAND ONLY, via `-c`, never `git config --local`.
|
|
#
|
|
# Writing it to --local config persists it in the repository, and this repo is a
|
|
# bind mount the host also uses -- so the host's git inherited
|
|
# `store --file=/sylph-home/re/.git-credentials`, a path that exists only inside
|
|
# the container, and every host push then failed with
|
|
# `unable to get credential storage lock: No such file or directory`.
|
|
#
|
|
# A tool that configures a shared repository to suit itself breaks every other
|
|
# user of that repository. Keep it to the invocation.
|
|
CRED_HELPER="store --file=$HOME/.git-credentials"
|
|
|
|
ahead=$(git rev-list --count "origin/$branch..$branch" 2>/dev/null || git rev-list --count HEAD)
|
|
echo "push-work: $branch — $ahead commit(s) to publish"
|
|
|
|
if [ "$DRY" = 1 ]; then
|
|
echo "push-work: --dry-run, stopping here"
|
|
exit 0
|
|
fi
|
|
|
|
# --force-with-lease is deliberately NOT offered. If this is rejected as
|
|
# non-fast-forward, someone else moved the branch: fetch and merge, do not
|
|
# overwrite.
|
|
git -c "credential.helper=$CRED_HELPER" push --follow-tags --set-upstream origin "$branch"
|
|
echo "push-work: pushed $branch"
|