Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
addressing review comments
  • Loading branch information
skarim committed May 15, 2026
commit 3f3acde842968778ab5ccffb17b009740ba3e318
12 changes: 8 additions & 4 deletions cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ conflicts interactively.
Use --prune to delete local branches for merged PRs. Stack metadata is
preserved so that rebase and display logic continue to work correctly.
If you are on a branch that would be pruned, your checkout is moved to
the nearest active branch or the trunk.`,
the first active branch in the stack, or the trunk if all are merged.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runSync(cfg, opts)
},
Expand Down Expand Up @@ -417,9 +417,6 @@ func runSync(cfg *config.Config, opts *syncOptions) error {
if err := git.DeleteBranch(name, true); err != nil {
cfg.Warningf("Failed to delete %s: %v", name, err)
} else {
// Also remove the remote-tracking ref so that
// `git checkout <name>` doesn't recreate the branch.
_ = git.DeleteTrackingRef(remote, name)
cfg.Successf("Pruned %s (merged)", name)
pruned++
}
Expand All @@ -431,6 +428,13 @@ func runSync(cfg *config.Config, opts *syncOptions) error {
cfg.Printf("")
cfg.Printf("No merged branches to prune")
}

// Clean up remote-tracking refs for all merged branches, even if
// the local branch was already deleted. This prevents
// `git checkout <name>` from resurrecting the branch.
for _, b := range merged {
_ = git.DeleteTrackingRef(remote, b.Branch)
}
}

// --- Step 7: Update base SHAs and save ---
Expand Down
14 changes: 14 additions & 0 deletions cmd/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ func TestSync_Prune_DeletesMergedBranches(t *testing.T) {
writeStackFile(t, tmpDir, s)

var deletedBranches []string
var deletedTrackingRefs []string

mock := newSyncMock(tmpDir, "b2")
mock.BranchExistsFn = func(name string) bool { return true }
Expand All @@ -1043,6 +1044,10 @@ func TestSync_Prune_DeletesMergedBranches(t *testing.T) {
assert.True(t, force, "should force-delete merged branch")
return nil
}
mock.DeleteTrackingRefFn = func(remote, branch string) error {
deletedTrackingRefs = append(deletedTrackingRefs, remote+"/"+branch)
return nil
}

restore := git.SetOps(mock)
defer restore()
Expand All @@ -1060,6 +1065,7 @@ func TestSync_Prune_DeletesMergedBranches(t *testing.T) {

assert.NoError(t, err)
assert.Equal(t, []string{"b1"}, deletedBranches)
assert.Equal(t, []string{"origin/b1"}, deletedTrackingRefs, "should delete remote-tracking ref for pruned branch")
assert.Contains(t, output, "Pruned b1 (merged)")
assert.Contains(t, output, "Pruned 1 merged branch")
}
Expand Down Expand Up @@ -1087,6 +1093,12 @@ func TestSync_Prune_SkipsNonExistentBranches(t *testing.T) {
return nil
}

var deletedTrackingRefs []string
mock.DeleteTrackingRefFn = func(remote, branch string) error {
deletedTrackingRefs = append(deletedTrackingRefs, remote+"/"+branch)
return nil
}

restore := git.SetOps(mock)
defer restore()

Expand All @@ -1103,6 +1115,8 @@ func TestSync_Prune_SkipsNonExistentBranches(t *testing.T) {

assert.NoError(t, err)
assert.Contains(t, output, "No merged branches to prune")
// Tracking ref should still be cleaned up even though local branch is gone
assert.Equal(t, []string{"origin/b1"}, deletedTrackingRefs, "should delete tracking ref even when local branch is already gone")
}

// TestSync_Prune_SwitchesToLowestUnmergedBranch verifies that when the user is
Expand Down
Loading