Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Exceptions are acceptable depending on the circumstances (critical bug fixes tha
- changed the Go module dependencies to their latest versions
- changed per-repo logging in parallel operations to run inside goroutines for progressive feedback

### Fixed

- fixed `RestoreAfterSync` to stay on the default branch after a successful sync

## [0.4.0] - 2026-03-31

### Added
Expand Down
14 changes: 7 additions & 7 deletions internal/repo/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func SyncAndRestore(
return SyncResult{Name: name, Status: fmt.Sprintf("FAIL (pull --rebase: %v)", err)}
}

return RestoreAfterSync(repoPath, name, defaultBranch, currentBranch, wipBranch, isDirty, runner)
return FinalizeSync(repoPath, name, defaultBranch, wipBranch, isDirty, runner)
}

// RestoreBranch restores the appropriate branch after a failure.
Expand All @@ -163,20 +163,20 @@ func RestoreBranch(repoPath, currentBranch, wipBranch string, isDirty bool, runn
}
}

// RestoreAfterSync restores the original branch state after a successful sync.
func RestoreAfterSync(
repoPath, name, defaultBranch, currentBranch, wipBranch string, isDirty bool, runner GitRunner,
// FinalizeSync rebases the WIP branch (if dirty) and ensures the repo ends on the default branch.
func FinalizeSync(
repoPath, name, defaultBranch, wipBranch string, isDirty bool, runner GitRunner,
) SyncResult {
status := "synced"
if isDirty {
_ = runner.Run(repoPath, "checkout", wipBranch)
if err := runner.Run(repoPath, "rebase", defaultBranch); err != nil {
_ = runner.Run(repoPath, "rebase", "--abort")
}
_ = runner.Run(repoPath, "checkout", currentBranch)
if err := runner.Run(repoPath, "checkout", defaultBranch); err != nil {
return SyncResult{Name: name, Status: fmt.Sprintf("FAIL (checkout %s: %v)", defaultBranch, err)}
}
status = fmt.Sprintf("synced (wip: %s)", wipBranch)
} else if currentBranch != defaultBranch {
_ = runner.Run(repoPath, "checkout", currentBranch)
}
return SyncResult{Name: name, Status: status}
}
61 changes: 54 additions & 7 deletions internal/repo/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
t.Parallel()
// given
runner := doubles.NewGitRunnerStub().
WithRunError([]string{"checkout", "-B", "wip/main"}, errors.New("checkout failed"))

Check failure on line 92 in internal/repo/sync_test.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "checkout failed" 3 times.

See more on https://sonarcloud.io/project/issues?id=rios0rios0_versainit&issues=AZ1PbeTxYnmc4uF9uo4t&open=AZ1PbeTxYnmc4uF9uo4t&pullRequest=56

// when
result, ok := repo.SaveWIPState("/repo", "my-repo", "main", "wip/main", runner)
Expand Down Expand Up @@ -198,6 +198,26 @@
// then
assert.Contains(t, result.Status, "FAIL (pull --rebase")
})

t.Run("should stay on default branch when starting from non-default branch", func(t *testing.T) {
t.Parallel()
// given
var checkouts []string
runner := doubles.NewGitRunnerStub()
runner.RunFunc = func(_ string, args ...string) error {
if len(args) > 0 && args[0] == "checkout" {
checkouts = append(checkouts, args[1])
}
return nil
}

// when
result := repo.SyncAndRestore("/repo", "my-repo", "main", "feat/x", "wip/feat/x", false, runner)

// then
assert.Equal(t, "synced", result.Status)
assert.Equal(t, []string{"main"}, checkouts)
})
}

func TestRestoreBranch(t *testing.T) {
Expand Down Expand Up @@ -242,7 +262,7 @@
})
}

func TestRestoreAfterSync(t *testing.T) {
func TestFinalizeSync(t *testing.T) {
t.Parallel()

t.Run("should return synced for clean repo on default branch", func(t *testing.T) {
Expand All @@ -251,13 +271,13 @@
runner := doubles.NewGitRunnerStub()

// when
result := repo.RestoreAfterSync("/repo", "my-repo", "main", "main", "wip/main", false, runner)
result := repo.FinalizeSync("/repo", "my-repo", "main", "wip/main", false, runner)

// then
assert.Equal(t, "synced", result.Status)
})

t.Run("should checkout original branch when not on default", func(t *testing.T) {
t.Run("should stay on default branch when clean repo was on non-default branch", func(t *testing.T) {
t.Parallel()
// given
var checkedOut string
Expand All @@ -270,23 +290,50 @@
}

// when
result := repo.RestoreAfterSync("/repo", "my-repo", "main", "feat/x", "wip/feat/x", false, runner)
result := repo.FinalizeSync("/repo", "my-repo", "main", "wip/feat/x", false, runner)

// then
assert.Equal(t, "synced", result.Status)
assert.Equal(t, "feat/x", checkedOut)
assert.Empty(t, checkedOut)
})

t.Run("should rebase WIP branch and return wip status for dirty repo", func(t *testing.T) {
t.Run("should rebase WIP branch and checkout default branch for dirty repo", func(t *testing.T) {
t.Parallel()
// given
var lastCheckedOut string
runner := doubles.NewGitRunnerStub()
runner.RunFunc = func(_ string, args ...string) error {
if len(args) > 0 && args[0] == "checkout" {
lastCheckedOut = args[1]
}
return nil
}

// when
result := repo.RestoreAfterSync("/repo", "my-repo", "main", "feat/x", "wip/feat/x", true, runner)
result := repo.FinalizeSync("/repo", "my-repo", "main", "wip/feat/x", true, runner)

// then
assert.Contains(t, result.Status, "synced (wip: wip/feat/x)")
assert.Equal(t, "main", lastCheckedOut)
})

t.Run("should return FAIL when checkout to default branch fails for dirty repo", func(t *testing.T) {
t.Parallel()
// given
runner := doubles.NewGitRunnerStub()
runner.RunFunc = func(_ string, args ...string) error {
if len(args) >= 2 && args[0] == "checkout" && args[1] == "main" {
return errors.New("checkout failed")
}
return nil
}

// when
result := repo.FinalizeSync("/repo", "my-repo", "main", "wip/feat/x", true, runner)

// then
assert.Contains(t, result.Status, "FAIL")
assert.Contains(t, result.Status, "checkout main")
})
}

Expand Down
Loading