Skip to content

Commit 2e958e6

Browse files
committed
fix for rev-parse error during sync
1 parent 08a7f7b commit 2e958e6

2 files changed

Lines changed: 95 additions & 13 deletions

File tree

cmd/sync.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,16 @@ func runSync(cfg *config.Config, opts *syncOptions) error {
129129
// Sync PR state to detect merged PRs before rebasing.
130130
syncStackPRs(cfg, s)
131131

132-
// Save original refs so we can restore on conflict
133-
branchNames := make([]string, len(s.Branches))
134-
for i, b := range s.Branches {
135-
branchNames[i] = b.Branch
132+
// Save original refs so we can restore on conflict.
133+
// Merged branches that no longer exist locally have no ref to
134+
// resolve. They are always skipped during rebase but we must
135+
// also exclude them here to avoid a rev-parse error.
136+
branchNames := make([]string, 0, len(s.Branches))
137+
for _, b := range s.Branches {
138+
if b.IsMerged() && !git.BranchExists(b.Branch) {
139+
continue
140+
}
141+
branchNames = append(branchNames, b.Branch)
136142
}
137143
originalRefs, _ := git.RevParseMap(branchNames)
138144

cmd/sync_test.go

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ func TestSync_SquashMergedBranch_UsesOnto(t *testing.T) {
549549
}
550550

551551
mock := newSyncMock(tmpDir, "b2")
552+
mock.BranchExistsFn = func(name string) bool { return true }
552553
// Trunk behind remote to trigger rebase
553554
mock.RevParseFn = func(ref string) (string, error) {
554555
if ref == "main" {
@@ -765,8 +766,8 @@ func TestSync_BranchFastForward_WithTrunkUpdate(t *testing.T) {
765766
writeStackFile(t, tmpDir, s)
766767

767768
var updateBranchRefCalls []struct{ branch, sha string }
768-
var rebaseCalls []rebaseCall
769-
var pushCalls []pushCall
769+
var rebaseCalls2 []rebaseCall
770+
var pushCalls2 []pushCall
770771

771772
mock := newSyncMock(tmpDir, "b1")
772773
// Trunk and b2 both behind remote
@@ -803,15 +804,15 @@ func TestSync_BranchFastForward_WithTrunkUpdate(t *testing.T) {
803804
}
804805
mock.CheckoutBranchFn = func(string) error { return nil }
805806
mock.RebaseFn = func(base string) error {
806-
rebaseCalls = append(rebaseCalls, rebaseCall{branch: "(rebase)" + base})
807+
rebaseCalls2 = append(rebaseCalls2, rebaseCall{branch: "(rebase)" + base})
807808
return nil
808809
}
809810
mock.RebaseOntoFn = func(newBase, oldBase, branch string) error {
810-
rebaseCalls = append(rebaseCalls, rebaseCall{newBase, oldBase, branch})
811+
rebaseCalls2 = append(rebaseCalls2, rebaseCall{newBase, oldBase, branch})
811812
return nil
812813
}
813814
mock.PushFn = func(remote string, branches []string, force, atomic bool) error {
814-
pushCalls = append(pushCalls, pushCall{remote, branches, force, atomic})
815+
pushCalls2 = append(pushCalls2, pushCall{remote, branches, force, atomic})
815816
return nil
816817
}
817818

@@ -829,7 +830,6 @@ func TestSync_BranchFastForward_WithTrunkUpdate(t *testing.T) {
829830
output := string(errOut)
830831

831832
assert.NoError(t, err)
832-
833833
// Both trunk and b2 should be updated
834834
branchUpdates := make(map[string]string)
835835
for _, c := range updateBranchRefCalls {
@@ -839,7 +839,83 @@ func TestSync_BranchFastForward_WithTrunkUpdate(t *testing.T) {
839839
assert.Equal(t, "b2-remote", branchUpdates["b2"], "b2 should be fast-forwarded")
840840

841841
assert.Contains(t, output, "fast-forwarded")
842-
assert.NotEmpty(t, rebaseCalls, "rebase should occur")
843-
require.Len(t, pushCalls, 1)
844-
assert.True(t, pushCalls[0].force, "push should use force after rebase")
842+
assert.NotEmpty(t, rebaseCalls2, "rebase should occur")
843+
require.Len(t, pushCalls2, 1)
844+
assert.True(t, pushCalls2[0].force, "push should use force after rebase")
845+
}
846+
847+
func TestSync_MergedBranchDeletedFromRemote(t *testing.T) {
848+
s := stack.Stack{
849+
Trunk: stack.BranchRef{Branch: "main"},
850+
Branches: []stack.BranchRef{
851+
{Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 1, Merged: true}},
852+
{Branch: "b2"},
853+
},
854+
}
855+
856+
tmpDir := t.TempDir()
857+
writeStackFile(t, tmpDir, s)
858+
859+
var rebaseOntoCalls []rebaseCall
860+
861+
mock := newSyncMock(tmpDir, "b2")
862+
mock.BranchExistsFn = func(name string) bool {
863+
// b1 does not exist locally (deleted from remote after merge)
864+
return name != "b1"
865+
}
866+
mock.RevParseMultiFn = func(refs []string) ([]string, error) {
867+
shas := make([]string, len(refs))
868+
for i, r := range refs {
869+
if r == "b1" {
870+
t.Fatalf("RevParseMulti should not be called with non-existent branch b1")
871+
}
872+
if r == "main" {
873+
shas[i] = "local-sha"
874+
} else if r == "origin/main" {
875+
shas[i] = "remote-sha"
876+
} else {
877+
shas[i] = "sha-" + r
878+
}
879+
}
880+
return shas, nil
881+
}
882+
// Trunk behind remote to trigger rebase
883+
mock.RevParseFn = func(ref string) (string, error) {
884+
if ref == "main" {
885+
return "local-sha", nil
886+
}
887+
if ref == "origin/main" {
888+
return "remote-sha", nil
889+
}
890+
return "sha-" + ref, nil
891+
}
892+
mock.IsAncestorFn = func(a, d string) (bool, error) {
893+
return a == "local-sha" && d == "remote-sha", nil
894+
}
895+
mock.UpdateBranchRefFn = func(string, string) error { return nil }
896+
mock.CheckoutBranchFn = func(string) error { return nil }
897+
mock.RebaseOntoFn = func(newBase, oldBase, branch string) error {
898+
rebaseOntoCalls = append(rebaseOntoCalls, rebaseCall{newBase, oldBase, branch})
899+
return nil
900+
}
901+
902+
restore := git.SetOps(mock)
903+
defer restore()
904+
905+
cfg, _, errR := config.NewTestConfig()
906+
cmd := SyncCmd(cfg)
907+
cmd.SetOut(io.Discard)
908+
cmd.SetErr(io.Discard)
909+
err := cmd.Execute()
910+
911+
cfg.Err.Close()
912+
errOut, _ := io.ReadAll(errR)
913+
output := string(errOut)
914+
915+
assert.NoError(t, err)
916+
assert.Contains(t, output, "Skipping b1")
917+
918+
// Only b2 should be rebased
919+
require.Len(t, rebaseOntoCalls, 1)
920+
assert.Equal(t, "b2", rebaseOntoCalls[0].branch)
845921
}

0 commit comments

Comments
 (0)