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