Skip to content

Commit dd5ad69

Browse files
committed
sync: skip trunk fast-forward silently when local branch doesn't exist
When the trunk branch (e.g. main) doesn't exist locally — only the remote tracking ref (origin/main) exists — `fastForwardTrunk` called `git rev-parse main origin/main` which failed, emitting: ⚠ Could not compare trunk main with remote — skipping trunk update This also caused `stackNeedsRebase` to always return true (since `IsAncestor("main", ...)` errors out), forcing an unnecessary rebase and force-push on every sync. Add a `BranchExists` check at the top of `fastForwardTrunk`. If the local trunk doesn't exist, return silently — there's nothing to fast-forward, and the remote tracking ref is sufficient for rebasing via git's DWIM resolution.
1 parent 44aac93 commit dd5ad69

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

cmd/sync_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func newSyncMock(tmpDir string, currentBranch string) *git.MockOps {
2828
return &git.MockOps{
2929
GitDirFn: func() (string, error) { return tmpDir, nil },
3030
CurrentBranchFn: func() (string, error) { return currentBranch, nil },
31+
BranchExistsFn: func(name string) bool { return true },
3132
RevParseFn: func(ref string) (string, error) {
3233
// Default: origin/<branch> returns same SHA as <branch> (no FF needed)
3334
if strings.HasPrefix(ref, "origin/") {
@@ -403,6 +404,51 @@ func TestSync_TrunkDiverged(t *testing.T) {
403404
assert.False(t, pushCalls[0].force, "push should not use force when no rebase")
404405
}
405406

407+
// TestSync_NoLocalTrunk_SkipsSilently verifies that when the trunk branch
408+
// does not exist locally (only origin/main exists), sync skips the
409+
// fast-forward silently without emitting a warning.
410+
func TestSync_NoLocalTrunk_SkipsSilently(t *testing.T) {
411+
s := stack.Stack{
412+
Trunk: stack.BranchRef{Branch: "main"},
413+
Branches: []stack.BranchRef{
414+
{Branch: "b1"},
415+
},
416+
}
417+
418+
tmpDir := t.TempDir()
419+
writeStackFile(t, tmpDir, s)
420+
421+
var pushCalls []pushCall
422+
423+
mock := newSyncMock(tmpDir, "b1")
424+
// Trunk does not exist locally.
425+
mock.BranchExistsFn = func(name string) bool { return name != "main" }
426+
mock.PushFn = func(remote string, branches []string, force, atomic bool) error {
427+
pushCalls = append(pushCalls, pushCall{remote, branches, force, atomic})
428+
return nil
429+
}
430+
431+
restore := git.SetOps(mock)
432+
defer restore()
433+
434+
cfg, _, errR := config.NewTestConfig()
435+
cmd := SyncCmd(cfg)
436+
cmd.SetOut(io.Discard)
437+
cmd.SetErr(io.Discard)
438+
err := cmd.Execute()
439+
440+
cfg.Err.Close()
441+
errOut, _ := io.ReadAll(errR)
442+
output := string(errOut)
443+
444+
assert.NoError(t, err)
445+
assert.NotContains(t, output, "Could not compare trunk")
446+
assert.NotContains(t, output, "skipping trunk update")
447+
448+
// Push should still happen
449+
require.Len(t, pushCalls, 1)
450+
}
451+
406452
// TestSync_RebaseConflict_RestoresAll verifies that when a rebase conflict
407453
// occurs during sync, all branches are restored to their original state.
408454
func TestSync_RebaseConflict_RestoresAll(t *testing.T) {

cmd/utils.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,12 @@ func resolveOriginalRefs(s *stack.Stack) (map[string]string, error) {
696696
// fastForwardTrunk fast-forwards the trunk branch to match its remote tracking
697697
// branch. Returns true if trunk was updated.
698698
func fastForwardTrunk(cfg *config.Config, trunk, remote, currentBranch string) bool {
699+
// If the local trunk branch doesn't exist, there's nothing to
700+
// fast-forward. The remote tracking ref is sufficient for rebasing.
701+
if !git.BranchExists(trunk) {
702+
return false
703+
}
704+
699705
localSHA, remoteSHA := "", ""
700706
trunkRefs, trunkErr := git.RevParseMulti([]string{trunk, remote + "/" + trunk})
701707
if trunkErr == nil {

0 commit comments

Comments
 (0)