fix for rev-parse error during sync

This commit is contained in:
Sameen Karim
2026-04-13 18:21:51 -04:00
parent 1f971988fe
commit 2ecb978fc0
2 changed files with 87 additions and 4 deletions
+10 -4
View File
@@ -125,10 +125,16 @@ func runSync(cfg *config.Config, opts *syncOptions) error {
// Sync PR state to detect merged PRs before rebasing.
syncStackPRs(cfg, s)
// Save original refs so we can restore on conflict
branchNames := make([]string, len(s.Branches))
for i, b := range s.Branches {
branchNames[i] = b.Branch
// Save original refs so we can restore on conflict.
// Merged branches that no longer exist locally have no ref to
// resolve. They are always skipped during rebase but we must
// also exclude them here to avoid a rev-parse error.
branchNames := make([]string, 0, len(s.Branches))
for _, b := range s.Branches {
if b.IsMerged() && !git.BranchExists(b.Branch) {
continue
}
branchNames = append(branchNames, b.Branch)
}
originalRefs, _ := git.RevParseMap(branchNames)
+77
View File
@@ -535,6 +535,7 @@ func TestSync_SquashMergedBranch_UsesOnto(t *testing.T) {
}
mock := newSyncMock(tmpDir, "b2")
mock.BranchExistsFn = func(name string) bool { return true }
// Trunk behind remote to trigger rebase
mock.RevParseFn = func(ref string) (string, error) {
if ref == "main" {
@@ -647,3 +648,79 @@ func TestSync_PushFailureAfterRebase(t *testing.T) {
assert.True(t, pushCalls[0].force, "push after rebase should use force")
assert.Contains(t, output, "Push failed")
}
func TestSync_MergedBranchDeletedFromRemote(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 1, Merged: true}},
{Branch: "b2"},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
var rebaseOntoCalls []rebaseCall
mock := newSyncMock(tmpDir, "b2")
mock.BranchExistsFn = func(name string) bool {
// b1 does not exist locally (deleted from remote after merge)
return name != "b1"
}
mock.RevParseMultiFn = func(refs []string) ([]string, error) {
shas := make([]string, len(refs))
for i, r := range refs {
if r == "b1" {
t.Fatalf("RevParseMulti should not be called with non-existent branch b1")
}
if r == "main" {
shas[i] = "local-sha"
} else if r == "origin/main" {
shas[i] = "remote-sha"
} else {
shas[i] = "sha-" + r
}
}
return shas, nil
}
// Trunk behind remote to trigger rebase
mock.RevParseFn = func(ref string) (string, error) {
if ref == "main" {
return "local-sha", nil
}
if ref == "origin/main" {
return "remote-sha", nil
}
return "sha-" + ref, nil
}
mock.IsAncestorFn = func(a, d string) (bool, error) {
return a == "local-sha" && d == "remote-sha", nil
}
mock.UpdateBranchRefFn = func(string, string) error { return nil }
mock.CheckoutBranchFn = func(string) error { return nil }
mock.RebaseOntoFn = func(newBase, oldBase, branch string) error {
rebaseOntoCalls = append(rebaseOntoCalls, rebaseCall{newBase, oldBase, branch})
return nil
}
restore := git.SetOps(mock)
defer restore()
cfg, _, errR := config.NewTestConfig()
cmd := SyncCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
errOut, _ := io.ReadAll(errR)
output := string(errOut)
assert.NoError(t, err)
assert.Contains(t, output, "Skipping b1")
// Only b2 should be rebased
require.Len(t, rebaseOntoCalls, 1)
assert.Equal(t, "b2", rebaseOntoCalls[0].branch)
}