Skip to content

Commit 19ac52a

Browse files
committed
fix(repository): replace Pull with Fetch+Reset to avoid go-git non-fast-forward bug
The go-git library's Pull() function has a known issue (#358) where it returns 'non-fast-forward update' errors even when a fast-forward merge is possible. This happens when local branches don't have proper upstream tracking configured. This commit replaces Pull() with an explicit Fetch() + Hard Reset approach: 1. Fetch latest changes from remote with Force=true 2. Get the remote reference for the target branch 3. Hard reset the worktree to the remote ref 4. Update the local branch reference This approach: - Avoids the go-git Pull() bug entirely - Is more explicit about what we want (always match remote) - Handles force-push scenarios correctly - Works regardless of tracking configuration Fixes: go-git/go-git#358
1 parent 05349b5 commit 19ac52a

File tree

1 file changed

+28
-14
lines changed

1 file changed

+28
-14
lines changed

internal/repository/providers/standard/repository.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,39 @@ func (p *GitProvider) Bundle(ref string) ([]byte, error) {
128128
return nil, fmt.Errorf("failed to checkout branch %s: %w", reference.Name(), err)
129129
}
130130

131-
// Pull latest changes
132-
pullOpts := &git.PullOptions{
131+
// Fetch latest changes from remote (instead of Pull which has issues with go-git)
132+
// See: https://github.com/go-git/go-git/issues/358
133+
log.Infof("fetching latest changes for repo %s", p.RepoURL)
134+
err = p.gitRepository.Fetch(&git.FetchOptions{
133135
Auth: p.AuthMethod,
134136
RemoteName: remote,
137+
Force: true,
138+
})
139+
if err != nil && err != git.NoErrAlreadyUpToDate {
140+
log.Warnf("failed to fetch: %v", err)
135141
}
136142

137-
log.Infof("pulling latest changes for repo %s on branch %s", p.RepoURL, reference.Name())
138-
err = worktree.Pull(pullOpts)
143+
// Get the latest remote ref and hard reset to it
144+
remoteRefName := getRemoteReferenceName(ref)
145+
remoteRef, err := p.gitRepository.Reference(remoteRefName, true)
139146
if err != nil {
140-
if err == git.NoErrAlreadyUpToDate {
141-
log.Info("repository is already up-to-date")
142-
} else {
143-
log.Warnf("failed to pull latest changes for ref %s: %v, deleting local repository", reference.Name(), err)
144-
p.gitRepository = nil
145-
if removeErr := os.RemoveAll(p.repositoryPath); removeErr != nil {
146-
return nil, fmt.Errorf("failed to remove repository at %s: %w", p.repositoryPath, removeErr)
147-
}
148-
return nil, fmt.Errorf("failed to pull latest changes for ref %s: %w, likely because of force-push, next run will re-clone", reference.Name(), err)
149-
}
147+
return nil, fmt.Errorf("failed to get remote reference %s: %w", remoteRefName, err)
148+
}
149+
150+
log.Infof("resetting to remote ref %s (%s)", remoteRefName, remoteRef.Hash().String())
151+
err = worktree.Reset(&git.ResetOptions{
152+
Commit: remoteRef.Hash(),
153+
Mode: git.HardReset,
154+
})
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to reset to remote ref %s: %w", remoteRefName, err)
157+
}
158+
159+
// Update local branch reference to match remote
160+
reference = plumbing.NewHashReference(localRefName, remoteRef.Hash())
161+
err = p.gitRepository.Storer.SetReference(reference)
162+
if err != nil {
163+
return nil, fmt.Errorf("failed to update local branch reference: %w", err)
150164
}
151165

152166
// Create git bundle

0 commit comments

Comments
 (0)