Skip to content

fix(git_ops): clean up path-keyed .gitmodules after a failed add + idempotency tests (#62 P2)#75

Merged
bashandbone merged 2 commits into
mainfrom
test/p2-idempotency-partial-failure
Jun 30, 2026
Merged

fix(git_ops): clean up path-keyed .gitmodules after a failed add + idempotency tests (#62 P2)#75
bashandbone merged 2 commits into
mainfrom
test/p2-idempotency-partial-failure

Conversation

@bashandbone

Copy link
Copy Markdown
Owner

What

Addresses the #62 audit's P2 bullet "Idempotency / partial-failure untested." Writing the tests surfaced a real masked bug.

Bug: failed add leaves a stale .gitmodules entry

When a submodule already exists (so .gitmodules is present) and a later add fails against an unreachable URL, a stale [submodule "<path>"] entry was left behind.

Root cause: the add fallback cleanup (src/git_ops/mod.rs) removed the .gitmodules section by opts.name, but git2 writes the section keyed by path (it uses the path as the section key when name != path). The name-keyed match missed git2's path-keyed entry. Fix: match both the name- and path-keyed section headers during cleanup (4 lines).

Tests (TDD)

Test Kind
test_failed_add_leaves_no_partial_state RED→GREEN: baseline add, then a failed add → asserts no stale .gitmodules/git-config entry, no orphan worktree dir, no dangling .git/modules, and that the pre-existing submodule survives (non-vacuous)
test_add_same_submodule_twice_is_idempotent characterization: re-add same name+path → exactly one entry in .gitmodules, git config, and submod.toml
test_delete_nonexistent_submodule_fails characterization: delete missing submodule → non-zero exit + specific ghost ... not found error

All assertions were probed against the real binary first (e.g. .git/config stores .url not .path; .gitmodules sections are path-keyed) so the counts are accurate and non-vacuous.

Verification

Full suite 558 pass, 0 fail; cargo fmt + clippy --all-features --tests clean. Fix confirmed load-bearing (the partial-state test is RED without it).

🤖 Generated with Claude Code

#62 P2)

The #62 audit (P2) asked for idempotency / partial-failure coverage. Writing the
tests surfaced a real masked bug: when a submodule already exists (so .gitmodules
is present) and a subsequent `add` fails against an unreachable URL, a stale
`[submodule "<path>"]` entry was left behind in .gitmodules.

Root cause: the add fallback cleanup (git_ops/mod.rs) removed the .gitmodules
section by `opts.name`, but git2 writes the section keyed by *path* (it uses the
path as the section key when name != path). The name-keyed match missed git2's
path-keyed entry, so it lingered after the failed add. Fix: match both the name-
and path-keyed section headers during cleanup.

TDD:
- test_failed_add_leaves_no_partial_state — baseline add, then a failed add;
  asserts no stale .gitmodules/git-config entry, no orphan worktree dir, no
  dangling .git/modules, AND that the pre-existing submodule survives
  (non-vacuous). RED before the fix, GREEN after.
- test_add_same_submodule_twice_is_idempotent — re-adding the same name+path
  succeeds with exactly one entry in .gitmodules, git config, and submod.toml
  (characterization; behavior was already correct).
- test_delete_nonexistent_submodule_fails — deleting a missing submodule exits
  non-zero with a specific "ghost ... not found" error (characterization).

Full suite 558 pass; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8D5ZK1473YCiZkbueAY2X
@bashandbone bashandbone merged commit 598747b into main Jun 30, 2026
5 of 8 checks passed
@bashandbone bashandbone deleted the test/p2-idempotency-partial-failure branch June 30, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant