Onset: version control where nobody waits and nothing gets lost. The on command, its core and onset-server.
| 6 | set -uo pipefail |
| 7 |
| 8 | BIN=${1:-$(cd "$(dirname "$0")/.." && pwd)/target/debug} |
| 9 | VCS=$BIN/vcs |
| 10 | SERVER=$BIN/vcs-server |
| 11 | WORK=$(mktemp -d) |
| 12 | PORT=$((20000 + RANDOM % 20000)) |
| 13 | HOST="127.0.0.1:$PORT" |
| 14 | SERVER_PID= |
| 15 | cleanup() { |
| 16 | [ -n "$SERVER_PID" ] && kill "$SERVER_PID" 2>/dev/null |
| 17 | rm -rf "$WORK" |
| 18 | } |
| 19 | trap cleanup EXIT |
| 20 | cd "$WORK" |
| 21 |
| 22 | start_server() { |
| 23 | "$SERVER" serve --data "$WORK/server" --listen "$HOST" --threads 32 2>"$WORK/server.log" & |
| 24 | SERVER_PID=$! |
| 25 | for _ in $(seq 50); do curl -s "http://$HOST/.well-known/vcs.json" >/dev/null && return; sleep 0.1; done |
| 26 | echo "server didn't start"; exit 1 |
| 27 | } |
| 28 |
| 29 | pass=0 |
| 30 | fail=0 |
| 31 | check() { |
| 32 | local name=$1 |
| 33 | shift |
| 34 | if "$@" >/dev/null 2>&1; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "FAIL: $name"; fi |
| 35 | } |
| 36 | code() { |
| 37 | local want=$1 |
| 38 | shift |
| 39 | "$@" >/dev/null 2>&1 |
| 40 | [ "$?" = "$want" ] |
| 41 | } |
| 42 | jq_() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } |
| 43 |
| 44 | # People and machines: each has a VCS_HOME. |
| 45 | as() { # who, command... |
| 46 | local who=$1 |
| 47 | shift |
| 48 | case $who in |
| 49 | laptop) VCS_HOME="$WORK/laptop" VCS_NAME="Alice" VCS_EMAIL="alice@acme.test" "$@" ;; |
| 50 | cloud) VCS_HOME="$WORK/cloud" VCS_NAME="Alice" VCS_EMAIL="alice@acme.test" "$@" ;; |
| 51 | bob) VCS_HOME="$WORK/bob" VCS_NAME="Bob" VCS_EMAIL="bob@acme.test" "$@" ;; |
| 52 | carol) VCS_HOME="$WORK/carol" VCS_NAME="Carol" VCS_EMAIL="carol@acme.test" "$@" ;; |
| 53 | esac |
| 54 | } |
| 55 |
| 56 | ALICE=$("$SERVER" user add alice --name Alice --email alice@acme.test --data "$WORK/server") |
| 57 | BOB=$("$SERVER" user add bob --name Bob --email bob@acme.test --data "$WORK/server") |
| 58 | CAROL=$("$SERVER" user add carol --name Carol --email carol@acme.test --data "$WORK/server") |
| 59 | start_server |
| 60 |
| 61 | check "laptop login" as laptop "$VCS" login "$HOST" --token "$ALICE" |
| 62 | check "cloud login" as cloud "$VCS" login "$HOST" --token "$ALICE" |
| 63 | check "bob login" as bob "$VCS" login "$HOST" --token "$BOB" |
| 64 | check "carol login" as carol "$VCS" login "$HOST" --token "$CAROL" |
| 65 |
| 66 | # Alice starts locally and publishes. |
| 67 | mkdir shop && cd shop |
| 68 | printf 'line 1\nline 2\nline 3\n' > shared.txt |
| 69 | printf 'SECRET=alice\n' > .env |
| 70 | check "init" as laptop "$VCS" init |
| 71 | check "first version" as laptop "$VCS" land -m "First version" |
| 72 | printf 'line 1\nline 2\nline 3\nline 4\n' > shared.txt |
| 73 | check "second version" as laptop "$VCS" land -m "Second version" |
| 74 | check "publish" as laptop "$VCS" publish "$HOST/acme/shop" |
| 75 | check "main is 2 on the host" test "$(curl -s -H "authorization: Bearer $ALICE" "http://$HOST/v1/projects/acme/shop" | jq_ 'd["main"]["n"]')" = 2 |
| 76 | "$SERVER" member add acme/shop bob --role member --data "$WORK/server" |
| 77 | "$SERVER" member add acme/shop carol --role reader --data "$WORK/server" |
| 78 |
| 79 | # Bob gets it. |
| 80 | cd "$WORK" |
| 81 | check "bob gets" as bob "$VCS" get "$HOST/acme/shop" bobshop |
| 82 | check "bob has the files" test "$(tail -1 bobshop/shared.txt)" = "line 4" |
| 83 | check "bob doesn't get alice's .env" test ! -e bobshop/.env |
| 84 |
| 85 | # Parallel landings from two people. |
| 86 | printf 'alice\n' > shop/alice.txt |
| 87 | printf 'bob\n' > bobshop/bob.txt |
| 88 | check "alice lands" as laptop "$VCS" -C shop land -m "Alice's file" |
| 89 | check "bob lands without syncing" as bob "$VCS" -C bobshop land -m "Bob's file" |
| 90 | check "main is 4" test "$(as bob "$VCS" --json -C bobshop log | jq_ 'd["versions"][0]["n"]')" = 4 |
| 91 |
| 92 | # A conflict between them. |
| 93 | as laptop "$VCS" -C shop sync >/dev/null 2>&1 |
| 94 | as bob "$VCS" -C bobshop sync >/dev/null 2>&1 |
| 95 | sed -i 's/^line 2$/line two (alice)/' shop/shared.txt |
| 96 | sed -i 's/^line 2$/line two (bob)/' bobshop/shared.txt |
| 97 | check "alice lands line 2" as laptop "$VCS" -C shop land -m "Alice's line 2" |
| 98 | check "bob conflicts (exit 3)" code 3 as bob "$VCS" -C bobshop land -m "Bob's line 2" |
| 99 | check "bob syncs (exit 3)" code 3 as bob "$VCS" -C bobshop sync |
| 100 | check "markers" grep -q '<<<<<<< main' bobshop/shared.txt |
| 101 | sed -i '/^<<<<<<< /d; /^=======$/d; /^>>>>>>> /d; /line two (alice)/d' bobshop/shared.txt |
| 102 | check "bob lands after fixing" as bob "$VCS" -C bobshop land |
| 103 |
| 104 | # A reader's change waits for someone who can land it. |
| 105 | check "carol gets" as carol "$VCS" get "$HOST/acme/shop" carolshop |
| 106 | printf 'fix from carol\n' > carolshop/carol.txt |
| 107 | check "carol's land waits (exit 5)" code 5 as carol "$VCS" -C carolshop land -m "Carol's fix" |
| 108 | CAROL_CHANGE=$(as carol "$VCS" --json -C carolshop log --changes | jq_ '[c["change"]["id"] for c in d["changes"] if c["change"]["state"]=="waiting"][0]') |
| 109 | check "alice sees the waiting change" sh -c "VCS_HOME=$WORK/laptop $VCS -C $WORK/shop log --changes | grep -q waiting" |
| 110 | as laptop "$VCS" -C shop land "$CAROL_CHANGE" >"$WORK/carol-land.out" 2>&1; cat "$WORK/carol-land.out" >&2 |
| 111 | check "alice lands carol's change" grep -q landed "$WORK/carol-land.out" |
| 112 | check "carol is the author" test "$(as laptop "$VCS" --json -C shop log -n 1 | jq_ 'd["versions"][0]["author"].split(" <")[0]')" = Carol |
| 113 |
| 114 | # Alice moves from her laptop to a cloud machine mid-change. |
| 115 | as laptop "$VCS" -C shop sync >/dev/null 2>&1 |
| 116 | printf 'started on the laptop\n' > shop/feature.txt |
| 117 | check "describe on laptop" as laptop "$VCS" -C shop describe "Feature" |
| 118 | FEATURE=$(as laptop "$VCS" --json -C shop status | jq_ 'd["change"]["id"]') |
| 119 | check "cloud picks it up" as cloud "$VCS" get "$HOST/acme/shop" cloudshop --change "$FEATURE" |
| 120 | check "the edit came along" test "$(cat cloudshop/feature.txt)" = "started on the laptop" |
| 121 | check ".env came along" test "$(cat cloudshop/.env)" = "SECRET=alice" |
| 122 | printf 'continued in the cloud\n' >> cloudshop/feature.txt |
| 123 | check "cloud saves" as cloud "$VCS" -C cloudshop status |
| 124 | printf 'late laptop edit\n' > shop/late.txt |
| 125 | OUT=$(as laptop "$VCS" --json -C shop status 2>/dev/null) |
| 126 | check "laptop's late edit is rescued into a new change" sh -c "echo '$OUT' | grep -q 'Rescued from'" |
| 127 | check "cloud lands the feature" as cloud "$VCS" -C cloudshop land |
| 128 | check "feature has both edits" test "$(as bob "$VCS" -C bobshop sync >/dev/null 2>&1; cat bobshop/feature.txt | wc -l)" = 2 |
| 129 |
| 130 | # Sharing a change. |
| 131 | printf 'draft\n' > bobshop/draft.txt |
| 132 | check "bob describes" as bob "$VCS" -C bobshop describe "Draft for Alice" |
| 133 | DRAFT=$(as bob "$VCS" --json -C bobshop status | jq_ 'd["change"]["id"]') |
| 134 | check "bob shares it" as bob "$VCS" -C bobshop access "$DRAFT" @alice |
| 135 | check "alice sees it" sh -c "VCS_HOME=$WORK/laptop $VCS -C $WORK/shop log --changes | grep -q 'Draft for Alice'" |
| 136 |
| 137 | # Undo a hosted land. |
| 138 | as cloud "$VCS" -C cloudshop sync >/dev/null 2>&1 |
| 139 | printf 'oops\n' > cloudshop/oops.txt |
| 140 | check "cloud lands oops" as cloud "$VCS" -C cloudshop land -m "Oops" |
| 141 | check "cloud undoes it" as cloud "$VCS" -C cloudshop undo |
| 142 | check "main took it back" sh -c "VCS_HOME=$WORK/bob $VCS -C $WORK/bobshop log -n 1 | grep -q 'Take back'" |
| 143 |
| 144 | # Offline: saves stay local, land says offline, later it all goes up. |
| 145 | kill "$SERVER_PID"; wait "$SERVER_PID" 2>/dev/null; SERVER_PID= |
| 146 | printf 'written offline\n' > cloudshop/offline.txt |
| 147 | check "status works offline" as cloud "$VCS" -C cloudshop status |
| 148 | check "land says offline (exit 6)" code 6 as cloud "$VCS" -C cloudshop land -m "Offline work" |
| 149 | start_server |
| 150 | check "land works once back" as cloud "$VCS" -C cloudshop land -m "Offline work" |
| 151 |
| 152 | # 32 agents land at once on one main, none refused. |
| 153 | BEFORE=$(as bob "$VCS" --json -C bobshop log -n 1 | jq_ 'd["versions"][0]["n"]') |
| 154 | for i in $(seq 1 32); do |
| 155 | as bob "$VCS" get "$HOST/acme/shop" "agent$i" >/dev/null 2>&1 |
| 156 | printf 'agent %s\n' "$i" > "agent$i/agent-$i.txt" |
| 157 | done |
| 158 | pids=() |
| 159 | for i in $(seq 1 32); do |
| 160 | (VCS_HOME="$WORK/bob" VCS_NAME="Bob" VCS_AGENT="agent-$i" "$VCS" -C "agent$i" land -m "Agent $i" >"$WORK/land$i.out" 2>&1; echo $? >"$WORK/land$i.code") & |
| 161 | pids+=($!) |
| 162 | done |
| 163 | wait "${pids[@]}" |
| 164 | OK=$(grep -l '^0$' "$WORK"/land*.code | wc -l) |
| 165 | check "all 32 landed" test "$OK" = 32 |
| 166 | AFTER=$(as bob "$VCS" --json -C bobshop log -n 1 | jq_ 'd["versions"][0]["n"]') |
| 167 | check "main moved by 32" test "$((AFTER - BEFORE))" = 32 |
| 168 |
| 169 | echo "hosted: $pass passed, $fail failed" |
| 170 | [ "$fail" = 0 ] |
| 171 |