Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
46 changes: 46 additions & 0 deletions cmd/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func newSyncMock(tmpDir string, currentBranch string) *git.MockOps {
return &git.MockOps{
GitDirFn: func() (string, error) { return tmpDir, nil },
CurrentBranchFn: func() (string, error) { return currentBranch, nil },
BranchExistsFn: func(name string) bool { return true },
RevParseFn: func(ref string) (string, error) {
// Default: origin/<branch> returns same SHA as <branch> (no FF needed)
if strings.HasPrefix(ref, "origin/") {
Expand Down Expand Up @@ -403,6 +404,51 @@ func TestSync_TrunkDiverged(t *testing.T) {
assert.False(t, pushCalls[0].force, "push should not use force when no rebase")
}

// TestSync_NoLocalTrunk_SkipsSilently verifies that when the trunk branch
// does not exist locally (only origin/main exists), sync skips the
// fast-forward silently without emitting a warning.
func TestSync_NoLocalTrunk_SkipsSilently(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1"},
},
}

tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)

var pushCalls []pushCall

mock := newSyncMock(tmpDir, "b1")
// Trunk does not exist locally.
mock.BranchExistsFn = func(name string) bool { return name != "main" }
mock.PushFn = func(remote string, branches []string, force, atomic bool) error {
pushCalls = append(pushCalls, pushCall{remote, branches, force, atomic})
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.NotContains(t, output, "Could not compare trunk")
assert.NotContains(t, output, "skipping trunk update")

// Push should still happen
require.Len(t, pushCalls, 1)
}

// TestSync_RebaseConflict_RestoresAll verifies that when a rebase conflict
// occurs during sync, all branches are restored to their original state.
func TestSync_RebaseConflict_RestoresAll(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,12 @@ func resolveOriginalRefs(s *stack.Stack) (map[string]string, error) {
// fastForwardTrunk fast-forwards the trunk branch to match its remote tracking
// branch. Returns true if trunk was updated.
func fastForwardTrunk(cfg *config.Config, trunk, remote, currentBranch string) bool {
// If the local trunk branch doesn't exist, there's nothing to
// fast-forward. The remote tracking ref is sufficient for rebasing.
if !git.BranchExists(trunk) {
return false
}

localSHA, remoteSHA := "", ""
trunkRefs, trunkErr := git.RevParseMulti([]string{trunk, remote + "/" + trunk})
if trunkErr == nil {
Expand Down
Loading