Onset: version control where nobody waits and nothing gets lost. The on command, its core and onset-server.
| 1 | #!/usr/bin/env bash |
| 2 | # The git bridge end to end: importing a repository's history, bringing a |
| 3 | # branch in as a change, publishing with the original git history, and the |
| 4 | # hosted git view (apps/vcs under `wrangler dev`, pushing to a local git |
| 5 | # server that stands in for git.coline.app): new versions as commits, |
| 6 | # private paths left out, a push from outside pausing it, and resuming. |
| 7 | # Usage: tests/git.sh [path/to/target/debug] |
| 8 | set -uo pipefail |
| 9 |
| 10 | ROOT=$(cd "$(dirname "$0")/.." && pwd) |
| 11 | BIN=${1:-$ROOT/target/debug} |
| 12 | VCS=$BIN/vcs |
| 13 | APP=$ROOT/../apps/vcs |
| 14 | WORK=$(mktemp -d) |
| 15 | export VCS_SAVER=0 VCS_HOME=$WORK/home VCS_NAME=Alice VCS_EMAIL=alice@acme.test |
| 16 | PORT=$((20000 + RANDOM % 20000)) |
| 17 | GPORT=$((PORT + 2)) |
| 18 | HOST="127.0.0.1:$PORT" |
| 19 | WORKER_PID= |
| 20 | GIT_PID= |
| 21 | cleanup() { |
| 22 | [ -n "$WORKER_PID" ] && kill -- "-$WORKER_PID" 2>/dev/null |
| 23 | [ -n "$GIT_PID" ] && kill "$GIT_PID" 2>/dev/null |
| 24 | wait 2>/dev/null |
| 25 | rm -rf "$WORK" |
| 26 | } |
| 27 | trap cleanup EXIT |
| 28 | cd "$WORK" |
| 29 |
| 30 | pass=0 |
| 31 | fail=0 |
| 32 | check() { |
| 33 | local name=$1 |
| 34 | shift |
| 35 | if "$@" >/dev/null 2>&1; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "FAIL: $name"; fi |
| 36 | } |
| 37 | jq_() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } |
| 38 | g() { git -c user.name=Ada -c user.email=ada@example.com -c init.defaultBranch=main -c commit.gpgsign=false "$@"; } |
| 39 |
| 40 | # A git repository with files, an executable, a link, a big file, folders, |
| 41 | # a submodule, a merge and a tag. |
| 42 | g init -q lib |
| 43 | (cd lib && printf 'lib\n' > lib.txt && g add -A && g commit -qm "Lib") |
| 44 | g init -q src |
| 45 | ( |
| 46 | cd src |
| 47 | printf '# Shop\n' > README.md && mkdir -p src && printf 'fn main() {}\n' > src/main.rs |
| 48 | g add -A && g commit -qm "First commit" -m "With a body." |
| 49 | printf '#!/bin/sh\necho hi\n' > run.sh && chmod +x run.sh && ln -s README.md link |
| 50 | g add -A && g commit -qm "Script and link" |
| 51 | head -c 3000000 /dev/urandom > big.bin && mkdir -p a/b && printf 'deep\n' > a/b/c.txt |
| 52 | g -c protocol.file.allow=always submodule add -q ../lib vendor/lib >/dev/null 2>&1 |
| 53 | g add -A && g commit -qm "Big file, folders and a submodule" |
| 54 | g checkout -qb side && printf 'side\n' > side.txt && g add -A && g commit -qm "Side work" && g checkout -q main |
| 55 | printf '# Shop\n\nSells things.\n' > README.md && g rm -q src/main.rs |
| 56 | g add -A && g commit -qm "Readme, no main.rs" |
| 57 | g merge -q --no-ff side -m "Merge side" |
| 58 | g tag v1 |
| 59 | g checkout -qb feature && printf 'feature\n' > feature.txt && printf 'deeper\n' > a/b/c.txt |
| 60 | g add -A && g commit -qm "Feature" -m "Adds feature.txt." && g checkout -q main |
| 61 | ) |
| 62 | COMMITS=$(git -C src rev-list --first-parent --count main) |
| 63 |
| 64 | check "import" "$VCS" import src shop |
| 65 | check "a version per first-parent commit" test "$("$VCS" --json -C shop log -n 1 | jq_ 'd["versions"][0]["n"]')" = "$COMMITS" |
| 66 | check "last title" sh -c "'$VCS' -C shop log -n 1 | grep -q 'Merge side'" |
| 67 | mkdir -p want && git -C src archive main | tar -x -C want |
| 68 | check "files match git" diff -r --exclude=.vcs --exclude=lib want shop |
| 69 | LIB=$(git -C src rev-parse main:vendor/lib) |
| 70 | check "the submodule is an empty folder" sh -c "test -d shop/vendor/lib && test -z \"\$(ls -A shop/vendor/lib)\"" |
| 71 | check "and nothing to save" sh -c "'$VCS' --json -C shop status | python3 -c 'import json,sys; d=json.load(sys.stdin); assert not d.get(\"files\"), d.get(\"files\")'" |
| 72 | check "executable kept" test -x shop/run.sh |
| 73 | check "link kept" test -L shop/link |
| 74 | check "big file intact" cmp src/big.bin shop/big.bin |
| 75 | check "first version has its body" sh -c "'$VCS' -C shop log 1 | grep -q 'With a body.'" |
| 76 |
| 77 | # Native work on top, then a git branch comes in as a change and lands. |
| 78 | printf 'native\n' > shop/native.txt |
| 79 | check "native land" "$VCS" -C shop land -m "Native work" |
| 80 | check "branch comes in as a change" "$VCS" -C shop import ../src --change feature |
| 81 | check "it's the workspace's change" sh -c "'$VCS' -C shop status | grep -q Feature" |
| 82 | check "workspace has the branch" test -f shop/feature.txt |
| 83 | check "branch lands" "$VCS" -C shop land |
| 84 | check "sync" "$VCS" -C shop sync |
| 85 | check "main has both" sh -c "test -f shop/native.txt && test -f shop/feature.txt && grep -q deeper shop/a/b/c.txt" |
| 86 | check "unknown branch refused" sh -c "! '$VCS' -C shop import ../src --change nope" |
| 87 |
| 88 | # The hosted git view. |
| 89 | mkdir -p gitroot/acme |
| 90 | git init -q --bare gitroot/acme/shop.git |
| 91 | python3 "$ROOT/tests/git-server.py" "$WORK/gitroot" "$GPORT" & |
| 92 | GIT_PID=$! |
| 93 | 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 |
| 94 | kill -0 "$GIT_PID" 2>/dev/null || { echo "git server didn't start (port $GPORT)"; exit 1; } |
| 95 | ALICE=vcs_test_alice_0123456789 |
| 96 | USERS="{\"$ALICE\":{\"handle\":\"alice\",\"name\":\"Alice\",\"email\":\"alice@acme.test\"}}" |
| 97 | 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))" \ |
| 98 | --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 & |
| 99 | WORKER_PID=$! |
| 100 | for _ in $(seq 300); do curl -s "http://$HOST/.well-known/vcs.json" 2>/dev/null | grep -q api && break; sleep 0.2; done |
| 101 | api() { curl -s -H "authorization: Bearer $ALICE" -H 'content-type: application/json' "$@"; } |
| 102 | gv() { git --git-dir="$WORK/gitroot/acme/shop.git" "$@"; } |
| 103 | wait_view() { # version number to wait for in the git view |
| 104 | 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 |
| 105 | return 1 |
| 106 | } |
| 107 |
| 108 | check "login" "$VCS" login "$HOST" --token "$ALICE" |
| 109 | api -X POST -d '{"owner":"acme","name":"shop","git":"view"}' "http://$HOST/v1/projects" >/dev/null |
| 110 | check "import and publish with history" "$VCS" import src hosted --to "$HOST/acme/shop" |
| 111 | check "git history copied" test "$(gv rev-parse main)" = "$(git -C src rev-parse main)" |
| 112 | check "tags copied" test "$(gv rev-parse v1)" = "$(git -C src rev-parse v1)" |
| 113 | check "branches copied" test "$(gv rev-parse feature)" = "$(git -C src rev-parse feature)" |
| 114 |
| 115 | N=$(("$COMMITS" + 1)) |
| 116 | printf 'from vcs\n' > hosted/fromvcs.txt |
| 117 | check "hosted land" "$VCS" -C hosted land -m "From vcs" -m "Landed natively." |
| 118 | check "git view gets the version" wait_view "$N" |
| 119 | check "commit follows the last git commit" test "$(gv rev-parse main~1)" = "$(git -C src rev-parse main)" |
| 120 | check "commit has the file" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:fromvcs.txt | grep -q 'from vcs'" |
| 121 | check "commit has the message" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' log -1 --format=%B main | grep -q 'Landed natively.'" |
| 122 | check "git fsck passes" gv fsck --strict --no-dangling |
| 123 | check "the submodule is still in git" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' ls-tree main vendor/lib | grep -q '^160000 commit $LIB'" |
| 124 | check "vcs git shows it" sh -c "'$VCS' -C hosted git | grep -q 'version $N is commit'" |
| 125 |
| 126 | # Private paths stay out of the git view. |
| 127 | check "make secret/ private" "$VCS" -C hosted access secret/ @alice |
| 128 | mkdir -p hosted/secret && printf 'key\n' > hosted/secret/key.txt && printf 'open\n' > hosted/open.txt |
| 129 | N=$((N + 1)) |
| 130 | check "land a secret" "$VCS" -C hosted land -m "Secret and open" |
| 131 | check "git view gets it" wait_view "$N" |
| 132 | check "open file in the git view" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:open.txt | grep -q open" |
| 133 | check "secret not in the git view" sh -c "! git --git-dir='$WORK/gitroot/acme/shop.git' cat-file -e main:secret/key.txt" |
| 134 | check "secret still in vcs" test -f hosted/secret/key.txt |
| 135 |
| 136 | # Someone pushes to git main directly: the view pauses, then resumes |
| 137 | # keeping their commit. |
| 138 | g clone -q "http://127.0.0.1:$GPORT/acme/shop.git" outside |
| 139 | (cd outside && printf 'outside\n' > outside.txt && g add -A && g commit -qm "Pushed with git" && g push -q origin main) |
| 140 | OUTSIDE=$(git -C outside rev-parse main) |
| 141 | printf 'more\n' > hosted/more.txt |
| 142 | N=$((N + 1)) |
| 143 | check "land after the outside push" "$VCS" -C hosted land -m "More" |
| 144 | sleep 3 |
| 145 | check "git view paused" sh -c "'$VCS' -C hosted git | grep -q paused" |
| 146 | check "outside commit untouched" test "$(gv rev-parse main)" = "$OUTSIDE" |
| 147 | check "outside commit comes in as a change" "$VCS" -C hosted import "http://127.0.0.1:$GPORT/acme/shop.git" --change main |
| 148 | check "and lands" "$VCS" -C hosted land |
| 149 | N=$((N + 1)) |
| 150 | check "resume" "$VCS" -C hosted git resume |
| 151 | check "git view catches up" wait_view "$N" |
| 152 | check "outside commit kept in git" gv merge-base --is-ancestor "$OUTSIDE" main |
| 153 | check "has the outside file" sh -c "git --git-dir='$WORK/gitroot/acme/shop.git' show main:outside.txt | grep -q outside" |
| 154 | check "git fsck passes after" gv fsck --strict --no-dangling |
| 155 |
| 156 | # A team that's left git behind turns the git view off; on again catches up. |
| 157 | check "git view off" sh -c "'$VCS' -C hosted settings git=off | grep -q 'off:'" |
| 158 | AT=$(gv rev-parse main) |
| 159 | for i in 1 2 3; do |
| 160 | printf '%s\n' "$i" > "hosted/off-$i.txt" |
| 161 | N=$((N + 1)) |
| 162 | check "land $i while off" "$VCS" -C hosted land -m "Off $i" |
| 163 | done |
| 164 | sleep 2 |
| 165 | check "git main didn't move" test "$(gv rev-parse main)" = "$AT" |
| 166 | check "git view on" "$VCS" -C hosted settings git=on |
| 167 | check "it catches up" wait_view "$N" |
| 168 | check "a commit per version" test "$(gv rev-list --count "$AT"..main)" = 3 |
| 169 | check "git fsck passes at the end" gv fsck --strict --no-dangling |
| 170 |
| 171 | echo "git: $pass passed, $fail failed" |
| 172 | [ "$fail" = 0 ] |
| 173 |