#!/usr/bin/env bash # End-to-end checks of the vcs CLI on a project that lives on this machine. # Usage: tests/local.sh [path/to/vcs] set -uo pipefail VCS=${1:-$(cd "$(dirname "$0")/.." && pwd)/target/debug/vcs} WORK=$(mktemp -d) export VCS_HOME="$WORK/home" VCS_NAME="Test Person" VCS_EMAIL="test@example.com" VCS_SAVER=0 # Run from inside an agent, its own session would be kept with the tests' lands. unset CLAUDE_CODE_SESSION_ID CLAUDECODE CLAUDE_CONFIG_DIR VCS_SESSION_FILE VCS_SESSIONS export CODEX_HOME="$WORK/no-codex" trap 'rm -rf "$WORK"' EXIT cd "$WORK" pass=0 fail=0 check() { # name, command... local name=$1 shift if "$@" >/dev/null 2>&1; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "FAIL: $name"; fi } code() { # expected exit code, command... local want=$1 shift "$@" >/dev/null 2>&1 [ "$?" = "$want" ] } json() { "$VCS" --json "$@" 2>/dev/null; } # A new project with a first version. mkdir app && cd app printf 'hello\n' > README.md mkdir src && printf 'a\nb\nc\nd\ne\nf\ng\n' > src/lib.txt printf '{\n "name": "app"\n}\n' > package.json printf 'SECRET=1\n' > .env check "init" "$VCS" init check "land first version" "$VCS" land -m "First version" check "main is version 1" test "$(json log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 1 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 "")')" check "empty land refused (exit 1)" code 1 "$VCS" land -m "Nothing" # A second workspace gets the files and the .env. cd "$WORK" check "get second workspace" "$VCS" get ./app app2 check "files arrived" test "$(cat app2/src/lib.txt | head -1)" = a check ".env came along" test "$(cat app2/.env)" = "SECRET=1" # Parallel edits to different files both land, in any order. printf 'hello there\n' > app/README.md printf 'a\nb\nc\nd\ne\nf\nG\n' > app2/src/lib.txt check "land from app" "$VCS" -C app land -m "Greet better" check "land from app2 without syncing" "$VCS" -C app2 land -m "Capital G" check "main is version 3" test "$(json -C app log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 # JSON files merge by key. cd "$WORK" && "$VCS" -C app sync >/dev/null 2>&1 && "$VCS" -C app2 sync >/dev/null 2>&1 printf '{\n "name": "app",\n "version": "1.0.0"\n}\n' > app/package.json printf '{\n "name": "app",\n "license": "MIT"\n}\n' > app2/package.json check "land json key A" "$VCS" -C app land -m "Add version" check "land json key B (merged by key)" "$VCS" -C app2 land -m "Add license" "$VCS" -C app sync >/dev/null 2>&1 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"' # A real conflict: land refuses, sync marks it, fixing it lands. "$VCS" -C app2 sync >/dev/null 2>&1 perl -pi -e 's/^b$/B from app/' app/src/lib.txt perl -pi -e 's/^b$/B from app2/' app2/src/lib.txt check "land A" "$VCS" -C app land -m "B from app" check "land B conflicts (exit 3)" code 3 "$VCS" -C app2 land -m "B from app2" check "sync leaves markers (exit 3)" code 3 "$VCS" -C app2 sync check "markers in file" grep -q '<<<<<<< main' app2/src/lib.txt check "land refused while markers remain" code 3 "$VCS" -C app2 land python3 - "$WORK/app2/src/lib.txt" <<'EOF' import sys, re p = sys.argv[1] t = open(p).read() t = re.sub(r"<<<<<<< [^\n]*\n(.*?)=======\n(.*?)>>>>>>> [^\n]*\n", lambda m: "B from both\n", t, flags=re.S) open(p, "w", newline="\n").write(t) EOF check "land after fixing (reuses description)" "$VCS" -C app2 land check "status clean after land" test "$(json -C app2 status | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["files"]))')" = 0 # A file's history, and the version behind each line. 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 check "log of a folder" sh -c "\"$VCS\" -C app log src | grep -q 'Capital G'" check "log of a file from inside its folder" sh -c "cd app/src && \"$VCS\" log lib.txt | grep -q 'First version'" 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 check "blame of a missing file fails" sh -c "! \"$VCS\" -C app blame nope.txt" check "show a file in main" sh -c "\"$VCS\" -C app show src/lib.txt | grep -q 'B from both'" check "show a file at a version" test "$("$VCS" -C app show src/lib.txt --from 1 | tail -1)" = g check "show a missing file fails" sh -c "! \"$VCS\" -C app show nope.txt" check "completions" sh -c "\"$VCS\" completions bash | grep -q _vcs" # Tags: names for versions. check "tag main's latest" "$VCS" -C app tag v1.0 check "tag an older version" "$VCS" -C app tag first 1 check "the list has them" sh -c "\"$VCS\" -C app tag | grep -q 'v1.0' && \"$VCS\" -C app tag | grep -q first" check "show at a tag" test "$("$VCS" -C app show src/lib.txt --from first | tail -1)" = g check "log of a tag" sh -c "\"$VCS\" -C app log first | grep -q 'First version'" check "a tag stays on its version (exit 3)" code 3 "$VCS" -C app tag first 2 check "a number can't be a tag (exit 2)" code 2 "$VCS" -C app tag 12 check "remove a tag" "$VCS" -C app tag first --remove check "it's gone" sh -c "! \"$VCS\" -C app tag | grep -q first" # Cleaning up: a dropped change's saves and objects go; what's kept still works. cd "$WORK/app" "$VCS" sync >/dev/null 2>&1 head -c 200000 /dev/urandom > scratch.bin "$VCS" status >/dev/null 2>&1 "$VCS" drop >/dev/null 2>&1 check "gc --dry-run says what would go" sh -c "VCS_GC_FRESH=0 '$VCS' gc --older 0s --dry-run | grep -q 'would remove'" check "gc removes the dropped change's objects" test "$(VCS_GC_FRESH=0 json gc --older 0s | python3 -c 'import json,sys; print(json.load(sys.stdin)["objects"] > 0)')" = True check "main still reads" sh -c "\"$VCS\" show src/lib.txt | grep -q 'B from both'" check "history still reads" sh -c "\"$VCS\" log src/lib.txt | grep -q 'First version'" check "nothing is damaged" "$VCS" check check "a second gc has nothing left" test "$(VCS_GC_FRESH=0 json gc --older 0s | python3 -c 'import json,sys; print(json.load(sys.stdin)["objects"])')" = 0 cd "$WORK" # Undo a land: main gets a version that takes it back out. cd "$WORK/app" "$VCS" sync >/dev/null 2>&1 printf 'oops\n' > oops.txt check "land oops" "$VCS" land -m "Oops" check "undo the land" "$VCS" undo check "oops is open again" test "$(json status | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"]["state"])')" = open 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" check "drop it" "$VCS" drop check "undo the drop" "$VCS" undo check "drop again" "$VCS" drop # Restore a file from an earlier save. printf 'v1\n' > notes.txt "$VCS" status >/dev/null 2>&1 sleep 3 printf 'broken\n' > notes.txt "$VCS" status >/dev/null 2>&1 check "restore --at" "$VCS" restore notes.txt --at "2s ago" check "notes is v1" test "$(cat notes.txt)" = v1 check "undo the restore" "$VCS" undo check "notes is broken again" test "$(cat notes.txt)" = broken check "restore to base (removes the new file)" "$VCS" restore notes.txt check "notes gone" test ! -e notes.txt # New, switch, stacks. printf 'feature a\n' > a.txt check "describe" "$VCS" describe "Feature A" A=$(json status | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"]["id"])') check "new stacked on A" "$VCS" new "Feature B" --on "$A" printf 'feature b\n' > b.txt check "a.txt visible in B" test -e a.txt check "switch back to A" "$VCS" switch "${A:0:6}" check "b.txt gone in A" test ! -e b.txt check "undo switch" "$VCS" undo check "b.txt back" test -e b.txt check "land B lands A then B" "$VCS" land 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" # Secrets don't land. printf 'const key = "sk_live_%s";\n' "$(printf 'x%.0s' {1..24})" > pay.js check "secret refused (exit 8)" code 8 "$VCS" land -m "Payments" rm pay.js printf 'const key = process.env.STRIPE_KEY;\n' > pay.js check "lands without the secret" "$VCS" land -m "Payments" # vcs.toml [land] run: the workspace's own checks before it lands. printf '[land]\nrun = ["exit 1"]\n' > vcs.toml check "a failing [land] run stops the land (exit 4)" code 4 "$VCS" land -m "Checks" check "--no-run lands anyway" "$VCS" land -m "Checks" --no-run printf '[land]\nrun = ["exit 0"]\n' > vcs.toml check "a passing [land] run lands" "$VCS" land -m "Checks pass" # No description. printf 'x\n' > x.txt check "no description (exit 2)" code 2 "$VCS" land check "describe then land" "$VCS" land -m "Add x" # Diff and log output. printf 'y\n' > x.txt check "diff shows the edit" sh -c "\"$VCS\" diff | grep -q '^+y'" check "log --saves" "$VCS" log --saves check "log --ops" "$VCS" log --ops check "log --changes" "$VCS" log --changes check "guide" sh -c "\"$VCS\" guide | grep -q 'vcs land'" # Deleting a workspace is deleting its folder; a new one can take its place. cd "$WORK" check "get a third workspace" "$VCS" get ./app app3 rm -rf app3 check "get again at the same path" "$VCS" get ./app app3 cd "$WORK/app" # Mine: vcs access me. printf 'token\n' > secrets.local check "access me" "$VCS" access secrets.local me check "mine stays out of the change" sh -c "! \"$VCS\" --json status | grep -q secrets.local" # An agent each: workspaces added next to this one, and cleaned up once done. check "add a workspace" "$VCS" workspaces add ../agent1 check "it has main's files" test -f ../agent1/README.md printf 'parent work\n' > parent.txt check "add one on this change" "$VCS" workspaces add ../sub1 --on . check "it starts from this change's work" test -f ../sub1/parent.txt check "the list has them" sh -c "'$VCS' workspaces | grep -q agent1 && '$VCS' workspaces | grep -q sub1" printf 'agent work\n' > ../agent1/agent.txt && printf 'sub work\n' > ../sub1/sub.txt check "clean (nothing idle long enough)" "$VCS" workspaces clean check "keeps them all" test -d ../agent1 -a -d ../sub1 -a -d ../app2 check "clean, no idle time" "$VCS" workspaces clean --idle 0s check "keeps work that hasn't landed" test -f ../agent1/agent.txt -a -f ../sub1/sub.txt check "removes a workspace whose change was empty" test ! -e ../app3 check "never this one" test -d .vcs check "agent1 lands" "$VCS" -C ../agent1 land -m "Agent work" check "clean again" "$VCS" workspaces clean --idle 0s check "removes it once its work landed" test ! -e ../agent1 check "and forgets it" sh -c "! '$VCS' workspaces | grep -q agent1" check "main has its work" sh -c "'$VCS' sync >/dev/null && test -f agent.txt" # Renames: a move shows as one, an edit made elsewhere follows the file to # its new name when it lands, and history follows it back. cd "$WORK" "$VCS" get ./app ren1 >/dev/null 2>&1 mkdir -p ren1/notes && for i in $(seq 1 30); do printf 'note line %s\n' "$i"; done > ren1/notes/long.txt check "land a long file" "$VCS" -C ren1 land -m "Long notes" "$VCS" get ./app ren2 >/dev/null 2>&1 mkdir -p ren1/docs && mv ren1/notes/long.txt ren1/docs/long.md check "status shows the move" sh -c "'$VCS' -C ren1 status | grep -q 'R notes/long.txt -> docs/long.md'" check "land the move" "$VCS" -C ren1 land -m "Move the notes" perl -pi -e 's/^note line 20$/note line twenty/' ren2/notes/long.txt check "an edit to the old name lands" "$VCS" -C ren2 land -m "Edit the notes" "$VCS" -C ren1 sync >/dev/null 2>&1 check "the edit followed the file" grep -q 'note line twenty' ren1/docs/long.md check "and the old name is gone" test ! -e ren1/notes/long.txt 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'" 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])') 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" # Smart merges: two new functions at the same spot both go in, and imports combine. printf 'import { a } from "./a";\nimport { z } from "./z";\n\nexport function one() {}\n' > ren1/smart.ts "$VCS" -C ren1 land -m "Smart base" >/dev/null 2>&1 "$VCS" -C ren2 sync >/dev/null 2>&1 printf 'import { a } from "./a";\nimport { b } from "./b";\nimport { z } from "./z";\n\nexport function one() {}\nexport function two() {}\n' > ren1/smart.ts printf 'import { a } from "./a";\nimport { c } from "./c";\nimport { z } from "./z";\n\nexport function one() {}\nexport function three() {}\n' > ren2/smart.ts check "one side lands" "$VCS" -C ren1 land -m "Two" check "the other lands without a conflict" "$VCS" -C ren2 land -m "Three" "$VCS" -C ren1 sync >/dev/null 2>&1 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() {}')" # Bisect: the first version where a check fails, tried in a folder of its own. mkdir "$WORK/bis" && cd "$WORK/bis" printf 'fine\n' > state.txt "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "v1" >/dev/null 2>&1 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 printf 'broken\n' > state.txt && "$VCS" land -m "Breaks it" >/dev/null 2>&1 for i in 8 9 10; do printf 'broken %s\n' "$i" > state.txt; "$VCS" land -m "v$i" >/dev/null 2>&1; done 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 check "the workspace's files didn't change" grep -q 'broken 10' state.txt 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" printf 'grep -qx "fine 6" state.txt && exit 125\ngrep -q fine state.txt\n' > "$WORK/skip-check.sh" 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 check "a good version after the bad one is refused (exit 2)" code 2 "$VCS" bisect 10 1 --run true # Release lines: 1.x from version 2 gets fixes of its own, and a fix from # main comes over (cleanly, or as a change on the line with conflicts marked). mkdir "$WORK/rel" && cd "$WORK/rel" printf 'one\ntwo\nthree\n' > app.txt && printf 'v1\n' > VERSION "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 printf 'v2\n' > VERSION && "$VCS" land -m "Two" >/dev/null 2>&1 check "start line 1.x from version 2" "$VCS" line 1.x --from 2 check "a second line by the same name is refused" code 1 "$VCS" line 1.x check "vcs line lists it" sh -c "'$VCS' line | grep -q 'from main 2'" printf 'v3\n' > VERSION && printf 'ONE\ntwo\nthree\n' > app.txt && "$VCS" land -m "Three" >/dev/null 2>&1 cd "$WORK" check "get a workspace on the line" "$VCS" get ./rel rel-1x --line 1.x check "it has the line's files" test "$(cat rel-1x/VERSION)" = v2 printf 'v2.1\n' > rel-1x/VERSION check "land a fix on the line" "$VCS" -C rel-1x land -m "Fix on 1.x" 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 check "main didn't move" test "$(json -C rel log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 check "the workspace stays on the line" sh -c "'$VCS' -C rel-1x status | grep -q 'on 1.x/1\|1.x/1'" printf 'v2.2\n' > rel-1x/VERSION check "the next land goes on the line too" "$VCS" -C rel-1x land -m "Second fix on 1.x" check "show reads a line" test "$("$VCS" -C rel show VERSION --from 1.x)" = v2.2 check "and a line's version" test "$("$VCS" -C rel show VERSION --from 1.x/1)" = v2.1 printf 'ONE\ntwo\nTHREE\n' > rel/app.txt "$VCS" -C rel land -m "Fix the third line" >/dev/null 2>&1 FIX=$(json -C rel log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])') check "bring main's fix over" "$VCS" -C rel land "$FIX" --to 1.x check "only the fix came over" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'one\ntwo\nTHREE')" 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 "$VCS" -C rel sync >/dev/null 2>&1 printf 'UNO\ntwo\nTHREE\n' > rel/app.txt "$VCS" -C rel land -m "Rename the first line" >/dev/null 2>&1 CLASH=$(json -C rel log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])') check "a fix that conflicts with the line (exit 3)" code 3 "$VCS" -C rel land "$CLASH" --to 1.x 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" printf 'uno\ntwo\nTHREE\n' > rel/app.txt check "fixed, it lands on the line" "$VCS" -C rel land check "as 1.x/4" test "$("$VCS" -C rel show app.txt --from 1.x/4)" = "$(printf 'uno\ntwo\nTHREE')" check "vcs new --line starts on the line's latest" sh -c "'$VCS' -C rel new --line 1.x | grep -q '1.x/4'" printf 'uno\ntwo\nTHREE\nfour\n' > rel/app.txt check "land 1.x/5" sh -c "'$VCS' -C rel land -m 'Add four' | grep -q '1.x/5'" 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'" check "the line is as it was" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'uno\ntwo\nTHREE')" check "and the change is open again" sh -c "'$VCS' -C rel status | grep -q 'open'" 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'" check "keeping what came after it" test "$("$VCS" -C rel show app.txt --from 1.x)" = "$(printf 'uno\ntwo\nthree')" # Purge: a key that landed comes out of every version and its content is # deleted, but not while another path still has the same bytes. mkdir "$WORK/pur" && cd "$WORK/pur" printf 'app\n' > app.txt "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 mkdir keys && printf 'not-a-real-key-7f3a\n' > keys/id.pem "$VCS" land -m "Oops, a key" --allow-secrets >/dev/null 2>&1 printf 'app 2\n' > app.txt && cp keys/id.pem copy.pem "$VCS" land -m "More, and a copy" --allow-secrets >/dev/null 2>&1 KEYHEX=$(printf '\001not-a-real-key-7f3a\n' | sha256sum | cut -c1-64) KEYOBJ="$VCS_HOME/objects/${KEYHEX:0:2}/${KEYHEX:2}" check "the key is stored" test -f "$KEYOBJ" 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" check "purge it" "$VCS" purge keys/id.pem --yes check "gone from the workspace" test ! -e keys 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" check "versions keep their numbers" test "$(json log | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])')" = 3 check "the copy still has the bytes" test -f "$KEYOBJ" -a -f copy.pem check "purge the copy too" "$VCS" purge copy.pem --yes check "now the bytes are gone" test ! -e "$KEYOBJ" check "the rest is whole" "$VCS" check check "nothing comes back on the next save" sh -c "'$VCS' status | grep -q 'no changes yet'" printf 'app 3\n' > app.txt check "landing goes on" "$VCS" land -m "After the purge" check "a path no version has" sh -c "'$VCS' purge nothing/here.txt --yes | grep -q 'nothing in'" # Agent sessions: what the agent was asked, said and ran since its last land # is kept with the change it lands, secrets removed; off stops it, delete # removes it. mkdir "$WORK/ses" && cd "$WORK/ses" printf 'x\n' > a.txt "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 mkdir -p "$WORK/claude/projects/-elsewhere" SES="$WORK/claude/projects/-elsewhere/abc-123.jsonl" printf '%s\n' '{"type":"user","message":{"role":"user","content":"add b, the key is ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}}' \ '{"type":"assistant","message":{"content":[{"type":"text","text":"Adding b."},{"type":"tool_use","name":"Bash","input":{"command":"vcs land -m B"}}]}}' > "$SES" agent() { CLAUDE_CONFIG_DIR="$WORK/claude" CLAUDE_CODE_SESSION_ID=abc-123 "$@"; } last() { json log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["change"])'; } printf 'b\n' > b.txt 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'" B=$(last) check "show prints it" sh -c "'$VCS' sessions show $B | grep -q 'agent: Adding b.' && '$VCS' sessions show $B | grep -q 'ran Bash vcs land'" 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" printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"Now c."}]}}' >> "$SES" printf 'c\n' > c.txt agent "$VCS" land -m "C" >/dev/null 2>&1 C=$(last) 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.'" check "a land by hand keeps nothing" sh -c "printf 'h\n' > h.txt && ! '$VCS' land -m Hand | grep -q '^session'" check "vcs sessions lists them" test "$("$VCS" sessions | grep -c 'Claude Code')" = 2 check "vcs gc keeps them" sh -c "VCS_GC_FRESH=0 '$VCS' gc >/dev/null && '$VCS' sessions show $B | grep -q 'Adding b.'" check "off" "$VCS" sessions off printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"Now d."}]}}' >> "$SES" printf 'd\n' > d.txt 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'" check "on again" "$VCS" sessions on printf 'e\n' > e.txt check "VCS_SESSIONS=0 skips one land" sh -c "! VCS_SESSIONS=0 CLAUDE_CONFIG_DIR=$WORK/claude CLAUDE_CODE_SESSION_ID=abc-123 '$VCS' land -m E | grep -q '^session'" mkdir -p "$WORK/codex/sessions/2026/10/28" printf '%s\n' "{\"type\":\"session_meta\",\"payload\":{\"id\":\"cx-1\",\"cwd\":\"$(pwd -P)\"}}" \ '{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"make f"}]}}' \ '{"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" printf 'f\n' > f.txt check "a Codex session in this folder is kept" sh -c "CODEX_HOME=$WORK/codex '$VCS' land -m F | grep -q 'Codex, 2 messages'" check "delete one change's" "$VCS" sessions delete "$B" check "it's gone" sh -c "'$VCS' sessions show $B | grep -q 'none kept'" check "delete --all" "$VCS" sessions delete --all check "none left" test "$("$VCS" sessions | grep -c 'Claude Code')" = 0 # Submodules: a pointer to a commit, added, pointed at another commit (or the # one checked out in its folder), checked out with git, and removed. cd "$WORK" gc_() { git -c user.name=T -c user.email=t@example.com "$@"; } 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) L1=$(git -C sublib rev-parse HEAD~1) L2=$(git -C sublib rev-parse HEAD) mkdir "$WORK/subs" && cd "$WORK/subs" printf 'app\n' > app.txt "$VCS" init >/dev/null 2>&1 && "$VCS" land -m "First" >/dev/null 2>&1 check "add a submodule" "$VCS" submodule vendor/lib "$L1" --url "$WORK/sublib" check ".gitmodules names it" grep -q "url = $WORK/sublib" .gitmodules check "it lands" "$VCS" land -m "Add lib" check "vcs submodule lists it" sh -c "'$VCS' submodule | grep -q 'vendor/lib ${L1:0:12}'" check "check it out" "$VCS" submodule --checkout check "at its commit" test "$(cat vendor/lib/lib.txt)" = v1 check "nothing in it is saved" sh -c "'$VCS' status | grep -q 'no changes yet'" git -C vendor/lib checkout -q "$L2" 2>/dev/null check "it says the checkout moved" sh -c "'$VCS' submodule | grep -q 'records it'" check "record the commit checked out" sh -c "'$VCS' submodule vendor/lib | grep -q 'now points at ${L2:0:12}'" check "status shows it" sh -c "'$VCS' status | grep -q 'M vendor/lib'" check "it lands too" "$VCS" land -m "Lib two" check "main has the new commit" sh -c "'$VCS' --json submodule | grep -q $L2" check "a commit that isn't one (exit 2)" code 2 "$VCS" submodule vendor/lib nothex check "removing one with a checkout in it is refused (exit 2)" code 2 "$VCS" submodule vendor/lib --remove rm -rf vendor/lib check "remove it" "$VCS" submodule vendor/lib --remove check "and its .gitmodules entry" test ! -e .gitmodules check "that lands" "$VCS" land -m "No lib" check "none left" sh -c "'$VCS' submodule | grep -q 'none in this project'" # A SHA-256 git repository imports like any other, and its branches come in as changes. cd "$WORK" git init -q --object-format=sha256 sha256repo (cd sha256repo && printf 'one\n' > a.txt && git add -A && gc_ commit -qm "First" && printf 'two\n' > a.txt && gc_ commit -qam "Second") check "import a SHA-256 repository" "$VCS" import sha256repo sha256proj check "with its history" test "$(json -C sha256proj log | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["versions"]))')" = 2 check "and its files" test "$(cat sha256proj/a.txt)" = two 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)\"" (cd sha256repo && git checkout -qb feature && printf 'three\n' > b.txt && git add -A && gc_ commit -qm "Feature") check "a branch of it comes in as a change" "$VCS" -C sha256proj import ../sha256repo --change feature check "and lands" "$VCS" -C sha256proj land check "with the branch's file" test "$(cat sha256proj/b.txt)" = three check "a SHA-256 submodule commit" "$VCS" -C sha256proj submodule lib "$(git -C sha256repo rev-parse HEAD)" --url https://example.com/lib.git echo "local: $pass passed, $fail failed" [ "$fail" = 0 ]