Skip to content

feat: add worktree management commands, per-worktree trunks, and worktree-aware filtering#1221

Open
ed-irl wants to merge 2 commits into
ed-irl/restack-options-paramfrom
ed-irl/sync-self-merge-detection
Open

feat: add worktree management commands, per-worktree trunks, and worktree-aware filtering#1221
ed-irl wants to merge 2 commits into
ed-irl/restack-options-paramfrom
ed-irl/sync-self-merge-detection

Conversation

@ed-irl

@ed-irl ed-irl commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Adds gs worktree create and gs worktree list, plus the per-worktree
trunk machinery and worktree-scoped filtering they rely on.

gs worktree create <path> sets up a new Git worktree with its own local
trunk branch (named after the worktree dir, or via --trunk) that tracks
the same remote ref as the canonical trunk. Stacks created there are
based on this branch, so repo sync and restacks in different worktrees
don't contend on a shared trunk checkout. --no-trunk preserves the legacy
detached-HEAD behavior; -b/--branch also creates a tracked branch stacked
on the worktree trunk. gs worktree list shows each worktree with its
checked-out branch and stack.

State gains a per-worktree trunk registry (state/worktree.go): an
additive, optional store key mapping trunk-equivalent branches to their
worktree roots. Store exposes IsTrunk, TrunkFor, and WorktreeTrunks;
branch transactions and the branch graph now treat any registered
worktree trunk as a graph root, so traversals stop there and these
branches can serve as a base but cannot themselves be tracked.

The sync handler distinguishes the canonical remote trunk from the local
trunk it pulls into for the current worktree, fast-forwarding the local
trunk from the remote canonical ref.

Adds -w/--worktree to log short and repo restack to scope operations
to stacks with a branch checked out in the current worktree, backed by
BranchGraph.StacksInWorktree. repo restack also gains SkipCheckout so the
caller controls checkout.

Includes unit tests, test scripts, regenerated docs/help, and a changelog
entry.

@ed-irl ed-irl changed the base branch from main to ed-irl/fix-windows-rebase-flake June 6, 2026 11:27
@ed-irl

ed-irl commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

@ed-irl ed-irl force-pushed the ed-irl/sync-self-merge-detection branch from 464d594 to 4dfd1a1 Compare June 6, 2026 11:27
abhinav pushed a commit that referenced this pull request Jun 7, 2026
…ws (#1227)

## Summary

- `TestRebase_interactiveRetryPreservesTerminal` flakes on Windows
because the test relies on a goroutine sleeping 20ms before removing a
fake `.git/index.lock`. On Windows runners the scheduler doesn't always
release the goroutine before the retry path runs — when it doesn't, the
test sees the lock still present and fails.
- Replace the goroutine + sleep with atomic lock removal in the first
mock call. The lock is removed *before* the mock returns the
lock-conflict error, so when the retry path executes there is no timing
dependency to lose.

## Before

```go
go func() {
    time.Sleep(20 * time.Millisecond)
    _ = os.Remove(lockPath)
}()

mockExec.EXPECT().
    Run(gomock.Any()).
    DoAndReturn(func(cmd *exec.Cmd) error {
        _, _ = fmt.Fprintln(cmd.Stderr, "fatal: Unable to create '.git/index.lock'")
        return &exec.ExitError{}
    })
```

## After

```go
mockExec.EXPECT().
    Run(gomock.Any()).
    DoAndReturn(func(cmd *exec.Cmd) error {
        // Remove the lock before returning the lock-conflict
        // error so the retry path observes a clean state.
        _ = os.Remove(lockPath)
        _, _ = fmt.Fprintln(cmd.Stderr, "fatal: Unable to create '.git/index.lock'")
        return &exec.ExitError{}
    })
```

No sleep, no goroutine, no race.

## Why this matters

The flake masked CI on multiple unrelated PRs in the current stack (e.g.
#1221), wasting workflow runs and obscuring real failures. Fix is
independent of any feature work.

## Test plan

- [x] `go test -run TestRebase_interactiveRetryPreservesTerminal
-count=10 ./internal/git/` — 10/10 passing locally
- [x] Verify on Windows CI runner once this lands
Base automatically changed from ed-irl/fix-windows-rebase-flake to main June 7, 2026 20:15
@ed-irl ed-irl force-pushed the ed-irl/sync-self-merge-detection branch from 4dfd1a1 to 77feb3f Compare June 8, 2026 10:18
@ed-irl ed-irl marked this pull request as ready for review June 12, 2026 01:41
@ed-irl ed-irl requested a review from abhinav June 12, 2026 01:41
@ed-irl ed-irl force-pushed the ed-irl/sync-self-merge-detection branch 3 times, most recently from 897f5d4 to d5d3334 Compare June 15, 2026 09:52
@ed-irl ed-irl changed the title sync: detect self-merged stacks pushed directly to trunk feat: add worktree management commands, per-worktree trunks, and worktree-aware filtering Jun 19, 2026
@ed-irl ed-irl force-pushed the ed-irl/sync-self-merge-detection branch from d5d3334 to b54686b Compare June 19, 2026 01:02
ed-irl added 2 commits June 23, 2026 11:38
Introduces restack.Options{AutoResolve} and threads an opts *Options
parameter through the high-level restack entry points (RestackBranch,
RestackStack, RestackDownstack) and the merge/sync RestackHandler
interfaces, with all current callers passing nil.

This is foundational plumbing for the restack auto-resolve feature.
Landing it on a branch below the consuming stacks lets every branch
inherit the 3-arg signature, rather than the signature change living
on a branch parallel to its callers.
When a user merges a stack by pushing the stack tip directly to trunk
on the remote (e.g., 'git push origin --force-with-lease HEAD:main'),
the forge often does not report the affected change requests as merged.
On the supported-forge path, repo sync therefore failed to clean up
the stack, leaving each tracked branch with a stale BaseHash that
later restacks would replay against trunk, causing conflicts.

Run the same local-ancestor merge check the unsupported-forge path
already uses as a fallback after the forge API queries: any tracked
branch whose head is reachable from the new trunk hash is treated
as merged, regardless of forge state. Branches with submitted change
requests still attach their ChangeID for downstack history
propagation; unsubmitted branches log "merged into trunk locally".
@ed-irl ed-irl changed the base branch from main to ed-irl/restack-options-param June 24, 2026 01:31
@ed-irl ed-irl force-pushed the ed-irl/sync-self-merge-detection branch from b54686b to 221cfdc Compare June 24, 2026 01:31
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.

1 participant