fix(sync): atomic export and force-push detection (bd-3bhl, bd-4hh5)

bd-3bhl: Add sync rollback on git commit failure
- Use exportToJSONLDeferred() instead of exportToJSONL() for atomic sync
- Call finalizeExport() only after git commit succeeds
- Rollback JSONL from git HEAD on commit failure
- Add rollbackJSONLFromGit() helper function
- Coverage: regular commit, sync branch, external beads repo paths

bd-4hh5: Fix false-positive force-push detection
- Use explicit refspec in CheckForcePush fetch
- +refs/heads/beads-sync:refs/remotes/origin/beads-sync
- Ensures tracking ref is always created/updated
- Fixes stale ref comparison causing false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
emma
2026-01-04 23:13:56 -08:00
committed by Steve Yegge
parent d7221f6858
commit 9b84ef73dd
3 changed files with 88 additions and 5 deletions

View File

@@ -79,7 +79,13 @@ func CheckForcePush(ctx context.Context, store storage.Storage, repoRoot, syncBr
status.Remote = getRemoteForBranch(ctx, worktreePath, syncBranch)
// Fetch from remote to get latest state
fetchCmd := exec.CommandContext(ctx, "git", "-C", repoRoot, "fetch", status.Remote, syncBranch) // #nosec G204 - repoRoot/syncBranch are validated git inputs
// bd-4hh5: Use explicit refspec to ensure the remote-tracking ref is always updated.
// Without an explicit refspec, `git fetch origin beads-sync` only updates
// refs/remotes/origin/beads-sync if it already exists. On fresh clones or
// after ref cleanup, this can leave the tracking ref stale, causing
// false-positive force-push detection when comparing against wrong commits.
refspec := fmt.Sprintf("+refs/heads/%s:refs/remotes/%s/%s", syncBranch, status.Remote, syncBranch)
fetchCmd := exec.CommandContext(ctx, "git", "-C", repoRoot, "fetch", status.Remote, refspec) // #nosec G204 - repoRoot/syncBranch are validated git inputs
fetchOutput, err := fetchCmd.CombinedOutput()
if err != nil {
// Check if remote branch doesn't exist