crates/storage-git-gix/parked-gix/.nornir/bench/transfer-setup.sh
#!/usr/bin/env bash
# Build the transfer-shape corpus: 19 real workspace_nordisk repositories pushed
# into a gunnar store BY STOCK GIT, with a client that is genuinely N commits
# behind, so a fetch is a real negotiation and not a replay.
#
# The corpus is built by PUSHING into a bare store. `git clone --bare` silently
# repacks and would make every arm measure git's repacker instead of gunnar's.
#
# Two snapshots come out of this and nothing else in the lane rebuilds them:
# $X/pristine/data the gunnar data root right after the pushes
# $X/pristine/client-$D one client per depth, holding exactly the old state
set -u -o pipefail
X=${XFER_DIR:-/home/rickard/scratch/xfer}
G=${GUNNAR_BIN:?set GUNNAR_BIN to the binary under test}
PORT=${PORT:-12931}
DEPTHS=${DEPTHS:-"50 200"}
REPOS=${REPOS:-"nornir modgunn funnel-core holger facett edda skade znippy znippy-zoomies knut korp korp-collectors ordning Skidbladnir tunnr draupnir dwarves bifrost workspace_nordisk"}
SRC=${SRC:-/home/rickard/git}
die() { echo "FATAL: $*" >&2; exit 1; }
# The subject binary, named on stdout before anything runs. A bench lane
# measured a ten-hour-old binary because nothing on the row said which one it
# was; path, size and mtime are what actually distinguish two builds.
[ -x "$G" ] || die "GUNNAR_BIN=$G is not executable"
echo "subject: $G"
stat -c 'subject: size=%s mtime=%y' "$G"
mkdir -p "$X"
rm -rf "$X/pristine" "$X/data" "$X/hashes.tsv"
mkdir -p "$X/pristine" "$X/keys"
[ -f "$X/keys/id_ed25519" ] || ssh-keygen -t ed25519 -N '' -C xfer -f "$X/keys/id_ed25519" >/dev/null
chmod 600 "$X/keys/id_ed25519"
SSH="ssh -i $X/keys/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
export GIT_SSH_COMMAND="$SSH"
stop_server() {
local pid
pid=$(ps -eo pid,args | awk -v p="--ssh-bind 127.0.0.1:$PORT" 'index($0,p)&&!/awk/{print $1}')
[ -n "$pid" ] && kill $pid 2>/dev/null
for _ in $(seq 1 40); do
ps -p ${pid:-0} >/dev/null 2>&1 || return 0
sleep 0.25
done
return 0
}
start_server() { # data-root logfile
GUNNAR_DATA_ROOT=$1 nohup "$G" serve --ssh-bind 127.0.0.1:$PORT --no-grpc \
--admin-key "@$X/keys/id_ed25519.pub" > "$2" 2>&1 &
for _ in $(seq 1 60); do
sleep 0.5
if timeout 3 bash -c "exec 3<>/dev/tcp/127.0.0.1/$PORT && head -c 20 <&3" 2>/dev/null | grep -q 'SSH-2.0-gunnar'; then
return 0
fi
done
die "gunnar did not answer on $PORT: $(tail -5 "$2")"
}
# ── create the repositories, which must happen with the server DOWN (S-019) ──
stop_server
mkdir -p "$X/data"
GUNNAR_DATA_ROOT=$X/data "$G" namespace create bench >/dev/null || die "namespace create"
# The hash comes from the SOURCE repository, not from a literal. `tunnr` is a
# sha256 repository and creating it as sha1 fails at the wire with "the
# receiving end does not support this repository's hash algorithm" -- which is
# also the only reason the corpus covers both hashes at all.
for r in $REPOS; do
h=$(git -C "$SRC/$r" rev-parse --show-object-format 2>/dev/null) || h=sha1
GUNNAR_DATA_ROOT=$X/data "$G" repo create "bench/$r" --hash "$h" --public >/dev/null \
|| die "repo create bench/$r ($h)"
echo "$r $h" >> "$X/hashes.tsv"
done
start_server "$X/data" "$X/serve-setup.log"
url() { echo "ssh://git@127.0.0.1:$PORT/bench/$1.git"; }
# ── push the OLD state, clone the clients off it, then push the rest ─────────
# The client's history is real: it was cloned when the server held only the
# first (n-D) commits, so its `have`s are the ones a client that has been away
# actually sends. Resetting a full clone would leave every "missing" object in
# the client's own object store and the fetch would send nothing.
for D in $DEPTHS; do mkdir -p "$X/pristine/client-$D"; done
for r in $REPOS; do
head=$(git -C "$SRC/$r" rev-parse HEAD) || die "$r: no HEAD"
# Push the old state at the DEEPEST depth first so every shallower client can
# be cut from the same server state; then push each successively newer cut.
# Sorting keeps the pushes strictly forward and avoids a non-fast-forward.
for D in $(for d in $DEPTHS; do echo "$d"; done | sort -rn); do
old=$(git -C "$SRC/$r" rev-parse "HEAD~$D" 2>/dev/null) || old=""
if [ -z "$old" ]; then echo "skip $r at depth $D (history shorter)"; continue; fi
git -C "$SRC/$r" -c lfs.allowincompletepush=true push --no-verify -q "$(url "$r")" "+$old:refs/heads/main" || die "$r push old@$D"
git -c filter.lfs.smudge=cat -c filter.lfs.process= -c filter.lfs.required=false clone -q --no-checkout "$(url "$r")" "$X/pristine/client-$D/$r" || die "$r clone client@$D"
done
git -C "$SRC/$r" -c lfs.allowincompletepush=true push --no-verify -q "$(url "$r")" "+$head:refs/heads/main" || die "$r push head"
echo "pushed $r"
done
stop_server
cp -a "$X/data" "$X/pristine/data"
echo "corpus ready: $(du -sh "$X/pristine/data" | cut -f1) in $X/pristine"