fix(pr,time,release): closed PRs for checkout and edit, dismiss state check first, worktree-aware checkout errors, exact piped durations (follow gh)

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 20:38:01 +02:00
1 parent 6224190bd9
commit 339527b26c
11 files changed
+131 -23

No files matched your search

+27
View File
@@ -99,6 +99,11 @@ func CheckoutPull(ctx context.Context, runner Runner, options CheckoutOptions) (
}
}
}
if existing {
if err := requireBranchFreeElsewhere(run, options.Branch); err != nil {
return CheckoutResult{}, err
}
}
fetchRemote := options.Remote
fetchRef := fmt.Sprintf("refs/pull/%d/head", options.Number)
tracking := ""
@@ -168,3 +173,25 @@ func CheckoutPull(ctx context.Context, runner Runner, options CheckoutOptions) (
}
return CheckoutResult{Branch: options.Branch, Commit: commit, Detached: options.Detached, Remote: fetchRemote, Tracking: tracking}, nil
}
// requireBranchFreeElsewhere fails when branch is checked out in a linked worktree other than
// the current one, which git switch would refuse with a message that does not say where.
func requireBranchFreeElsewhere(run func(...string) (string, error), branch string) error {
top, err := run("rev-parse", "--show-toplevel")
if err != nil {
return err
}
listing, err := run("worktree", "list", "--porcelain")
if err != nil {
return err
}
path := ""
for line := range strings.SplitSeq(listing, "\n") {
if value, ok := strings.CutPrefix(line, "worktree "); ok {
path = value
} else if line == "branch refs/heads/"+branch && path != top {
return fmt.Errorf("branch %q is checked out in the worktree %s; pass --detach to check out the pull request without a branch, or choose another branch with --branch", branch, path)
}
}
return nil
}
+19
View File
@@ -17,6 +17,7 @@ type checkoutRunner struct {
dirty, collision bool
localCommit string
diverged bool
worktrees string
}
func (r *checkoutRunner) Output(_ context.Context, dir string, prompt bool, args ...string) (string, error) {
@@ -38,6 +39,8 @@ func (r *checkoutRunner) Output(_ context.Context, dir string, prompt bool, args
return "true", nil
}
return "abc123", nil
case "worktree":
return r.worktrees, nil
case "status":
if r.dirty {
return "?? file", nil
@@ -172,3 +175,19 @@ func TestPullCheckoutTracksHeadBranch(t *testing.T) {
require.NotContains(t, call, "refs/pull/")
}
}
func TestPullCheckoutRejectsBranchCheckedOutInAnotherWorktree(t *testing.T) {
options := CheckoutOptions{Directory: "/work", Remote: "origin", Number: 7, Branch: "pr/7", ExpectedCommit: "abc123", Update: true}
runner := &checkoutRunner{collision: true, localCommit: "abc123", worktrees: "worktree abc123\nHEAD 1\nbranch refs/heads/main\n\nworktree /other/tree\nHEAD 2\nbranch refs/heads/pr/7\n"}
_, err := CheckoutPull(context.Background(), runner, options)
require.ErrorContains(t, err, `branch "pr/7" is checked out in the worktree /other/tree`)
require.ErrorContains(t, err, "--detach")
for _, call := range runner.calls {
require.NotContains(t, call, "fetch")
require.NotContains(t, call, "switch")
}
// The current worktree holding the branch is fine.
runner = &checkoutRunner{collision: true, localCommit: "abc123", worktrees: "worktree abc123\nHEAD 1\nbranch refs/heads/pr/7\n"}
_, err = CheckoutPull(context.Background(), runner, options)
require.NoError(t, err)
}