Onset: version control where nobody waits and nothing gets lost. The on command, its core and onset-server.
| 6 | VCS=${1:-$(cd "$(dirname "$0")/.." && pwd)/target/debug/on} |
| 7 | WORK=$(mktemp -d) |
| 8 | export ONSET_HOME="$WORK/home" ONSET_NAME="Test Person" ONSET_EMAIL="test@example.com" ONSET_SAVER=0 |
| 9 | # Workspaces' .git views have tests of their own (below); elsewhere their |
| 10 | # background updates would only race the suite's own folder changes. |
| 11 | export ONSET_GIT=0 |
| 12 | # Run from inside an agent, its own session would be kept with the tests' lands. |
| 13 | unset CLAUDE_CODE_SESSION_ID CLAUDECODE CLAUDE_CONFIG_DIR ONSET_SESSION_FILE ONSET_SESSIONS VCS_SESSION_FILE VCS_SESSIONS VCS_HOME VCS_TOKEN |
| 14 | export CODEX_HOME="$WORK/no-codex" |
| 15 | trap 'rm -rf "$WORK"' EXIT |
| 16 | cd "$WORK" |
| 17 |
| 18 | pass=0 |
| 19 | fail=0 |
| 20 | check() { # name, command... |
| 21 | local name=$1 |
| 22 | shift |
| 23 | if "$@" >/dev/null 2>&1; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "FAIL: $name"; fi |
| 24 | } |
| 25 | code() { # expected exit code, command... |
| 26 | local want=$1 |
| 27 | shift |
| 28 | "$@" >/dev/null 2>&1 |
| 29 | [ "$?" = "$want" ] |
| 30 | } |
| 31 | json() { "$VCS" --json "$@" 2>/dev/null; } |
| 32 | # A path as on and other native programs see it: on Windows, bash's /d/a/x is D:/a/x. |
| 33 | native() { if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else printf '%s' "$1"; fi; } |
| 34 |
| 35 | # A new project with a first version. |
| 36 | mkdir app && cd app |
| 37 | printf 'hello\n' > README.md |
| 38 | mkdir src && printf 'a\nb\nc\nd\ne\nf\ng\n' > src/lib.txt |
| 39 | printf '{\n "name": "app"\n}\n' > package.json |
| 40 | printf 'SECRET=1\n' > .env |
| 41 | check "init" "$VCS" init |
| 42 | check "land first version" "$VCS" land -m "First version" |
| 43 | check "main is version 1" test "$(json log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 1 |
| 44 | check ".env never landed" test -z "$(json log 1 | python3 -c 'import json,sys; print([f for f in json.load(sys.stdin)["files"] if ".env" in f] or "")')" |
| 45 | check "empty land refused (exit 1)" code 1 "$VCS" land -m "Nothing" |
| 46 |
| 47 | # A second workspace gets the files and the .env. |
| 48 | cd "$WORK" |
| 49 | check "get second workspace" "$VCS" get ./app app2 |
| 50 | check "files arrived" test "$(cat app2/src/lib.txt | head -1)" = a |
| 51 | check ".env came along" test "$(cat app2/.env)" = "SECRET=1" |
| 52 |
| 53 | # Parallel edits to different files both land, in any order. |
| 54 | printf 'hello there\n' > app/README.md |
| 55 | printf 'a\nb\nc\nd\ne\nf\nG\n' > app2/src/lib.txt |
| 56 | check "land from app" "$VCS" -C app land -m "Greet better" |
| 57 | check "land from app2 without syncing" "$VCS" -C app2 land -m "Capital G" |
| 58 | check "main is version 3" test "$(json -C app log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 |
| 59 |
| 60 | # JSON files merge by key. |
| 61 | cd "$WORK" && "$VCS" -C app sync >/dev/null 2>&1 && "$VCS" -C app2 sync >/dev/null 2>&1 |
| 62 | printf '{\n "name": "app",\n "version": "1.0.0"\n}\n' > app/package.json |
| 63 | printf '{\n "name": "app",\n "license": "MIT"\n}\n' > app2/package.json |
| 64 | check "land json key A" "$VCS" -C app land -m "Add version" |
| 65 | check "land json key B (merged by key)" "$VCS" -C app2 land -m "Add license" |
| 66 | "$VCS" -C app sync >/dev/null 2>&1 |
| 67 | check "both keys on main" python3 -c 'import json; d=json.load(open("app/package.json")); assert d["version"]=="1.0.0" and d["license"]=="MIT"' |
| 68 |
| 69 | # A real conflict: land refuses, sync marks it, fixing it lands. |
| 70 | "$VCS" -C app2 sync >/dev/null 2>&1 |
| 71 | perl -pi -e 's/^b$/B from app/' app/src/lib.txt |
| 72 | perl -pi -e 's/^b$/B from app2/' app2/src/lib.txt |
| 73 | check "land A" "$VCS" -C app land -m "B from app" |
| 74 | check "land B conflicts (exit 3)" code 3 "$VCS" -C app2 land -m "B from app2" |
| 75 | check "sync leaves markers (exit 3)" code 3 "$VCS" -C app2 sync |
| 76 | check "markers in file" grep -q '<<<<<<< main' app2/src/lib.txt |
| 77 | check "land refused while markers remain" code 3 "$VCS" -C app2 land |
| 78 | python3 - "$WORK/app2/src/lib.txt" <<'EOF' |
| 79 | import sys, re |
| 80 | p = sys.argv[1] |
| 81 | t = open(p).read() |
| 82 | t = re.sub(r"<<<<<<< [^\n]*\n(.*?)=======\n(.*?)>>>>>>> [^\n]*\n", lambda m: "B from both\n", t, flags=re.S) |
| 83 | open(p, "w", newline="\n").write(t) |
| 84 | EOF |
| 85 | check "land after fixing (reuses description)" "$VCS" -C app2 land |
| 86 | check "status clean after land" test "$(json -C app2 status | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["files"]))')" = 0 |
| 87 |
| 88 | # A file's history, and the version behind each line. |
| 89 | check "log of a file" test "$(json -C app log src/lib.txt | python3 -c 'import json,sys; print(",".join(str(v["n"]) for v in json.load(sys.stdin)["versions"]))')" = 7,6,3,1 |
| 90 | check "log of a folder" sh -c "\"$VCS\" -C app log src | grep -q 'Capital G'" |
| 91 | check "log of a file from inside its folder" sh -c "cd app/src && \"$VCS\" log lib.txt | grep -q 'First version'" |
| 92 | check "blame" test "$(json -C app2 blame src/lib.txt | python3 -c 'import json,sys; print(",".join(str(l["version"]) for l in json.load(sys.stdin)["lines"]))')" = 1,7,1,1,1,1,3 |
| 93 | check "blame of a missing file fails" sh -c "! \"$VCS\" -C app blame nope.txt" |
| 94 | check "show a file in main" sh -c "\"$VCS\" -C app show src/lib.txt | grep -q 'B from both'" |
| 95 | check "show a file at a version" test "$("$VCS" -C app show src/lib.txt --from 1 | tail -1)" = g |
| 96 | check "show a missing file fails" sh -c "! \"$VCS\" -C app show nope.txt" |
| 97 | check "completions" sh -c "\"$VCS\" completions bash | grep -q _on" |
| 98 |
| 99 | # Tags: names for versions. |
| 100 | check "tag main's latest" "$VCS" -C app tag v1.0 |
| 101 | check "tag an older version" "$VCS" -C app tag first 1 |
| 102 | check "the list has them" sh -c "\"$VCS\" -C app tag | grep -q 'v1.0' && \"$VCS\" -C app tag | grep -q first" |
| 103 | check "show at a tag" test "$("$VCS" -C app show src/lib.txt --from first | tail -1)" = g |
| 104 | check "log of a tag" sh -c "\"$VCS\" -C app log first | grep -q 'First version'" |
| 105 | check "a tag stays on its version (exit 3)" code 3 "$VCS" -C app tag first 2 |
| 106 | check "a number can't be a tag (exit 2)" code 2 "$VCS" -C app tag 12 |
| 107 | check "remove a tag" "$VCS" -C app tag first --remove |
| 108 | check "it's gone" sh -c "! \"$VCS\" -C app tag | grep -q first" |
| 109 |
| 110 | # Cleaning up: a dropped change's saves and objects go; what's kept still works. |
| 111 | cd "$WORK/app" |
| 112 | "$VCS" sync >/dev/null 2>&1 |
| 113 | head -c 200000 /dev/urandom > scratch.bin |
| 114 | "$VCS" status >/dev/null 2>&1 |
| 115 | "$VCS" drop >/dev/null 2>&1 |
| 116 | check "gc --dry-run says what would go" sh -c "ONSET_GC_FRESH=0 '$VCS' gc --older 0s --dry-run | grep -q 'would remove'" |
| 117 | check "gc removes the dropped change's objects" test "$(ONSET_GC_FRESH=0 json gc --older 0s | python3 -c 'import json,sys; print(json.load(sys.stdin)["objects"] > 0)')" = True |
| 118 | check "main still reads" sh -c "\"$VCS\" show src/lib.txt | grep -q 'B from both'" |
| 119 | check "history still reads" sh -c "\"$VCS\" log src/lib.txt | grep -q 'First version'" |
| 120 | check "nothing is damaged" "$VCS" check |
| 121 | check "a second gc has nothing left" test "$(ONSET_GC_FRESH=0 json gc --older 0s | python3 -c 'import json,sys; print(json.load(sys.stdin)["objects"])')" = 0 |
| 122 | # Packs: gc moves loose objects into one file, and everything still reads. |
| 123 | LOOSE_BEFORE=$(find "$ONSET_HOME/objects" -path '*/packs' -prune -o -type f -print | wc -l | tr -d ' ') |
| 124 | ONSET_GC_FRESH=0 ONSET_PACK_AT=1 "$VCS" gc >/dev/null 2>&1 |
| 125 | check "gc packs loose objects" test "$(find "$ONSET_HOME/objects" -path '*/packs' -prune -o -type f -print | wc -l | tr -d ' ')" -lt "$LOOSE_BEFORE" |
| 126 | check "into a pack and its index" sh -c "ls '$ONSET_HOME/objects/packs/' | grep -q '\.idx$'" |
| 127 | check "packed files read" sh -c "\"$VCS\" show src/lib.txt | grep -q 'B from both'" |
| 128 | check "packed history reads" sh -c "\"$VCS\" log src/lib.txt | grep -q 'First version'" |
| 129 | check "packed objects check out" sh -c "\"$VCS\" check | grep -q 'each matching its ID'" |
| 130 | check "a new workspace comes from packed objects" sh -c "cd '$WORK' && '$VCS' get ./app app-packed >/dev/null && grep -q 'B from both' app-packed/src/lib.txt" |
| 131 | # A packed object nothing needs goes (its pack is written again without it). |
| 132 | head -c 100000 /dev/urandom > scratch2.bin |
| 133 | "$VCS" status >/dev/null 2>&1 |
| 134 | ONSET_GC_FRESH=0 ONSET_PACK_AT=1 "$VCS" gc >/dev/null 2>&1 |
| 135 | "$VCS" drop >/dev/null 2>&1 |
| 136 | check "gc removes packed objects nothing needs" test "$(ONSET_GC_FRESH=0 json gc --older 0s | python3 -c 'import json,sys; print(json.load(sys.stdin)["objects"] > 0)')" = True |
| 137 | check "and what's kept still checks out" sh -c "\"$VCS\" check | grep -q 'each matching its ID'" |
| 138 | cd "$WORK" |
| 139 |
| 140 | # Undo a land: main gets a version that takes it back out. |
| 141 | cd "$WORK/app" |
| 142 | "$VCS" sync >/dev/null 2>&1 |
| 143 | printf 'oops\n' > oops.txt |
| 144 | check "land oops" "$VCS" land -m "Oops" |
| 145 | check "undo the land" "$VCS" undo |
| 146 | check "oops is open again" test "$(json status | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"]["state"])')" = open |
| 147 | check "main took it back" test "$(json log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["title"][:13])')" = "Take back mai" |
| 148 | check "drop it" "$VCS" drop |
| 149 | check "undo the drop" "$VCS" undo |
| 150 | check "drop again" "$VCS" drop |
| 151 |
| 152 | # Restore a file from an earlier save. |
| 153 | printf 'v1\n' > notes.txt |
| 154 | "$VCS" status >/dev/null 2>&1 |
| 155 | sleep 3 |
| 156 | printf 'broken\n' > notes.txt |
| 157 | "$VCS" status >/dev/null 2>&1 |
| 158 | check "restore --at" "$VCS" restore notes.txt --at "2s ago" |
| 159 | check "notes is v1" test "$(cat notes.txt)" = v1 |
| 160 | check "undo the restore" "$VCS" undo |
| 161 | check "notes is broken again" test "$(cat notes.txt)" = broken |
| 162 | check "restore to base (removes the new file)" "$VCS" restore notes.txt |
| 163 | check "notes gone" test ! -e notes.txt |
| 164 |
| 165 | # New, switch, stacks. |
| 166 | printf 'feature a\n' > a.txt |
| 167 | check "describe" "$VCS" describe "Feature A" |
| 168 | A=$(json status | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"]["id"])') |
| 169 | check "new stacked on A" "$VCS" new "Feature B" --on "$A" |
| 170 | printf 'feature b\n' > b.txt |
| 171 | check "a.txt visible in B" test -e a.txt |
| 172 | check "switch back to A" "$VCS" switch "${A:0:6}" |
| 173 | check "b.txt gone in A" test ! -e b.txt |
| 174 | check "undo switch" "$VCS" undo |
| 175 | check "b.txt back" test -e b.txt |
| 176 | check "land B lands A then B" "$VCS" land |
| 177 | check "main has both" test "$(json log -n 2 | python3 -c 'import json,sys; print(",".join(v["title"] for v in json.load(sys.stdin)["versions"]))')" = "Feature B,Feature A" |
| 178 |
| 179 | # Secrets don't land. |
| 180 | printf 'const key = "sk_live_%s";\n' "$(printf 'x%.0s' {1..24})" > pay.js |
| 181 | check "secret refused (exit 8)" code 8 "$VCS" land -m "Payments" |
| 182 | rm pay.js |
| 183 | printf 'const key = process.env.STRIPE_KEY;\n' > pay.js |
| 184 | check "lands without the secret" "$VCS" land -m "Payments" |
| 185 |
| 186 | # onset.toml [land] run: the workspace's own checks before it lands. |
| 187 | printf '[land]\nrun = ["exit 1"]\n' > onset.toml |
| 188 | check "a failing [land] run stops the land (exit 4)" code 4 "$VCS" land -m "Checks" |
| 189 | check "--no-run lands anyway" "$VCS" land -m "Checks" --no-run |
| 190 | printf '[land]\nrun = ["exit 0"]\n' > onset.toml |
| 191 | check "a passing [land] run lands" "$VCS" land -m "Checks pass" |
| 192 |
| 193 | # No description. |
| 194 | printf 'x\n' > x.txt |
| 195 | check "no description (exit 2)" code 2 "$VCS" land |
| 196 | check "describe then land" "$VCS" land -m "Add x" |
| 197 |
| 198 | # Diff and log output. |
| 199 | printf 'y\n' > x.txt |
| 200 | check "diff shows the edit" sh -c "\"$VCS\" diff | grep -q '^+y'" |
| 201 | check "log --saves" "$VCS" log --saves |
| 202 | check "log --ops" "$VCS" log --ops |
| 203 | check "log --changes" "$VCS" log --changes |
| 204 | check "guide" sh -c "\"$VCS\" guide | grep -q 'on land'" |
| 205 |
| 206 | # Deleting a workspace is deleting its folder; a new one can take its place. |
| 207 | cd "$WORK" |
| 208 | check "get a third workspace" "$VCS" get ./app app3 |
| 209 | rm -rf app3 |
| 210 | check "get again at the same path" "$VCS" get ./app app3 |
| 211 | cd "$WORK/app" |
| 212 |
| 213 | # Mine: vcs access <path> me. |
| 214 | printf 'token\n' > secrets.local |
| 215 | check "access me" "$VCS" access secrets.local me |
| 216 | check "mine stays out of the change" sh -c "! \"$VCS\" --json status | grep -q secrets.local" |
| 217 |
| 218 | # An agent each: workspaces added next to this one, and cleaned up once done. |
| 219 | check "add a workspace" "$VCS" workspaces add ../agent1 |
| 220 | check "it has main's files" test -f ../agent1/README.md |
| 221 | printf 'parent work\n' > parent.txt |
| 222 | check "add one on this change" "$VCS" workspaces add ../sub1 --on . |
| 223 | check "it starts from this change's work" test -f ../sub1/parent.txt |
| 224 | check "the list has them" sh -c "'$VCS' workspaces | grep -q agent1 && '$VCS' workspaces | grep -q sub1" |
| 225 | printf 'agent work\n' > ../agent1/agent.txt && printf 'sub work\n' > ../sub1/sub.txt |
| 226 | check "clean (nothing idle long enough)" "$VCS" workspaces clean |
| 227 | check "keeps them all" test -d ../agent1 -a -d ../sub1 -a -d ../app2 |
| 228 | check "clean, no idle time" "$VCS" workspaces clean --idle 0s |
| 229 | check "keeps work that hasn't landed" test -f ../agent1/agent.txt -a -f ../sub1/sub.txt |
| 230 | check "removes a workspace whose change was empty" test ! -e ../app3 |
| 231 | check "never this one" test -d .onset |
| 232 | check "agent1 lands" "$VCS" -C ../agent1 land -m "Agent work" |
| 233 | check "clean again" "$VCS" workspaces clean --idle 0s |
| 234 | check "removes it once its work landed" test ! -e ../agent1 |
| 235 | check "and forgets it" sh -c "! '$VCS' workspaces | grep -q agent1" |
| 236 | check "main has its work" sh -c "'$VCS' sync >/dev/null && test -f agent.txt" |
| 237 |
| 238 | # Renames: a move shows as one, an edit made elsewhere follows the file to |
| 239 | # its new name when it lands, and history follows it back. |
| 240 | cd "$WORK" |
| 241 | "$VCS" get ./app ren1 >/dev/null 2>&1 |
| 242 | mkdir -p ren1/notes && for i in $(seq 1 30); do printf 'note line %s\n' "$i"; done > ren1/notes/long.txt |
| 243 | check "land a long file" "$VCS" -C ren1 land -m "Long notes" |
| 244 | "$VCS" get ./app ren2 >/dev/null 2>&1 |
| 245 | mkdir -p ren1/docs && mv ren1/notes/long.txt ren1/docs/long.md |
| 246 | check "status shows the move" sh -c "'$VCS' -C ren1 status | grep -q 'R notes/long.txt -> docs/long.md'" |
| 247 | check "land the move" "$VCS" -C ren1 land -m "Move the notes" |
| 248 | perl -pi -e 's/^note line 20$/note line twenty/' ren2/notes/long.txt |
| 249 | check "an edit to the old name lands" "$VCS" -C ren2 land -m "Edit the notes" |
| 250 | "$VCS" -C ren1 sync >/dev/null 2>&1 |
| 251 | check "the edit followed the file" grep -q 'note line twenty' ren1/docs/long.md |
| 252 | check "and the old name is gone" test ! -e ren1/notes/long.txt |
| 253 | check "log follows the move" sh -c "'$VCS' -C ren1 log docs/long.md | grep -q 'from notes/long.txt' && '$VCS' -C ren1 log docs/long.md | grep -q 'Long notes'" |
| 254 | LONG=$(json -C ren1 log docs/long.md | python3 -c 'import json,sys; print([v["n"] for v in json.load(sys.stdin)["versions"] if v["title"] == "Long notes"][0])') |
| 255 | check "blame goes back past the move" test "$(json -C ren1 blame docs/long.md | python3 -c 'import json,sys; print(json.load(sys.stdin)["lines"][0]["version"])')" = "$LONG" |
| 256 |
| 257 | # Smart merges: two new functions at the same spot both go in, and imports combine. |
| 258 | printf 'import { a } from "./a";\nimport { z } from "./z";\n\nexport function one() {}\n' > ren1/smart.ts |
| 259 | "$VCS" -C ren1 land -m "Smart base" >/dev/null 2>&1 |
| 260 | "$VCS" -C ren2 sync >/dev/null 2>&1 |
| 261 | printf 'import { a } from "./a";\nimport { b } from "./b";\nimport { z } from "./z";\n\nexport function one() {}\nexport function two() {}\n' > ren1/smart.ts |
| 262 | printf 'import { a } from "./a";\nimport { c } from "./c";\nimport { z } from "./z";\n\nexport function one() {}\nexport function three() {}\n' > ren2/smart.ts |
| 263 | check "one side lands" "$VCS" -C ren1 land -m "Two" |
| 264 | check "the other lands without a conflict" "$VCS" -C ren2 land -m "Three" |
| 265 | "$VCS" -C ren1 sync >/dev/null 2>&1 |
| 266 | check "both functions and all imports, in order" test "$(cat ren1/smart.ts)" = "$(printf 'import { a } from "./a";\nimport { b } from "./b";\nimport { c } from "./c";\nimport { z } from "./z";\n\nexport function one() {}\nexport function two() {}\nexport function three() {}')" |
| 267 |
| 268 | # Bisect: the first version where a check fails, tried in a folder of its own. |
| 269 | mkdir "$WORK/bis" && cd "$WORK/bis" |
| 270 | printf 'fine\n' > state.txt |
| 271 | "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "v1" >/dev/null 2>&1 |
| 272 | for i in 2 3 4 5 6; do printf 'fine %s\n' "$i" > state.txt; "$VCS" land -m "v$i" >/dev/null 2>&1; done |
| 273 | printf 'broken\n' > state.txt && "$VCS" land -m "Breaks it" >/dev/null 2>&1 |
| 274 | for i in 8 9 10; do printf 'broken %s\n' "$i" > state.txt; "$VCS" land -m "v$i" >/dev/null 2>&1; done |
| 275 | check "bisect finds the version that broke it" test "$(json bisect 1 10 --run 'grep -q fine state.txt' | python3 -c 'import json,sys; print(json.load(sys.stdin)["first_bad"])')" = 7 |
| 276 | check "the workspace's files didn't change" grep -q 'broken 10' state.txt |
| 277 | check "with --in it keeps the folder" sh -c "'$VCS' bisect 1 main --run 'grep -q fine state.txt' --in ../bis-try >/dev/null && test -f ../bis-try/state.txt" |
| 278 | printf 'grep -qx "fine 6" state.txt && exit 125\ngrep -q fine state.txt\n' > "$WORK/skip-check.sh" |
| 279 | check "exit 125 skips a version" test "$(json bisect 1 10 --run "sh $WORK/skip-check.sh" | python3 -c 'import json,sys; print(json.load(sys.stdin)["first_bad"])')" = 7 |
| 280 | check "a good version after the bad one is refused (exit 2)" code 2 "$VCS" bisect 10 1 --run true |
| 281 |
| 282 | # Release lines: 1.x from version 2 gets fixes of its own, and a fix from |
| 283 | # main comes over (cleanly, or as a change on the line with conflicts marked). |
| 284 | mkdir "$WORK/rel" && cd "$WORK/rel" |
| 285 | printf 'one\ntwo\nthree\n' > app.txt && printf 'v1\n' > VERSION |
| 286 | "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 |
| 287 | printf 'v2\n' > VERSION && "$VCS" land -m "Two" >/dev/null 2>&1 |
| 288 | check "start line 1.x from version 2" "$VCS" line 1.x --from 2 |
| 289 | check "a second line by the same name is refused" code 1 "$VCS" line 1.x |
| 290 | check "vcs line lists it" sh -c "'$VCS' line | grep -q 'from main 2'" |
| 291 | printf 'v3\n' > VERSION && printf 'ONE\ntwo\nthree\n' > app.txt && "$VCS" land -m "Three" >/dev/null 2>&1 |
| 292 | cd "$WORK" |
| 293 | check "get a workspace on the line" "$VCS" get ./rel rel-1x --line 1.x |
| 294 | check "it has the line's files" test "$(cat rel-1x/VERSION)" = v2 |
| 295 | printf 'v2.1\n' > rel-1x/VERSION |
| 296 | check "land a fix on the line" "$VCS" -C rel-1x land -m "Fix on 1.x" |
| 297 | check "it's 1.x/1" test "$(json -C rel-1x log --line 1.x | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 1 |
| 298 | check "main didn't move" test "$(json -C rel log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 |
| 299 | check "the workspace stays on the line" sh -c "'$VCS' -C rel-1x status | grep -q 'on 1.x/1\|1.x/1'" |
| 300 | printf 'v2.2\n' > rel-1x/VERSION |
| 301 | check "the next land goes on the line too" "$VCS" -C rel-1x land -m "Second fix on 1.x" |
| 302 | check "show reads a line" test "$("$VCS" -C rel show VERSION --from 1.x)" = v2.2 |
| 303 | check "and a line's version" test "$("$VCS" -C rel show VERSION --from 1.x/1)" = v2.1 |
| 304 | printf 'ONE\ntwo\nTHREE\n' > rel/app.txt |
| 305 | "$VCS" -C rel land -m "Fix the third line" >/dev/null 2>&1 |
| 306 | FIX=$(json -C rel log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])') |
| 307 | check "bring main's fix over" "$VCS" -C rel land "$FIX" --to 1.x |
| 308 | check "only the fix came over" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'one\ntwo\nTHREE')" |
| 309 | check "as 1.x/3" test "$(json -C rel log --line 1.x | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 |
| 310 | "$VCS" -C rel sync >/dev/null 2>&1 |
| 311 | printf 'UNO\ntwo\nTHREE\n' > rel/app.txt |
| 312 | "$VCS" -C rel land -m "Rename the first line" >/dev/null 2>&1 |
| 313 | CLASH=$(json -C rel log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])') |
| 314 | check "a fix that conflicts with the line (exit 3)" code 3 "$VCS" -C rel land "$CLASH" --to 1.x |
| 315 | check "the workspace is on the line with it marked" sh -c "'$VCS' -C rel status | grep -q '1.x/3' && grep -q '<<<<<<<' rel/app.txt" |
| 316 | printf 'uno\ntwo\nTHREE\n' > rel/app.txt |
| 317 | check "fixed, it lands on the line" "$VCS" -C rel land |
| 318 | check "as 1.x/4" test "$("$VCS" -C rel show app.txt --from 1.x/4)" = "$(printf 'uno\ntwo\nTHREE')" |
| 319 | check "vcs new --line starts on the line's latest" sh -c "'$VCS' -C rel new --line 1.x | grep -q '1.x/4'" |
| 320 | printf 'uno\ntwo\nTHREE\nfour\n' > rel/app.txt |
| 321 | check "land 1.x/5" sh -c "'$VCS' -C rel land -m 'Add four' | grep -q '1.x/5'" |
| 322 | check "vcs undo takes a line's landing back" sh -c "'$VCS' -C rel undo | grep -q '1.x/6 takes 1.x/5 back out'" |
| 323 | check "the line is as it was" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'uno\ntwo\nTHREE')" |
| 324 | check "and the change is open again" sh -c "'$VCS' -C rel status | grep -q 'open'" |
| 325 | check "vcs undo 1.x/3 takes an older one back" sh -c "'$VCS' -C rel undo 1.x/3 | grep -q 'takes it back out'" |
| 326 | check "keeping what came after it" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'uno\ntwo\nthree')" |
| 327 |
| 328 | # Purge: a key that landed comes out of every version and its content is |
| 329 | # deleted, but not while another path still has the same bytes. |
| 330 | mkdir "$WORK/pur" && cd "$WORK/pur" |
| 331 | printf 'app\n' > app.txt |
| 332 | "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 |
| 333 | mkdir keys && printf 'not-a-real-key-7f3a\n' > keys/id.pem |
| 334 | "$VCS" land -m "Oops, a key" --allow-secrets >/dev/null 2>&1 |
| 335 | printf 'app 2\n' > app.txt && cp keys/id.pem copy.pem |
| 336 | "$VCS" land -m "More, and a copy" --allow-secrets >/dev/null 2>&1 |
| 337 | KEYHEX=$(printf '\001not-a-real-key-7f3a\n' | sha256sum | cut -c1-64) |
| 338 | KEYOBJ="$ONSET_HOME/objects/${KEYHEX:0:2}/${KEYHEX:2}" |
| 339 | check "the key is stored" test -f "$KEYOBJ" |
| 340 | check "without --yes it only says what it would do" sh -c "'$VCS' purge keys/id.pem | grep -q 'would' && test -f keys/id.pem" |
| 341 | check "purge it" "$VCS" purge keys/id.pem --yes |
| 342 | check "gone from the workspace" test ! -e keys |
| 343 | check "and from every version" sh -c "! '$VCS' show keys/id.pem --from 2 >/dev/null 2>&1 && ! '$VCS' show keys/id.pem --from 3 >/dev/null 2>&1" |
| 344 | check "versions keep their numbers" test "$(json log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 |
| 345 | check "the copy still has the bytes" test -f "$KEYOBJ" -a -f copy.pem |
| 346 | check "purge the copy too" "$VCS" purge copy.pem --yes |
| 347 | check "now the bytes are gone" test ! -e "$KEYOBJ" |
| 348 | check "the rest is whole" "$VCS" check |
| 349 | check "nothing comes back on the next save" sh -c "'$VCS' status | grep -q 'no changes yet'" |
| 350 | printf 'app 3\n' > app.txt |
| 351 | check "landing goes on" "$VCS" land -m "After the purge" |
| 352 | check "a path no version has" sh -c "'$VCS' purge nothing/here.txt --yes | grep -q 'nothing in'" |
| 353 |
| 354 | # Agent sessions: what the agent was asked, said and ran since its last land |
| 355 | # is kept with the change it lands, secrets removed; off stops it, delete |
| 356 | # removes it. |
| 357 | mkdir "$WORK/ses" && cd "$WORK/ses" |
| 358 | printf 'x\n' > a.txt |
| 359 | "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 |
| 360 | mkdir -p "$WORK/claude/projects/-elsewhere" |
| 361 | SES="$WORK/claude/projects/-elsewhere/abc-123.jsonl" |
| 362 | printf '%s\n' '{"type":"user","message":{"role":"user","content":"add b, the key is ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}}' \ |
| 363 | '{"type":"assistant","message":{"content":[{"type":"text","text":"Adding b."},{"type":"tool_use","name":"Bash","input":{"command":"on land -m B"}}]}}' > "$SES" |
| 364 | agent() { CLAUDE_CONFIG_DIR="$WORK/claude" CLAUDE_CODE_SESSION_ID=abc-123 "$@"; } |
| 365 | last() { json log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])'; } |
| 366 | printf 'b\n' > b.txt |
| 367 | check "an agent's land keeps its session" sh -c "CLAUDE_CONFIG_DIR=$WORK/claude CLAUDE_CODE_SESSION_ID=abc-123 '$VCS' land -m B | grep -q 'Claude Code, 2 messages'" |
| 368 | B=$(last) |
| 369 | check "show prints it" sh -c "'$VCS' sessions show $B | grep -q 'agent: Adding b.' && '$VCS' sessions show $B | grep -q 'ran Bash on land'" |
| 370 | check "with the secret removed" sh -c "'$VCS' sessions show $B --raw | grep -q 'secret removed' && ! '$VCS' sessions show $B --raw | grep -q ghp_AAAA" |
| 371 | printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"Now c."}]}}' >> "$SES" |
| 372 | printf 'c\n' > c.txt |
| 373 | agent "$VCS" land -m "C" >/dev/null 2>&1 |
| 374 | C=$(last) |
| 375 | check "the next land keeps only what came after" sh -c "'$VCS' sessions show $C | grep -q 'Now c.' && ! '$VCS' sessions show $C | grep -q 'Adding b.'" |
| 376 | check "a land by hand keeps nothing" sh -c "printf 'h\n' > h.txt && ! '$VCS' land -m Hand | grep -q '^session'" |
| 377 | check "vcs sessions lists them" test "$("$VCS" sessions | grep -c 'Claude Code')" = 2 |
| 378 | check "vcs gc keeps them" sh -c "ONSET_GC_FRESH=0 '$VCS' gc >/dev/null && '$VCS' sessions show $B | grep -q 'Adding b.'" |
| 379 | check "off" "$VCS" sessions off |
| 380 | printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"Now d."}]}}' >> "$SES" |
| 381 | printf 'd\n' > d.txt |
| 382 | check "nothing is kept while it's off" sh -c "! CLAUDE_CONFIG_DIR=$WORK/claude CLAUDE_CODE_SESSION_ID=abc-123 '$VCS' land -m D | grep -q '^session'" |
| 383 | check "on again" "$VCS" sessions on |
| 384 | printf 'e\n' > e.txt |
| 385 | check "ONSET_SESSIONS=0 skips one land" sh -c "! ONSET_SESSIONS=0 CLAUDE_CONFIG_DIR=$WORK/claude CLAUDE_CODE_SESSION_ID=abc-123 '$VCS' land -m E | grep -q '^session'" |
| 386 | mkdir -p "$WORK/codex/sessions/2026/10/28" |
| 387 | printf '%s\n' "{\"type\":\"session_meta\",\"payload\":{\"id\":\"cx-1\",\"cwd\":\"$(native "$(pwd -P)")\"}}" \ |
| 388 | '{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"make f"}]}}' \ |
| 389 | '{"type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Made f."}]}}' > "$WORK/codex/sessions/2026/10/28/rollout-cx-1.jsonl" |
| 390 | printf 'f\n' > f.txt |
| 391 | check "a Codex session in this folder is kept" sh -c "CODEX_HOME=$WORK/codex '$VCS' land -m F | grep -q 'Codex, 2 messages'" |
| 392 | check "delete one change's" "$VCS" sessions delete "$B" |
| 393 | check "it's gone" sh -c "'$VCS' sessions show $B | grep -q 'none kept'" |
| 394 | check "delete --all" "$VCS" sessions delete --all |
| 395 | check "none left" test "$("$VCS" sessions | grep -c 'Claude Code')" = 0 |
| 396 |
| 397 | # Submodules: a pointer to a commit, added, pointed at another commit (or the |
| 398 | # one checked out in its folder), checked out with git, and removed. |
| 399 | cd "$WORK" |
| 400 | gc_() { git -c user.name=T -c user.email=t@example.com "$@"; } |
| 401 | git init -q sublib && (cd sublib && printf 'v1\n' > lib.txt && git add -A && gc_ commit -qm one && printf 'v2\n' > lib.txt && gc_ commit -qam two) |
| 402 | L1=$(git -C sublib rev-parse HEAD~1) |
| 403 | L2=$(git -C sublib rev-parse HEAD) |
| 404 | mkdir "$WORK/subs" && cd "$WORK/subs" |
| 405 | printf 'app\n' > app.txt |
| 406 | "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 |
| 407 | SUBURL=$(native "$WORK/sublib") |
| 408 | check "add a submodule" "$VCS" submodule vendor/lib "$L1" --url "$SUBURL" |
| 409 | check ".gitmodules names it" grep -q "url = $SUBURL" .gitmodules |
| 410 | check "it lands" "$VCS" land -m "Add lib" |
| 411 | check "vcs submodule lists it" sh -c "'$VCS' submodule | grep -q 'vendor/lib ${L1:0:12}'" |
| 412 | check "check it out" "$VCS" submodule --checkout |
| 413 | check "at its commit" test "$(cat vendor/lib/lib.txt)" = v1 |
| 414 | check "nothing in it is saved" sh -c "'$VCS' status | grep -q 'no changes yet'" |
| 415 | git -C vendor/lib checkout -q "$L2" 2>/dev/null |
| 416 | check "it says the checkout moved" sh -c "'$VCS' submodule | grep -q 'records it'" |
| 417 | check "record the commit checked out" sh -c "'$VCS' submodule vendor/lib | grep -q 'now points at ${L2:0:12}'" |
| 418 | check "status shows it" sh -c "'$VCS' status | grep -q 'M vendor/lib'" |
| 419 | check "it lands too" "$VCS" land -m "Lib two" |
| 420 | check "main has the new commit" sh -c "'$VCS' --json submodule | grep -q $L2" |
| 421 | check "a commit that isn't one (exit 2)" code 2 "$VCS" submodule vendor/lib nothex |
| 422 | check "removing one with a checkout in it is refused (exit 2)" code 2 "$VCS" submodule vendor/lib --remove |
| 423 | rm -rf vendor/lib |
| 424 | check "remove it" "$VCS" submodule vendor/lib --remove |
| 425 | check "and its .gitmodules entry" test ! -e .gitmodules |
| 426 | check "that lands" "$VCS" land -m "No lib" |
| 427 | check "none left" sh -c "'$VCS' submodule | grep -q 'none in this project'" |
| 428 |
| 429 | # A SHA-256 git repository imports like any other, and its branches come in as changes. |
| 430 | cd "$WORK" |
| 431 | git init -q --object-format=sha256 sha256repo |
| 432 | (cd sha256repo && printf 'one\n' > a.txt && git add -A && gc_ commit -qm "First" && printf 'two\n' > a.txt && gc_ commit -qam "Second") |
| 433 | check "import a SHA-256 repository" "$VCS" import sha256repo sha256proj |
| 434 | check "with its history" test "$(json -C sha256proj log | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["versions"]))')" = 2 |
| 435 | check "and its files" test "$(cat sha256proj/a.txt)" = two |
| 436 | check "its versions name their 64-digit commits" sh -c "'$VCS' --json -C sha256proj log -n 1 | grep -q \"$(git -C sha256repo rev-parse HEAD)\"" |
| 437 | (cd sha256repo && git checkout -qb feature && printf 'three\n' > b.txt && git add -A && gc_ commit -qm "Feature") |
| 438 | check "a branch of it comes in as a change" "$VCS" -C sha256proj import ../sha256repo --change feature |
| 439 | check "and lands" "$VCS" -C sha256proj land |
| 440 | check "with the branch's file" test "$(cat sha256proj/b.txt)" = three |
| 441 | check "a SHA-256 submodule commit" "$VCS" -C sha256proj submodule lib "$(git -C sha256repo rev-parse HEAD)" --url https://example.com/lib.git |
| 442 |
| 443 | # Onset was called vcs before it had its name: what a machine set up then |
| 444 | # has keeps working. A workspace's .vcs folder becomes .onset, a project's |
| 445 | # vcs.toml is read, VCS_* settings count, and the data folder stays. |
| 446 | cd "$WORK" |
| 447 | "$VCS" init oldname >/dev/null 2>&1 && printf 'old\n' > oldname/a.txt && "$VCS" -C oldname land -m "Before the name" >/dev/null 2>&1 |
| 448 | mv oldname/.onset oldname/.vcs |
| 449 | check "a .vcs workspace is still one" sh -c "'$VCS' -C oldname status | grep -q 'no changes yet'" |
| 450 | check "and its folder is .onset now" test -d oldname/.onset -a ! -e oldname/.vcs |
| 451 | printf '[land]\nrun = ["exit 3"]\n' > oldname/vcs.toml |
| 452 | printf 'more\n' >> oldname/a.txt |
| 453 | check "vcs.toml is read" code 4 "$VCS" -C oldname land -m "Checked" |
| 454 | rm oldname/vcs.toml |
| 455 | check "VCS_ settings count" sh -c "VCS_JSON=1 '$VCS' -C oldname status | grep -q '\"ok\"'" |
| 456 | check "and it lands" "$VCS" -C oldname land -m "By the old name" |
| 457 | # Where each system keeps app data; the folder from then is named vcs. |
| 458 | case "$(uname -s)" in |
| 459 | Darwin) OLDDATA="$WORK/fakehome/Library/Application Support" ;; |
| 460 | MINGW* | MSYS* | CYGWIN*) OLDDATA="$WORK/fakelocal" ;; |
| 461 | *) OLDDATA="$WORK/fakehome/.local/share" ;; |
| 462 | esac |
| 463 | mkdir -p "$OLDDATA/vcs" |
| 464 | cp -R "$ONSET_HOME/." "$OLDDATA/vcs/" |
| 465 | check "a data folder named vcs is used" sh -c "env -u ONSET_HOME -u XDG_DATA_HOME HOME='$WORK/fakehome' LOCALAPPDATA='$(native "$WORK/fakelocal")' '$VCS' --json -C oldname log -n 1 | grep -q 'By the old name' && test ! -e '$OLDDATA/onset'" |
| 466 |
| 467 | # A .git in each workspace, for editors and tools: at the change's base, so |
| 468 | # git shows the change's own edits; git's commit and push say what to run. |
| 469 | if command -v git >/dev/null 2>&1; then |
| 470 | cd "$WORK" |
| 471 | mkdir gv && cd gv |
| 472 | printf 'one\n' > a.txt |
| 473 | printf 'SECRET=2\n' > .env |
| 474 | export ONSET_GIT=1 ONSET_GIT_WAIT=1 |
| 475 | "$VCS" init >/dev/null 2>&1 |
| 476 | "$VCS" land -m "Git view one" >/dev/null 2>&1 |
| 477 | check "a workspace has a .git" test -f .git |
| 478 | check "git sees no changes" test -z "$(git status --porcelain)" |
| 479 | printf 'two\n' >> a.txt |
| 480 | check "git sees the edit" sh -c "git status --porcelain | grep -q ' M a.txt'" |
| 481 | check "git diff shows it" sh -c "git diff | grep -q '^+two'" |
| 482 | check "git commit says to land" sh -c "git -c user.name=T -c user.email=t@example.com commit -qam x 2>&1 | grep -q 'on land'" |
| 483 | "$VCS" land -m "Git view two" >/dev/null 2>&1 |
| 484 | check "after a land, git is at main's latest" sh -c "git log -1 --format=%s | grep -q 'main [0-9]*: Git view two'" |
| 485 | check "and git log reads as main's versions" sh -c "git log --format=%s | sed -n 2p | grep -q 'Git view one'" |
| 486 | check "git sees no changes again" test -z "$(git status --porcelain)" |
| 487 | check "Onset's own folder and .env aren't changes to git" sh -c "! git status --porcelain --untracked-files=all | grep -q -e '.onset' -e '.env'" |
| 488 | check "a second workspace shares the objects" sh -c "cd '$WORK' && '$VCS' get ./gv gv2 >/dev/null 2>&1 && test \"\$(git -C gv2 rev-parse HEAD)\" = \"\$(git -C gv rev-parse HEAD)\"" |
| 489 | check "ONSET_GIT=0 makes none" sh -c "cd '$WORK' && ONSET_GIT=0 '$VCS' get ./gv gv3 >/dev/null 2>&1 && test ! -e gv3/.git" |
| 490 | unset ONSET_GIT_WAIT |
| 491 | export ONSET_GIT=0 |
| 492 | cd "$WORK" |
| 493 | fi |
| 494 |
| 495 | # on watch: a check on each new version of main; the version that breaks it |
| 496 | # is found by bisecting and taken back, and its change is open again. |
| 497 | cd "$WORK" |
| 498 | mkdir wt && cd wt |
| 499 | printf 'test ! -e broken\n' > check.sh |
| 500 | printf 'one\n' > a.txt |
| 501 | "$VCS" init >/dev/null 2>&1 |
| 502 | "$VCS" land -m "Watch one" >/dev/null 2>&1 |
| 503 | latest() { json log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])'; } |
| 504 | GOOD=$(latest) |
| 505 | for i in 2 3; do printf '%s\n' "$i" >> a.txt; "$VCS" land -m "Fine $i" >/dev/null 2>&1; done |
| 506 | printf 'x\n' > broken |
| 507 | "$VCS" land -m "Breaks it" >/dev/null 2>&1 |
| 508 | BROKE=$(latest) |
| 509 | BROKE_CHANGE=$(json log "$BROKE" | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"])') |
| 510 | printf '5\n' >> a.txt |
| 511 | "$VCS" land -m "Fine after" >/dev/null 2>&1 |
| 512 | check "watch takes back the version that broke the check" sh -c "'$VCS' watch --run 'sh check.sh' --from $GOOD --once | grep -q 'main $BROKE (\"Breaks it\"'" |
| 513 | check "main passes the check again" sh -c "! '$VCS' show broken >/dev/null 2>&1" |
| 514 | check "what landed after it stays" sh -c "'$VCS' show a.txt | grep -q '^5$'" |
| 515 | check "its change is open again" sh -c "'$VCS' --json status $BROKE_CHANGE | grep -q '\"state\": *\"open\"'" |
| 516 | check "the take-back says what failed" sh -c "'$VCS' log \$('$VCS' --json log | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"versions\"][0][\"n\"])') | grep -q 'failed on it'" |
| 517 | check "a second watch has nothing new to check" test -z "$("$VCS" watch --run 'sh check.sh' --once 2>/dev/null | grep -v '^$')" |
| 518 | printf '6\n' >> a.txt |
| 519 | rm -f broken |
| 520 | "$VCS" land -m "Fine later" >/dev/null 2>&1 |
| 521 | check "and checks only what landed since" sh -c "'$VCS' watch --run 'sh check.sh' --once | grep -q 'passed *main $((BROKE + 3))'" |
| 522 | check "a check that fails with nothing known to pass says so (exit 4)" code 4 sh -c "cd '$WORK/wt' && '$VCS' watch --run 'false' --once" |
| 523 | cd "$WORK" |
| 524 |
| 525 | # on git mirror: main to a branch of any git repository and back (a bare |
| 526 | # repository stands in for GitHub here). |
| 527 | if command -v git >/dev/null 2>&1; then |
| 528 | cd "$WORK" |
| 529 | mkdir mir && cd mir |
| 530 | printf 'a1\n' > a.txt |
| 531 | printf 'b1\n' > b.txt |
| 532 | "$VCS" init >/dev/null 2>&1 |
| 533 | "$VCS" land -m "Mirror one" >/dev/null 2>&1 |
| 534 | printf 'a2\n' >> a.txt |
| 535 | "$VCS" land -m "Mirror two" >/dev/null 2>&1 |
| 536 | git init -q --bare -b main "$WORK/gh.git" |
| 537 | check "mirror writes main to the branch" sh -c "'$VCS' git mirror '$(native "$WORK/gh.git")' --once | grep -q 'mirrored *main 2'" |
| 538 | git clone -q "$WORK/gh.git" "$WORK/ghc" |
| 539 | check "each version is a commit, with its trailers" sh -c "git -C '$WORK/ghc' log -1 --format=%B | grep -q 'Onset-Version: 2'" |
| 540 | check "with its author" test "$(git -C "$WORK/ghc" log -1 --format=%an)" = "Test Person" |
| 541 | # A pull request merged there, and a land here meanwhile. |
| 542 | printf 'b2 from git\n' >> "$WORK/ghc/b.txt" |
| 543 | git -C "$WORK/ghc" -c user.name=Gitty -c user.email=g@example.com commit -qam "Fixed on the git side" |
| 544 | git -C "$WORK/ghc" push -q |
| 545 | printf 'a3\n' >> a.txt |
| 546 | "$VCS" land -m "Mirror three" >/dev/null 2>&1 |
| 547 | check "commits made there come into main" sh -c "'$VCS' git mirror '$(native "$WORK/gh.git")' --once | grep -q 'came in *1 commit from git'" |
| 548 | check "main has both" sh -c "'$VCS' show b.txt | grep -q 'b2 from git' && '$VCS' show a.txt | grep -q a3" |
| 549 | git -C "$WORK/ghc" pull -q |
| 550 | check "and the branch has both, joined by a merge" sh -c "grep -q a3 '$WORK/ghc/a.txt' && grep -q 'b2 from git' '$WORK/ghc/b.txt' && test \"\$(git -C '$WORK/ghc' log -1 --format=%p | wc -w | tr -d ' ')\" = 2" |
| 551 | check "a mirror with nothing new does nothing" test -z "$("$VCS" git mirror "$(native "$WORK/gh.git")" --once 2>/dev/null)" |
| 552 | git -C "$WORK/ghc" -c user.name=Gitty -c user.email=g@example.com commit -q --allow-empty -m x |
| 553 | git -C "$WORK/ghc" push -q -f origin HEAD~2:main 2>/dev/null |
| 554 | check "a branch rewritten there stops the mirror (exit 3)" code 3 "$VCS" git mirror "$(native "$WORK/gh.git")" --once |
| 555 | cd "$WORK" |
| 556 | fi |
| 557 |
| 558 | echo "local: $pass passed, $fail failed" |
| 559 | [ "$fail" = 0 ] |
| 560 |