merge_pr.py silently repoints your current branch at main on a worktree checkout nf-zyf ← Beads

open priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/643 by tools/beads/import_github.py's reconcile pass.

merge_pr.py moves your current branch to main on any worktree-based checkout, silently. Work that is committed but not pushed is unreachable afterwards.

Filed separately from #566 because that issue is a broad "can't merge on Linux" thread and this is one specific, severe, actively-armed defect that should not be lost in it. Reported there first: <https://github.com/cfsh/ninefold/issues/566#issuecomment-5204988764>

The code

tools/pr/merge_pr.py:454-456, after a successful merge:

```python run(["git", "checkout", "main"], check=False) run(["git", "fetch", "origin"], check=False) run(["git", "reset", "--hard", "origin/main"], check=False) ```

git checkout main cannot succeed on a worktree box — git permits one worktree per branch, and main is checked out in the parent (~/nf/ninefold). It is check=False, so the failure is discarded. The reset --hard then runs anyway, against whatever ref is actually checked out: your feature branch.

Observed

Immediately after merge_pr.py 596 from /root/nf/devs/bork:

``` ## feat/reviewer-worktrees...origin/feat/reviewer-worktrees [ahead 4, behind 7] HEAD = d574e15 "reviewer: check the PR description is still true of the PR (#596)" ```

d574e15 was main's tip. The local branch had been repointed at main; four commits were no longer reachable by that name. Recovered with git reset --hard origin/feat/reviewer-worktrees only because they were pushed.

The same run's probe-gate line agrees: [probe-gate] sweeping probes on feat/reviewer-worktrees @ d574e15 — it gated main's tree while naming the feature branch.

Why this is worse than the usual Linux-merge friction
Fix

Gate the reset on the checkout actually succeeding, and say so when it does not:

```python switched = run(["git", "checkout", "main"], check=False) run(["git", "fetch", "origin"], check=False) if switched.ok: run(["git", "reset", "--hard", "origin/main"], check=False) else: print("merge_pr: NOT syncing local main — it is checked out in another " "worktree. Sync it there; this checkout is untouched.", file=sys.stderr) ```

Strictly better on every box: unchanged behaviour on a normal clone, and on a worktree box it stops rewriting a ref it was never asked to touch. Worth a test that a failed checkout main produces no reset --hard, since the current failure mode is exactly "an error was swallowed and the next command ran regardless".

⚠️ Workaround until then: git checkout --detach before running merge_pr.py, so there is no branch pointer to move. That is what I did for the following merge, deliberately.

Found while wrapping the #576 chunk. Unclaimed.

Dependencies

None.

Comments

No comments.

Add a comment