fix(mr): fast-forward a checked-out branch on repeat checkout

Description

Running glab mr checkout <id> a second time, for a merge request whose branch is already checked out, made git refuse to fetch into the current branch (fatal: refusing to fetch into branch 'refs/heads/...' checked out at ...). The fallback fetch into FETCH_HEAD then treated the local branch as diverged, so the command either prompted to hard-reset the branch or, in non-interactive use, failed asking for --force — even though the branch was merely behind and nothing would be lost.

This MR adds a fast-forward path to resolveDivergence. After the fallback fetch, if the local branch is an ancestor of FETCH_HEAD (git merge-base --is-ancestor), it is fast-forwarded instead of reset:

  • On the checked-out branch this goes through git merge --ff-only FETCH_HEAD, which updates the working tree and still refuses if uncommitted changes are in the way.
  • When another branch is checked out, the ref is moved directly with git branch -f <branch> FETCH_HEAD.

Genuine divergence (local commits not on the MR) keeps the existing prompt / --force behaviour untouched.

Differences

Before: glab mr checkout 46 on an already checked-out MR branch printed fatal: refusing to fetch into branch ... exit status 128, or prompted "Local branch has diverged from remote. Reset to remote (discards local commits)?" for a branch that was only behind.

After: the branch is fast-forwarded to the MR head and checked out; the divergence prompt only appears when the local branch really has commits the MR does not.

Resolves #1214 (closed)

How has this been tested?

Two new cases in internal/commands/mr/checkout/mr_checkout_test.go, using the existing gomock git runner:

  • when the checked-out branch is behind the merge request, fast-forwards — the first fetch fails with git's "refusing to fetch into branch ... checked out" error, the fallback fetch succeeds, merge-base --is-ancestor reports the local branch is behind, and the test asserts git merge --ff-only FETCH_HEAD is run (not reset --hard) before the checkout.
  • when another branch is checked out and the local branch is behind, moves it — same setup with main checked out; asserts git branch -f feat-new-mr FETCH_HEAD is used instead.

The existing divergence tests (prompt, --force, non-TTY FlagError, dirty working tree guard) are unchanged and still pass:

go test ./internal/commands/mr/checkout/...

Manually: check out an MR with glab mr checkout <id>, push a commit to the MR branch from elsewhere, run glab mr checkout <id> again — the branch fast-forwards with no prompt. Add a local commit first and run it again — the divergence prompt appears as before.

Edited by Jeston Singh

Merge request reports

Loading
Loading