Skip to content

Commit 14364a2

Browse files
committed
waterfall pr lookup by url, number, branch name
1 parent 426ad11 commit 14364a2

5 files changed

Lines changed: 599 additions & 44 deletions

File tree

cmd/checkout.go

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"errors"
55
"fmt"
6-
"strconv"
76

87
"github.com/cli/go-gh/v2/pkg/prompter"
98
"github.com/github/gh-stack/internal/config"
@@ -82,11 +81,13 @@ func runCheckout(cfg *config.Config, opts *checkoutOptions) error {
8281
targetBranch = s.Branches[len(s.Branches)-1].Branch
8382
} else {
8483
// Resolve target against local stacks
85-
s, targetBranch, err = findStackByTarget(sf, opts.target)
84+
var br *stack.BranchRef
85+
s, br, err = resolvePR(sf, opts.target)
8686
if err != nil {
8787
cfg.Errorf("%s", err)
8888
return nil
8989
}
90+
targetBranch = br.Branch
9091
}
9192

9293
currentBranch, _ := git.CurrentBranch()
@@ -106,36 +107,6 @@ func runCheckout(cfg *config.Config, opts *checkoutOptions) error {
106107
return nil
107108
}
108109

109-
// findStackByTarget resolves a target string against locally tracked stacks.
110-
// It tries PR number first (integer), then branch name.
111-
func findStackByTarget(sf *stack.StackFile, target string) (*stack.Stack, string, error) {
112-
// Try parsing as a PR number
113-
if prNumber, err := strconv.Atoi(target); err == nil && prNumber > 0 {
114-
s, b := sf.FindStackByPRNumber(prNumber)
115-
if s != nil && b != nil {
116-
return s, b.Branch, nil
117-
}
118-
}
119-
120-
// Try matching as a branch name
121-
stacks := sf.FindAllStacksForBranch(target)
122-
if len(stacks) == 1 {
123-
return stacks[0], target, nil
124-
}
125-
if len(stacks) > 1 {
126-
// Target is in multiple stacks (e.g. a trunk branch) — return the first one.
127-
// A future improvement could prompt for disambiguation here.
128-
return stacks[0], target, nil
129-
}
130-
131-
return nil, "", fmt.Errorf(
132-
"no locally tracked stack found for %q\n"+
133-
"This command currently only works with stacks created locally.\n"+
134-
"Server-side stack discovery will be available in a future release.",
135-
target,
136-
)
137-
}
138-
139110
// interactiveStackPicker shows a menu of all locally tracked stacks and returns
140111
// the one the user selects. Returns nil, nil if the user has no stacks.
141112
func interactiveStackPicker(cfg *config.Config, sf *stack.StackFile) (*stack.Stack, error) {

cmd/merge.go

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,113 @@
11
package cmd
22

33
import (
4+
"fmt"
5+
6+
"github.com/cli/go-gh/v2/pkg/browser"
7+
"github.com/cli/go-gh/v2/pkg/prompter"
48
"github.com/github/gh-stack/internal/config"
9+
"github.com/github/gh-stack/internal/stack"
510
"github.com/spf13/cobra"
611
)
712

813
func MergeCmd(cfg *config.Config) *cobra.Command {
9-
opts := struct{}{}
10-
1114
cmd := &cobra.Command{
12-
Use: "merge <pr>",
15+
Use: "merge [<pr-or-branch>]",
1316
Short: "Merge a stack of PRs",
14-
Long: "Merges the specified PR and all PRs below it in the stack.",
15-
Args: cobra.ExactArgs(1),
17+
Long: `Merges the specified PR and all PRs below it in the stack.
18+
19+
Accepts a PR URL, PR number, or branch name. When run without
20+
arguments, operates on the current branch's PR.`,
21+
Args: cobra.MaximumNArgs(1),
1622
RunE: func(cmd *cobra.Command, args []string) error {
17-
return runMerge(cfg, opts)
23+
var target string
24+
if len(args) > 0 {
25+
target = args[0]
26+
}
27+
return runMerge(cfg, target)
1828
},
1929
}
2030

2131
return cmd
2232
}
2333

24-
// runMerge is a placeholder for the stack merge workflow.
25-
//
26-
// We need a mergeability check for the entire stack
27-
// and an endpoint for merging an entire stack
28-
func runMerge(cfg *config.Config, opts struct{}) error {
29-
cfg.Warningf("`%s` is not yet implemented", cfg.ColorCyan("gh stack merge"))
34+
func runMerge(cfg *config.Config, target string) error {
35+
// Standard stack loading and validation.
36+
result, err := loadStack(cfg, "")
37+
if err != nil {
38+
return nil
39+
}
40+
s := result.Stack
41+
currentBranch := result.CurrentBranch
42+
43+
// Sync PR state from GitHub so merge status is up to date.
44+
syncStackPRs(cfg, s)
45+
46+
// Persist the refreshed PR state.
47+
_ = stack.Save(result.GitDir, result.StackFile)
48+
49+
// Resolve which branch to operate on.
50+
var br *stack.BranchRef
51+
if target != "" {
52+
_, br, err = resolvePR(result.StackFile, target)
53+
if err != nil {
54+
cfg.Errorf("%s", err)
55+
return nil
56+
}
57+
} else {
58+
idx := s.IndexOf(currentBranch)
59+
if idx < 0 {
60+
if s.IsFullyMerged() {
61+
cfg.Successf("All PRs in this stack have already been merged")
62+
return nil
63+
}
64+
cfg.Errorf("current branch %q is not a stack branch (it may be the trunk)", currentBranch)
65+
return nil
66+
}
67+
br = &s.Branches[idx]
68+
}
69+
70+
if br.PullRequest == nil {
71+
cfg.Errorf("no pull request found for branch %q", currentBranch)
72+
cfg.Printf(" Run %s to create PRs for this stack.", cfg.ColorCyan("gh stack push"))
73+
return nil
74+
}
75+
76+
if br.IsMerged() {
77+
cfg.Successf("PR %s has already been merged", cfg.PRLink(br.PullRequest.Number, br.PullRequest.URL))
78+
cfg.Printf(" %s", br.PullRequest.URL)
79+
return nil
80+
}
81+
82+
prURL := br.PullRequest.URL
83+
prLink := cfg.PRLink(br.PullRequest.Number, prURL)
84+
85+
cfg.Warningf("Merging stacked PRs from the CLI is not yet supported")
86+
87+
if cfg.IsInteractive() {
88+
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
89+
openWeb, promptErr := p.Confirm(
90+
fmt.Sprintf("Open %s in your browser?", prLink), true)
91+
if promptErr != nil {
92+
if isInterruptError(promptErr) {
93+
printInterrupt(cfg)
94+
return nil
95+
}
96+
cfg.Errorf("prompt failed: %s", promptErr)
97+
return nil
98+
}
99+
100+
if openWeb {
101+
b := browser.New("", cfg.Out, cfg.Err)
102+
if err := b.Browse(prURL); err != nil {
103+
cfg.Warningf("failed to open browser: %s", err)
104+
} else {
105+
cfg.Successf("Opened %s in your browser", prLink)
106+
return nil
107+
}
108+
}
109+
}
110+
111+
cfg.Printf(" You can merge this PR at: %s", prURL)
30112
return nil
31113
}

0 commit comments

Comments
 (0)