Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 43 additions & 34 deletions tests/worktree.bats
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bats

# Integration tests for `meta git worktree` subcommand
# Tests: create, add, list, status, diff, exec, destroy, configuration, edge cases
# Tests: create, add, list, status, diff, exec, remove, configuration, edge cases

setup() {
META_BIN="$BATS_TEST_DIRNAME/../target/debug/meta"
Expand Down Expand Up @@ -373,50 +373,59 @@ assert d['totals']['files_changed'] >= 1
[ "$status" -ne 0 ] || [[ "$output" == *"Usage"* ]] || [[ "$output" == *"command"* ]]
}

# ============ Destroy ============
# ============ Remove ============

@test "worktree destroy removes worktree set" {
@test "worktree remove removes worktree set" {
"$META_BIN" git worktree create temp --repo backend --no-deps
[ -d ".worktrees/temp" ]
run "$META_BIN" git worktree destroy temp
run "$META_BIN" git worktree remove temp
[ "$status" -eq 0 ]
[ ! -d ".worktrees/temp" ]
}

@test "worktree destroy preserves branches" {
@test "worktree remove preserves branches" {
"$META_BIN" git worktree create keep-branch --repo backend --no-deps
"$META_BIN" git worktree destroy keep-branch
run "$META_BIN" git worktree remove keep-branch
[ "$status" -eq 0 ]
git -C backend branch | grep -q "keep-branch"
}

@test "worktree destroy refuses dirty worktree without --force" {
@test "worktree remove refuses dirty worktree without --force" {
"$META_BIN" git worktree create dirty-test --repo backend
echo "uncommitted" >> ".worktrees/dirty-test/backend/README.md"
run "$META_BIN" git worktree destroy dirty-test
run "$META_BIN" git worktree remove dirty-test
[ "$status" -ne 0 ]
[[ "$output" == *"uncommitted"* ]] || [[ "$output" == *"dirty"* ]] || [[ "$output" == *"--force"* ]]
}

@test "worktree destroy --force removes dirty worktree" {
@test "worktree remove --force removes dirty worktree" {
"$META_BIN" git worktree create force-test --repo backend
echo "uncommitted" >> ".worktrees/force-test/backend/README.md"
run "$META_BIN" git worktree destroy force-test --force
run "$META_BIN" git worktree remove force-test --force
[ "$status" -eq 0 ]
[ ! -d ".worktrees/force-test" ]
}

@test "worktree destroy nonexistent worktree fails" {
run "$META_BIN" git worktree destroy nonexistent
@test "worktree remove nonexistent worktree fails" {
run "$META_BIN" git worktree remove nonexistent
[ "$status" -ne 0 ]
}

@test "worktree destroy with multiple repos removes all" {
"$META_BIN" git worktree create multi-destroy --repo backend --repo frontend --no-deps
[ -d ".worktrees/multi-destroy/backend" ]
[ -d ".worktrees/multi-destroy/frontend" ]
run "$META_BIN" git worktree destroy multi-destroy
@test "worktree remove with multiple repos removes all" {
"$META_BIN" git worktree create multi-remove --repo backend --repo frontend --no-deps
[ -d ".worktrees/multi-remove/backend" ]
[ -d ".worktrees/multi-remove/frontend" ]
run "$META_BIN" git worktree remove multi-remove
[ "$status" -eq 0 ]
[ ! -d ".worktrees/multi-remove" ]
}

@test "worktree destroy alias still works" {
"$META_BIN" git worktree create alias-test --repo backend --no-deps
[ -d ".worktrees/alias-test" ]
run "$META_BIN" git worktree destroy alias-test
[ "$status" -eq 0 ]
[ ! -d ".worktrees/multi-destroy" ]
[ ! -d ".worktrees/alias-test" ]
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# ============ Configuration ============
Expand Down Expand Up @@ -475,13 +484,13 @@ assert d['totals']['files_changed'] >= 1
run "$META_BIN" git worktree
[ "$status" -eq 0 ]
[[ "$output" == *"create"* ]]
[[ "$output" == *"destroy"* ]]
[[ "$output" == *"remove"* ]]
[[ "$output" == *"list"* ]]
}

# ============ Full Lifecycle ============

@test "full lifecycle: create, list, status, exec, destroy" {
@test "full lifecycle: create, list, status, exec, remove" {
# Create (use --no-deps to avoid auto-including root repo which may have test changes)
run "$META_BIN" git worktree create lifecycle --repo backend --repo frontend --no-deps
[ "$status" -eq 0 ]
Expand All @@ -502,8 +511,8 @@ assert d['totals']['files_changed'] >= 1
[ "$status" -eq 0 ]
[[ "$output" == *"lifecycle-ok"* ]]

# Destroy
run "$META_BIN" git worktree destroy lifecycle
# Remove
run "$META_BIN" git worktree remove lifecycle
[ "$status" -eq 0 ]
[ ! -d ".worktrees/lifecycle" ]

Expand All @@ -524,33 +533,33 @@ assert d['totals']['files_changed'] >= 1
# Must show ACTUAL help content (not just a reference to --help)
[[ "$output" == *"USAGE"* ]]
[[ "$output" == *"create"* ]]
[[ "$output" == *"destroy"* ]]
[[ "$output" == *"remove"* ]]
[[ "$output" == *"list"* ]]
}

# ============ Edge Cases: Destroy with dot-alias ============
# ============ Edge Cases: Remove with dot-alias ============

@test "worktree destroy with dot-alias removes children before root" {
@test "worktree remove with dot-alias removes children before root" {
# Create worktree with . (meta repo) + child repos
run "$META_BIN" git worktree create dot-destroy --repo . --repo backend --repo frontend
run "$META_BIN" git worktree create dot-remove --repo . --repo backend --repo frontend
[ "$status" -eq 0 ]
[ -f ".worktrees/dot-destroy/.git" ]
[ -d ".worktrees/dot-destroy/backend" ]
[ -d ".worktrees/dot-destroy/frontend" ]
[ -f ".worktrees/dot-remove/.git" ]
[ -d ".worktrees/dot-remove/backend" ]
[ -d ".worktrees/dot-remove/frontend" ]

# Destroy — children must be removed before "." (root)
# Remove — children must be removed before "." (root)
# --force needed: the meta repo worktree is dirty (.worktrees/ + .gitignore changes)
run "$META_BIN" git worktree destroy dot-destroy --force
run "$META_BIN" git worktree remove dot-remove --force
[ "$status" -eq 0 ]
[ ! -d ".worktrees/dot-destroy" ]
[ ! -d ".worktrees/dot-remove" ]

# Original repos should be intact
[ -d "backend" ]
[ -d "frontend" ]

# Branches should still exist in original repos
git -C backend branch | grep -q "dot-destroy"
git -C frontend branch | grep -q "dot-destroy"
git -C backend branch | grep -q "dot-remove"
git -C frontend branch | grep -q "dot-remove"
}

# ============ Edge Cases: Add duplicate repo ============
Expand Down
22 changes: 11 additions & 11 deletions tests/worktree_cloud.bats
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ assert d['ttl_seconds'] == 1800, f'got: {d.get(\"ttl_seconds\")}'
[ "$status" -eq 0 ]
echo "$output" | python3 -c "import json,sys; assert json.load(sys.stdin)['ttl_seconds'] == 30"

"$META_BIN" git worktree destroy ttl-s --force
"$META_BIN" git worktree remove ttl-s --force

# Minutes
run "$META_BIN" git worktree create ttl-m --repo backend --ttl 5m --json
[ "$status" -eq 0 ]
echo "$output" | python3 -c "import json,sys; assert json.load(sys.stdin)['ttl_seconds'] == 300"

"$META_BIN" git worktree destroy ttl-m --force
"$META_BIN" git worktree remove ttl-m --force

# Days
run "$META_BIN" git worktree create ttl-d --repo backend --ttl 2d --json
Expand Down Expand Up @@ -292,7 +292,7 @@ assert entry['repos'][0]['alias'] == 'backend'
"
}

@test "worktree destroy removes from centralized store" {
@test "worktree remove removes from centralized store" {
"$META_BIN" git worktree create store-rm --repo backend --no-deps
STORE="$META_DATA/worktree.json"

Expand All @@ -305,7 +305,7 @@ assert any(v['name'] == 'store-rm' for v in data['worktrees'].values())
"

# Destroy
run "$META_BIN" git worktree destroy store-rm
run "$META_BIN" git worktree remove store-rm
[ "$status" -eq 0 ]

# Verify entry removed
Expand All @@ -317,10 +317,10 @@ assert not any(v['name'] == 'store-rm' for v in data['worktrees'].values())
"
}

@test "worktree destroy --json outputs structured result" {
@test "worktree remove --json outputs structured result" {
"$META_BIN" git worktree create destroy-json --repo backend --repo frontend --no-deps

run "$META_BIN" git worktree destroy destroy-json --json
run "$META_BIN" git worktree remove destroy-json --json
[ "$status" -eq 0 ]
echo "$output" | python3 -c "
import json, sys
Expand Down Expand Up @@ -413,7 +413,7 @@ assert 'repos' in payload
EOF

"$META_BIN" git worktree create hook-destroy --repo backend
run "$META_BIN" git worktree destroy hook-destroy --force
run "$META_BIN" git worktree remove hook-destroy --force
[ "$status" -eq 0 ]
[ -f "$HOOK_LOG" ]

Expand Down Expand Up @@ -740,7 +740,7 @@ assert wt['ephemeral'] == True
"

# Destroy
run "$META_BIN" git worktree destroy lifecycle-meta --force
run "$META_BIN" git worktree remove lifecycle-meta --force
[ "$status" -eq 0 ]
[ ! -d ".worktrees/lifecycle-meta" ]

Expand Down Expand Up @@ -877,7 +877,7 @@ assert len(data['repos']) == 1
[[ "$output" == *"name-after-repo"* ]]

# Clean up
run "$META_BIN" git worktree destroy name-after-repo --force
run "$META_BIN" git worktree remove name-after-repo --force
[ "$status" -eq 0 ]
}

Expand All @@ -890,7 +890,7 @@ assert len(data['repos']) == 1
[[ "$output" == *"--ephemeral"* ]] || [[ "$output" == *"ephemeral"* ]]
[[ "$output" == *"--meta"* ]] || [[ "$output" == *"meta"* ]]
[[ "$output" == *"--ttl"* ]] || [[ "$output" == *"ttl"* ]]
# Verify DESTROY OPTIONS section is present
[[ "$output" == *"DESTROY OPTIONS"* ]]
# Verify REMOVE OPTIONS section is present
[[ "$output" == *"REMOVE OPTIONS"* ]]
[[ "$output" == *"--force"* ]]
}