#!/usr/bin/env bash # The git bridge end to end: importing a repository's history, bringing a # branch in as a change, publishing with the original git history, and the # hosted git view (apps/vcs under `wrangler dev`, pushing to a local git # server that stands in for git.coline.app): new versions as commits, # private paths left out, a push from outside pausing it, and resuming. # Usage: tests/git.sh [path/to/target/debug] set -uo pipefail ROOT=$(cd "$(dirname "$0")/.." && pwd) BIN=${1:-$ROOT/target/debug} VCS=$BIN/vcs APP=$ROOT/../apps/vcs WORK=$(mktemp -d) export VCS_SAVER=0 VCS_HOME=$WORK/home VCS_NAME=Alice VCS_EMAIL=alice@acme.test PORT=$((20000 + RANDOM % 20000)) GPORT=$((PORT + 2)) HOST="127.0.0.1:$PORT" WORKER_PID= GIT_PID= cleanup() { [ -n "$WORKER_PID" ] && kill -- "-$WORKER_PID" 2>/dev/null [ -n "$GIT_PID" ] && kill "$GIT_PID" 2>/dev/null wait 2>/dev/null rm -rf "$WORK" } trap cleanup EXIT cd "$WORK" pass=0 fail=0 check() { local name=$1 shift if "$@" >/dev/null 2>&1; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "FAIL: $name"; fi } jq_() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } g() { git -c user.name=Ada -c user.email=ada@example.com -c init.defaultBranch=main -c commit.gpgsign=false "$@"; } # A git repository with files, an executable, a link, a big file, folders, # a merge and a tag. g init -q src ( cd src printf '# Shop\n' > README.md && mkdir -p src && printf 'fn main() {}\n' > src/main.rs g add -A && g commit -qm "First commit" -m "With a body." printf '#!/bin/sh\necho hi\n' > run.sh && chmod +x run.sh && ln -s README.md link g add -A && g commit -qm "Script and link" head -c 3000000 /dev/urandom > big.bin && mkdir -p a/b && printf 'deep\n' > a/b/c.txt g add -A && g commit -qm "Big file and folders" g checkout -qb side && printf 'side\n' > side.txt && g add -A && g commit -qm "Side work" && g checkout -q main printf '# Shop\n\nSells things.\n' > README.md && g rm -q src/main.rs g add -A && g commit -qm "Readme, no main.rs" g merge -q --no-ff side -m "Merge side" g tag v1 g checkout -qb feature && printf 'feature\n' > feature.txt && printf 'deeper\n' > a/b/c.txt g add -A && g commit -qm "Feature" -m "Adds feature.txt." && g checkout -q main ) COMMITS=$(git -C src rev-list --first-parent --count main) check "import" "$VCS" import src shop check "a version per first-parent commit" test "$("$VCS" --json -C shop log -n 1 | jq_ 'd["versions"][0]["n"]')" = "$COMMITS" check "last title" sh -c "'$VCS' -C shop log -n 1 | grep -q 'Merge side'" mkdir -p want && git -C src archive main | tar -x -C want check "files match git" diff -r --exclude=.vcs want shop check "executable kept" test -x shop/run.sh check "link kept" test -L shop/link check "big file intact" cmp src/big.bin shop/big.bin check "first version has its body" sh -c "'$VCS' -C shop log 1 | grep -q 'With a body.'" # Native work on top, then a git branch comes in as a change and lands. printf 'native\n' > shop/native.txt check "native land" "$VCS" -C shop land -m "Native work" check "branch comes in as a change" "$VCS" -C shop import ../src --change feature check "it's the workspace's change" sh -c "'$VCS' -C shop status | grep -q Feature" check "workspace has the branch" test -f shop/feature.txt check "branch lands" "$VCS" -C shop land check "sync" "$VCS" -C shop sync check "main has both" sh -c "test -f shop/native.txt && test -f shop/feature.txt && grep -q deeper shop/a/b/c.txt" check "unknown branch refused" sh -c "! '$VCS' -C shop import ../src --change nope" # The hosted git view. mkdir -p gitroot/acme git init -q --bare gitroot/acme/shop.git python3 "$ROOT/tests/git-server.py" "$WORK/gitroot" "$GPORT" & GIT_PID=$! for _ in $(seq 100); do curl -s -o /dev/null "http://127.0.0.1:$GPORT/acme/shop.git/info/refs?service=git-upload-pack" && break; sleep 0.1; done kill -0 "$GIT_PID" 2>/dev/null || { echo "git server didn't start (port $GPORT)"; exit 1; } ALICE=vcs_test_alice_0123456789 USERS="{\"$ALICE\":{\"handle\":\"alice\",\"name\":\"Alice\",\"email\":\"alice@acme.test\"}}" WRANGLER_SEND_METRICS=false setsid "$APP/node_modules/.bin/wrangler" dev --cwd "$APP" --name "vcs-test-$PORT" --port "$PORT" --ip 127.0.0.1 --inspector-port "$((PORT + 1))" \ --persist-to "$WORK/wrangler" --var AUTH_MODE:test --var "TEST_USERS:$USERS" --var "GIT_BASE:http://127.0.0.1:$GPORT" >"$WORK/worker.log" 2>&1 & WORKER_PID=$! for _ in $(seq 300); do curl -s "http://$HOST/.well-known/vcs.json" 2>/dev/null | grep -q api && break; sleep 0.2; done api() { curl -s -H "authorization: Bearer $ALICE" -H 'content-type: application/json' "$@"; } gv() { git --git-dir="$WORK/gitroot/acme/shop.git" "$@"; } wait_view() { # version number to wait for in the git view for _ in $(seq 100); do gv log -1 --format=%B main 2>/dev/null | grep -q "Vcs-Version: $1\$" && return 0; sleep 0.2; done return 1 } check "login" "$VCS" login "$HOST" --token "$ALICE" api -X POST -d '{"owner":"acme","name":"shop","git":"view"}' "http://$HOST/v1/projects" >/dev/null check "import and publish with history" "$VCS" import src hosted --to "$HOST/acme/shop" check "git history copied" test "$(gv rev-parse main)" = "$(git -C src rev-parse main)" check "tags copied" test "$(gv rev-parse v1)" = "$(git -C src rev-parse v1)" check "branches copied" test "$(gv rev-parse feature)" = "$(git -C src rev-parse feature)" N=$(("$COMMITS" + 1)) printf 'from vcs\n' > hosted/fromvcs.txt check "hosted land" "$VCS" -C hosted land -m "From vcs" -m "Landed natively." check "git view gets the version" wait_view "$N" check "commit follows the last git commit" test "$(gv rev-parse main~1)" = "$(git -C src rev-parse main)" check "commit has the file" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:fromvcs.txt | grep -q 'from vcs'" check "commit has the message" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' log -1 --format=%B main | grep -q 'Landed natively.'" check "git fsck passes" gv fsck --strict --no-dangling check "vcs git shows it" sh -c "'$VCS' -C hosted git | grep -q 'version $N is commit'" # Private paths stay out of the git view. check "make secret/ private" "$VCS" -C hosted access secret/ @alice mkdir -p hosted/secret && printf 'key\n' > hosted/secret/key.txt && printf 'open\n' > hosted/open.txt N=$((N + 1)) check "land a secret" "$VCS" -C hosted land -m "Secret and open" check "git view gets it" wait_view "$N" check "open file in the git view" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:open.txt | grep -q open" check "secret not in the git view" sh -c "! git --git-dir='$WORK/gitroot/acme/shop.git' cat-file -e main:secret/key.txt" check "secret still in vcs" test -f hosted/secret/key.txt # Someone pushes to git main directly: the view pauses, then resumes # keeping their commit. g clone -q "http://127.0.0.1:$GPORT/acme/shop.git" outside (cd outside && printf 'outside\n' > outside.txt && g add -A && g commit -qm "Pushed with git" && g push -q origin main) OUTSIDE=$(git -C outside rev-parse main) printf 'more\n' > hosted/more.txt N=$((N + 1)) check "land after the outside push" "$VCS" -C hosted land -m "More" sleep 3 check "git view paused" sh -c "'$VCS' -C hosted git | grep -q paused" check "outside commit untouched" test "$(gv rev-parse main)" = "$OUTSIDE" check "outside commit comes in as a change" "$VCS" -C hosted import "http://127.0.0.1:$GPORT/acme/shop.git" --change main check "and lands" "$VCS" -C hosted land N=$((N + 1)) check "resume" "$VCS" -C hosted git resume check "git view catches up" wait_view "$N" check "outside commit kept in git" gv merge-base --is-ancestor "$OUTSIDE" main check "has the outside file" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:outside.txt | grep -q outside" check "git fsck passes after" gv fsck --strict --no-dangling echo "git: $pass passed, $fail failed" [ "$fail" = 0 ]