fix: always set upstream tracking on git push and fix task worktree startPoint (#536)

This commit is contained in:
Carlos Florêncio
2026-04-03 15:40:40 +01:00
committed by GitHub
parent 653ec2a1df
commit bccd4d5f40
4 changed files with 143 additions and 8 deletions
@@ -186,9 +186,9 @@ func (g *GitOperator) getCurrentBranch(ctx context.Context) (string, error) {
return strings.TrimSpace(output), nil
}
// getUpstreamRef returns the upstream tracking ref for the given branch, or "" if none is set.
func (g *GitOperator) getUpstreamRef(ctx context.Context, branch string) string {
output, err := g.runGitCommand(ctx, "rev-parse", "--abbrev-ref", "--symbolic-full-name", branch+"@{upstream}")
// getUpstreamRef returns the current branch's upstream tracking ref, or "" if none is set.
func (g *GitOperator) getUpstreamRef(ctx context.Context) string {
output, err := g.runGitCommand(ctx, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}")
if err != nil {
return ""
}
@@ -259,7 +259,7 @@ func (g *GitOperator) Pull(ctx context.Context, rebase bool) (*GitOperationResul
// Use upstream branch if set, otherwise fall back to default (main/master).
// This handles local branches that haven't been pushed to the remote yet.
pullBranch := branch
if upstream := g.getUpstreamRef(ctx, branch); upstream == "" {
if upstream := g.getUpstreamRef(ctx); upstream == "" {
if defaultBranch := g.getDefaultRemoteBranch(ctx); defaultBranch != "" {
pullBranch = defaultBranch
}
@@ -312,8 +312,8 @@ func (g *GitOperator) Push(ctx context.Context, force bool, setUpstream bool) (*
}
args := []string{"push"}
if setUpstream {
shouldSetUpstream := setUpstream || g.getUpstreamRef(ctx) == ""
if shouldSetUpstream {
args = append(args, "--set-upstream")
}
@@ -333,7 +333,10 @@ func (g *GitOperator) Push(ctx context.Context, force bool, setUpstream bool) (*
}
result.Success = true
g.logger.Info("push completed", zap.String("branch", branch), zap.Bool("force", force), zap.Bool("set_upstream", setUpstream))
g.logger.Info("push completed",
zap.String("branch", branch),
zap.Bool("force", force),
zap.Bool("set_upstream", shouldSetUpstream))
return result, nil
}
@@ -0,0 +1,52 @@
package process
import (
"context"
"path/filepath"
"strings"
"testing"
)
func TestGitOperatorPush_PreservesExistingUpstream(t *testing.T) {
repoDir, cleanup := setupTestRepo(t)
defer cleanup()
log := newTestLogger(t)
runGit(t, repoDir, "checkout", "-b", "feature/pr-branch")
writeFile(t, repoDir, "feature.txt", "feature branch\n")
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "feature commit")
runGit(t, repoDir, "push", "-u", "origin", "feature/pr-branch")
runGit(t, repoDir, "checkout", "main")
worktreeBase := t.TempDir()
directDir := filepath.Join(worktreeBase, "wt-direct")
suffixedDir := filepath.Join(worktreeBase, "wt-sfx")
runGit(t, repoDir, "worktree", "add", directDir, "feature/pr-branch")
runGit(t, repoDir, "worktree", "add", "-b", "feature/pr-branch-sfx", suffixedDir, "origin/feature/pr-branch")
runGit(t, suffixedDir, "branch", "--set-upstream-to=origin/feature/pr-branch", "feature/pr-branch-sfx")
writeFile(t, suffixedDir, "feature.txt", "feature branch\nlocal change\n")
runGit(t, suffixedDir, "add", ".")
runGit(t, suffixedDir, "commit", "-m", "local change")
before := strings.TrimSpace(runGit(t, suffixedDir, "rev-parse", "--abbrev-ref", "@{upstream}"))
if before != "origin/feature/pr-branch" {
t.Fatalf("upstream before push = %q, want %q", before, "origin/feature/pr-branch")
}
gitOp := NewGitOperator(suffixedDir, log, nil)
result, err := gitOp.Push(context.Background(), false, false)
if err != nil {
t.Fatalf("Push returned error: %v", err)
}
if !result.Success {
t.Fatalf("Push failed: %+v", result)
}
after := strings.TrimSpace(runGit(t, suffixedDir, "rev-parse", "--abbrev-ref", "@{upstream}"))
if after != "origin/feature/pr-branch" {
t.Fatalf("upstream after push = %q, want %q", after, "origin/feature/pr-branch")
}
}
+5 -1
View File
@@ -342,7 +342,11 @@ func (m *Manager) createInTaskDir(ctx context.Context, req CreateRequest, baseRe
if err != nil {
return nil, err
}
startPoint = req.CheckoutBranch
if fetchResult.StartPoint != "" {
startPoint = fetchResult.StartPoint
} else {
startPoint = req.CheckoutBranch
}
}
worktreeID, branchName, err := m.addWorktreeForBranch(ctx, req, worktreePath, branchName, startPoint, baseRef)
@@ -0,0 +1,76 @@
package worktree
import (
"context"
"strings"
"testing"
)
func TestCreateInTaskDir_CheckoutBranchUsesRemoteStartPointAndUpstream(t *testing.T) {
cfg := newTestConfig(t)
cfg.TasksBasePath = cfg.BasePath
log := newTestLogger()
store := newMockStore()
mgr, err := NewManager(cfg, store, log)
if err != nil {
t.Fatalf("NewManager failed: %v", err)
}
repoPath := initGitRepoWithRemote(t)
wt1, err := mgr.Create(context.Background(), CreateRequest{
TaskID: "task-1",
SessionID: "session-1",
TaskTitle: "PR Review 1",
RepositoryID: "repo-1",
RepositoryPath: repoPath,
BaseBranch: "main",
CheckoutBranch: "feature/pr-branch",
TaskDirName: "task-1",
RepoName: "repo-1",
})
if err != nil {
t.Fatalf("Create() first task-dir worktree: %v", err)
}
if wt1.Branch != "feature/pr-branch" {
t.Fatalf("first worktree branch = %q, want %q", wt1.Branch, "feature/pr-branch")
}
wt2, err := mgr.Create(context.Background(), CreateRequest{
TaskID: "task-2",
SessionID: "session-2",
TaskTitle: "PR Review 2",
RepositoryID: "repo-1",
RepositoryPath: repoPath,
BaseBranch: "main",
CheckoutBranch: "feature/pr-branch",
TaskDirName: "task-2",
RepoName: "repo-1",
})
if err != nil {
t.Fatalf("Create() second task-dir worktree: %v", err)
}
if !strings.HasPrefix(wt2.Branch, "feature/pr-branch-") {
t.Fatalf("second worktree branch = %q, want feature/pr-branch-*", wt2.Branch)
}
if wt2.FetchWarning != "" {
t.Fatalf("second worktree fetch warning = %q, want empty", wt2.FetchWarning)
}
sha1 := strings.TrimSpace(runGit(t, wt1.Path, "rev-parse", "HEAD"))
sha2 := strings.TrimSpace(runGit(t, wt2.Path, "rev-parse", "HEAD"))
if sha1 != sha2 {
t.Fatalf("worktree SHAs differ: wt1=%q wt2=%q", sha1, sha2)
}
const wantUpstream = "origin/feature/pr-branch"
upstream1 := strings.TrimSpace(runGit(t, wt1.Path, "rev-parse", "--abbrev-ref", "@{upstream}"))
if upstream1 != wantUpstream {
t.Fatalf("first worktree upstream = %q, want %q", upstream1, wantUpstream)
}
upstream2 := strings.TrimSpace(runGit(t, wt2.Path, "rev-parse", "--abbrev-ref", "@{upstream}"))
if upstream2 != wantUpstream {
t.Fatalf("second worktree upstream = %q, want %q", upstream2, wantUpstream)
}
}