Skip to content

Curate the error when the sync target is checked out in another worktree - #14076

Merged
sergiou87 merged 2 commits into
12927-fix-repo-sync-worktreefrom
wm/repo-sync-worktree-error-message
Aug 5, 2026
Merged

Curate the error when the sync target is checked out in another worktree#14076
sergiou87 merged 2 commits into
12927-fix-repo-sync-worktreefrom
wm/repo-sync-worktree-error-message

Conversation

@williammartin

@williammartin williammartin commented Aug 5, 2026

Copy link
Copy Markdown
Member

Follow-up to #14060, which fixes #12927. Targets 12927-fix-repo-sync-worktree so @sergiou87 can take it or leave it.

Description

A git repository can have more than one working directory attached to it, via git worktree. Each one has its own checked-out branch, its own index, and its own files on disk, but they all share one set of branch refs.

gh repo sync updates your local branch to match the remote. When the branch you're syncing isn't the one you currently have checked out, it can't merge into your working directory, so it moves the branch ref directly instead. #12927 reported that this silently corrupts things: if some other worktree has that branch checked out, moving the ref leaves that worktree's index and files pointing at the old commit, so everything shows up as staged changes.

#14060 fixes that by switching from git update-ref to git branch --force. Git itself refuses to move a branch another worktree is using, so silent corruption becomes a hard failure. That's the right call. The gap is what the user sees:

failed to run git: fatal: cannot force update the branch 'main' used by worktree at '/path/to/worktree'

That's a raw git error with a doubled failed to run git: fatal: prefix. It's also inconsistent with what gh repo sync does six lines away in the same function, where a dirty working directory produces a curated message and a tip:

refusing to sync due to uncommitted/untracked local changes
tip: use `git stash --all` before retrying the sync and run `git stash pop` afterwards

This PR detects the worktree case before attempting the update and gives it the same treatment:

can't sync "main" because it's checked out in another worktree at /path/to/worktree
tip: run `gh repo sync` from that worktree instead

Detection runs git worktree list --porcelain and looks for a worktree holding refs/heads/<branch>. git branch --force stays in place as the safety net.

How did you test this change?

$ GH_ACCEPTANCE_SCRIPT=repo-sync-worktree.txtar GH_ACCEPTANCE_HOST=github.com \
  GH_ACCEPTANCE_ORG=gh-acceptance-testing GH_ACCEPTANCE_TOKEN=$(gh auth token) \
  go test -tags=acceptance -run ^TestRepo$ ./acceptance -v
# Syncing from the linked worktree must refuse rather than silently move the default branch ref (1.287s)
> cd ../topic-worktree
> ! exec gh repo sync
[stderr]
can't sync "main" because it's checked out in another worktree at $WORK/repo_sync_worktree-SBHtlzxkHD
tip: run `gh repo sync` from that worktree instead
[exit status 1]
> stderr 'can''t sync "main" because it''s checked out in another worktree at .*'$SCRIPT_NAME'-'$RANDOM_STRING'$'
> stderr 'tip: run `gh repo sync` from that worktree instead'
# The primary worktree's branch and working tree are left untouched (0.025s)
> exec git status --porcelain
> ! stdout .
> ! exists asset.txt
# Syncing from the primary worktree, where the default branch is actually checked out, still works (1.308s)
> exec gh repo sync
> exists asset.txt
PASS

--- PASS: TestRepo/repo-sync-worktree (8.73s)

I also ran that acceptance test against trunk to confirm it's a real regression test and not just a test that agrees with the new code. It fails there, because update-ref silently succeeds:

> ! exec gh repo sync
FAIL: testdata/repo/repo-sync-worktree.txtar:28: unexpected command success

Key points

We use the porcelain output to avoid matching on the git error message which I believe version and locale dependent. This does introduce a small potential race, but it seems like a reasonable first step.

Notes for reviewers

None

Authorship and follow-up

Who wrote this:

  • A human wrote it.
  • An agent wrote it under close human direction.
  • An agent wrote it independently, and no human has guided the implementation beyond the initial prompt.

Who answers review comments:

  • @williammartin will read and reply directly.
  • An agent will draft replies and @username will read them before they are posted.
  • Nobody has explicitly committed to replying.

williammartin and others added 2 commits August 5, 2026 11:52
`git branch --force` already refuses to move a branch that another worktree has
checked out, but it surfaces a raw git fatal. Detect the case up front with
`git worktree list --porcelain` and return a curated error naming the worktree,
matching the guidance we already give for uncommitted changes.

Detection parses porcelain output rather than matching git's message, which
varies by version ("used by worktree at" on 2.53, "checked out at" on older).
`branch --force` stays as the safety net for states the check can't see, such
as an in-progress bisect.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f88d532-4a31-47c7-ab50-a3d0b9a77fc4
Reproduces the issue end to end: `main` checked out in the primary worktree
while `gh repo sync` runs from a linked worktree. Asserts the sync refuses,
the primary worktree's ref and working tree are untouched, and that syncing
from the worktree that owns the branch still fast forwards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4f88d532-4a31-47c7-ab50-a3d0b9a77fc4
Copilot AI lite review requested due to automatic review settings August 5, 2026 09:52
@williammartin
williammartin changed the base branch from trunk to 12927-fix-repo-sync-worktree August 5, 2026 09:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the gh repo sync user experience when the target branch is checked out in a different Git worktree by detecting that state up front and returning a curated, actionable error message (instead of surfacing Git’s raw fatal text). This aligns the worktree-occupancy failure path with the command’s existing “refuse + tip” style used for dirty working trees.

Changes:

  • Add a pre-check using git worktree list --porcelain to detect when the target branch is checked out in another worktree and refuse with a curated message.
  • Introduce a porcelain parser helper (worktreePathForBranch) and wire it through the sync command’s git client.
  • Expand coverage with a new unit test for the parser, updated real-git tests, a mocked sync-run case, and a new acceptance test reproducing the end-to-end scenario.
Show a summary per file
File Description
pkg/cmd/repo/sync/sync.go Refuse local sync with a curated error when the target branch is checked out in another worktree.
pkg/cmd/repo/sync/sync_test.go Update integration/unit expectations to assert the curated error and correct worktree identification.
pkg/cmd/repo/sync/mocks.go Extend the git mock to support the new BranchWorktreePath method.
pkg/cmd/repo/sync/git.go Add BranchWorktreePath and a porcelain parser to detect worktree occupancy.
pkg/cmd/repo/sync/git_test.go New table test validating worktreePathForBranch parsing behavior across edge cases.
acceptance/testdata/repo/repo-sync-worktree.txtar New acceptance scenario reproducing the worktree sync refusal and ensuring no unintended mutation occurs.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

@sergiou87 sergiou87 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it! Thank you 🙇‍♂️

@sergiou87
sergiou87 marked this pull request as ready for review August 5, 2026 11:54
@sergiou87
sergiou87 requested a review from a team as a code owner August 5, 2026 11:54
@sergiou87
sergiou87 requested review from babakks and removed request for a team August 5, 2026 11:54
@sergiou87
sergiou87 merged commit 39f9ec5 into 12927-fix-repo-sync-worktree Aug 5, 2026
49 checks passed
@sergiou87
sergiou87 deleted the wm/repo-sync-worktree-error-message branch August 5, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants