From dde4a80a920366f7b446c55b3b2993cc2a9f8ba1 Mon Sep 17 00:00:00 2001 From: Brian Kildow Date: Mon, 27 Apr 2026 20:49:24 -0400 Subject: [PATCH] Skip behind-count when @{upstream} can't resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-check upstream resolvability with `git rev-parse --verify --quiet @{upstream}` before running rev-list. When branch..merge points at a non-existent remote ref, rev-list emits `fatal: no such branch: 'HEAD..'`, which the prior stderr substring match didn't catch — so `wt sync` printed a noisy "could not check upstream" warning for every worktree whose tracking ref was gone. The pre-check returns cleanly, treating these as up-to-date. Adds e2e/testdata/sync.txtar covering healthy upstreams, the configured-but-gone regression case, and a behind-and-pulls case. --- e2e/testdata/sync.txtar | 51 +++++++++++++++++++++++++++++++++++++++++ internal/git/git.go | 21 ++++++++++------- 2 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 e2e/testdata/sync.txtar diff --git a/e2e/testdata/sync.txtar b/e2e/testdata/sync.txtar new file mode 100644 index 0000000..b1d52ba --- /dev/null +++ b/e2e/testdata/sync.txtar @@ -0,0 +1,51 @@ +[!exec:git] skip 'git not available' + +# Set up a project with a remote that has master + develop branches. +setup-repo develop +setup-project + +cd $WORK/project + +# Create worktrees for the existing local branches and configure upstream +# tracking explicitly. Using git directly (not `wt add`) keeps the test focused +# on sync behavior with guaranteed upstream setup. +exec git --git-dir=.bare worktree add --relative-paths worktrees/master master +exec git --git-dir=.bare worktree add --relative-paths worktrees/develop develop +exec git -C worktrees/master branch --set-upstream-to=origin/master +exec git -C worktrees/develop branch --set-upstream-to=origin/develop + +# Both upstreams resolve cleanly — sync reports up to date with no warnings. +exec wt sync +stderr 'master: up to date' +stderr 'develop: up to date' +! stderr 'could not check upstream' +stderr 'Sync complete: 0 updated, 0 skipped, 0 failed' + +# Regression test: configured-but-gone upstream. When branch..merge points +# at a non-existent remote ref, git's @{upstream} resolution fails with +# "fatal: no such branch: 'HEAD..'". wt sync should treat this as up-to-date +# and not surface a warning. +exec git --git-dir=.bare config branch.develop.merge refs/heads/does-not-exist +exec wt sync +stderr 'develop: up to date' +! stderr 'could not check upstream' +! stderr 'no such branch' + +# Restore develop's upstream, push a new commit to remote develop, and verify +# wt sync detects the lag and pulls. +exec git --git-dir=.bare config branch.develop.merge refs/heads/develop + +cd $WORK/remote +exec git checkout develop +cp $WORK/newfile.txt newfile.txt +exec git add newfile.txt +exec git commit -m 'add newfile' + +cd $WORK/project +exec wt sync +stderr 'develop: pulling 1 commit' +stderr 'Sync complete: 1 updated' +exists worktrees/develop/newfile.txt + +-- newfile.txt -- +hello diff --git a/internal/git/git.go b/internal/git/git.go index 3beae48..5b2651e 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -361,14 +361,23 @@ func (r *Runner) GetLastCommitAge(ctx context.Context, worktreePath string) (str } func (r *Runner) GetBehindCount(ctx context.Context, worktreePath string) (int, error) { - args := []string{"-C", worktreePath, "rev-list", "--count", "HEAD..@{upstream}"} - cmdStr := "git " + strings.Join(args, " ") + checkArgs := []string{"-C", worktreePath, "rev-parse", "--verify", "--quiet", "@{upstream}"} + checkStr := "git " + strings.Join(checkArgs, " ") if r.DryRun { - ui.DryRunNotice(cmdStr) + ui.DryRunNotice(checkStr) + ui.DryRunNotice("git -C " + worktreePath + " rev-list --count HEAD..@{upstream}") + return 0, nil + } + + ui.Command(checkStr) + if err := exec.CommandContext(ctx, "git", checkArgs...).Run(); err != nil { return 0, nil } + args := []string{"-C", worktreePath, "rev-list", "--count", "HEAD..@{upstream}"} + cmdStr := "git " + strings.Join(args, " ") + ui.Command(cmdStr) cmd := exec.CommandContext(ctx, "git", args...) var stdout, stderr bytes.Buffer @@ -376,11 +385,7 @@ func (r *Runner) GetBehindCount(ctx context.Context, worktreePath string) (int, cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - stderrStr := stderr.String() - if strings.Contains(stderrStr, "no upstream") || strings.Contains(stderrStr, "unknown revision") { - return 0, nil - } - return 0, fmt.Errorf("%s: %w\n%s", cmdStr, err, stderrStr) + return 0, fmt.Errorf("%s: %w\n%s", cmdStr, err, stderr.String()) } return parseBehindCount(stdout.String()), nil