X Tutup
Skip to content

Commit 29de133

Browse files
committed
Accept PR URL or branch argument in pr checkout <pr>
1 parent 519e731 commit 29de133

File tree

3 files changed

+135
-24
lines changed

3 files changed

+135
-24
lines changed

api/queries_pr.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ func PullRequestByNumber(client *Client, ghRepo Repo, number int) (*PullRequest,
285285
query($owner: String!, $repo: String!, $pr_number: Int!) {
286286
repository(owner: $owner, name: $repo) {
287287
pullRequest(number: $pr_number) {
288+
number
288289
headRefName
289290
headRepositoryOwner {
290291
login

command/pr.go

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -226,24 +226,11 @@ func prView(cmd *cobra.Command, args []string) error {
226226

227227
var openURL string
228228
if len(args) > 0 {
229-
if prNumber, err := strconv.Atoi(args[0]); err == nil {
230-
pr, err := api.PullRequestByNumber(apiClient, baseRepo, prNumber)
231-
if err != nil {
232-
return err
233-
}
234-
openURL = pr.URL
235-
} else {
236-
prRE := regexp.MustCompile(`^https://github\.com/[^/]+/[^/]+/pull/\d+`)
237-
if m := prRE.FindStringSubmatch(args[0]); m != nil {
238-
openURL = m[0]
239-
} else {
240-
pr, err := api.PullRequestForBranch(apiClient, baseRepo, args[0])
241-
if err != nil {
242-
return err
243-
}
244-
openURL = pr.URL
245-
}
229+
pr, err := prFromArg(apiClient, baseRepo, args[0])
230+
if err != nil {
231+
return err
246232
}
233+
openURL = pr.URL
247234
} else {
248235
prNumber, branchWithOwner, err := prSelectorForCurrentBranch(ctx)
249236
if err != nil {
@@ -265,6 +252,20 @@ func prView(cmd *cobra.Command, args []string) error {
265252
return utils.OpenInBrowser(openURL)
266253
}
267254

255+
var prURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/pull/(\d+)`)
256+
257+
func prFromArg(apiClient *api.Client, baseRepo context.GitHubRepository, arg string) (*api.PullRequest, error) {
258+
if prNumber, err := strconv.Atoi(arg); err == nil {
259+
return api.PullRequestByNumber(apiClient, baseRepo, prNumber)
260+
}
261+
262+
if m := prURLRE.FindStringSubmatch(arg); m != nil {
263+
return &api.PullRequest{URL: m[0]}, nil
264+
}
265+
266+
return api.PullRequestForBranch(apiClient, baseRepo, arg)
267+
}
268+
268269
func prSelectorForCurrentBranch(ctx context.Context) (prNumber int, prHeadRef string, err error) {
269270
baseRepo, err := ctx.BaseRepo()
270271
if err != nil {
@@ -311,11 +312,6 @@ func prSelectorForCurrentBranch(ctx context.Context) (prNumber int, prHeadRef st
311312
}
312313

313314
func prCheckout(cmd *cobra.Command, args []string) error {
314-
prNumber, err := strconv.Atoi(args[0])
315-
if err != nil {
316-
return err
317-
}
318-
319315
ctx := contextForCommand(cmd)
320316
currentBranch, _ := ctx.Branch()
321317
remotes, err := ctx.Remotes()
@@ -332,10 +328,19 @@ func prCheckout(cmd *cobra.Command, args []string) error {
332328
return err
333329
}
334330

335-
pr, err := api.PullRequestByNumber(apiClient, baseRemote, prNumber)
331+
pr, err := prFromArg(apiClient, baseRemote, args[0])
336332
if err != nil {
337333
return err
338334
}
335+
if pr.Number == 0 {
336+
// hydrate the pr object by fetching extra information from the API
337+
m := prURLRE.FindStringSubmatch(pr.URL)
338+
prNumber, _ := strconv.Atoi(m[3])
339+
pr, err = api.PullRequestByNumber(apiClient, baseRemote, prNumber)
340+
if err != nil {
341+
return err
342+
}
343+
}
339344

340345
headRemote := baseRemote
341346
if pr.IsCrossRepository {
@@ -369,7 +374,7 @@ func prCheckout(cmd *cobra.Command, args []string) error {
369374
newBranchName = fmt.Sprintf("%s/%s", pr.HeadRepositoryOwner.Login, newBranchName)
370375
}
371376

372-
ref := fmt.Sprintf("refs/pull/%d/head", prNumber)
377+
ref := fmt.Sprintf("refs/pull/%d/head", pr.Number)
373378
if newBranchName == currentBranch {
374379
// PR head matches currently checked out branch
375380
cmdQueue = append(cmdQueue, []string{"git", "fetch", baseRemote.Name, ref})

command/pr_checkout_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func TestPRCheckout_sameRepo(t *testing.T) {
2323

2424
http.StubResponse(200, bytes.NewBufferString(`
2525
{ "data": { "repository": { "pullRequest": {
26+
"number": 123,
2627
"headRefName": "feature",
2728
"headRepositoryOwner": {
2829
"login": "hubot"
@@ -61,6 +62,104 @@ func TestPRCheckout_sameRepo(t *testing.T) {
6162
eq(t, strings.Join(ranCommands[3], " "), "git config branch.feature.merge refs/heads/feature")
6263
}
6364

65+
func TestPRCheckout_urlArg(t *testing.T) {
66+
ctx := context.NewBlank()
67+
ctx.SetBranch("master")
68+
ctx.SetRemotes(map[string]string{
69+
"origin": "OWNER/REPO",
70+
})
71+
initContext = func() context.Context {
72+
return ctx
73+
}
74+
http := initFakeHTTP()
75+
76+
http.StubResponse(200, bytes.NewBufferString(`
77+
{ "data": { "repository": { "pullRequest": {
78+
"number": 123,
79+
"headRefName": "feature",
80+
"headRepositoryOwner": {
81+
"login": "hubot"
82+
},
83+
"headRepository": {
84+
"name": "REPO",
85+
"defaultBranchRef": {
86+
"name": "master"
87+
}
88+
},
89+
"isCrossRepository": false,
90+
"maintainerCanModify": false
91+
} } } }
92+
`))
93+
94+
ranCommands := [][]string{}
95+
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
96+
switch strings.Join(cmd.Args, " ") {
97+
case "git show-ref --verify --quiet refs/heads/feature":
98+
return &errorStub{"exit status: 1"}
99+
default:
100+
ranCommands = append(ranCommands, cmd.Args)
101+
return &outputStub{}
102+
}
103+
})
104+
defer restoreCmd()
105+
106+
output, err := RunCommand(prCheckoutCmd, `pr checkout https://github.com/OWNER/REPO/pull/123/files`)
107+
eq(t, err, nil)
108+
eq(t, output, "")
109+
110+
eq(t, len(ranCommands), 4)
111+
eq(t, strings.Join(ranCommands[1], " "), "git checkout -b feature --no-track origin/feature")
112+
}
113+
114+
func TestPRCheckout_branchArg(t *testing.T) {
115+
ctx := context.NewBlank()
116+
ctx.SetBranch("master")
117+
ctx.SetRemotes(map[string]string{
118+
"origin": "OWNER/REPO",
119+
})
120+
initContext = func() context.Context {
121+
return ctx
122+
}
123+
http := initFakeHTTP()
124+
125+
http.StubResponse(200, bytes.NewBufferString(`
126+
{ "data": { "repository": { "pullRequests": { "nodes": [
127+
{ "number": 123,
128+
"headRefName": "feature",
129+
"headRepositoryOwner": {
130+
"login": "hubot"
131+
},
132+
"headRepository": {
133+
"name": "REPO",
134+
"defaultBranchRef": {
135+
"name": "master"
136+
}
137+
},
138+
"isCrossRepository": true,
139+
"maintainerCanModify": false }
140+
] } } } }
141+
`))
142+
143+
ranCommands := [][]string{}
144+
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
145+
switch strings.Join(cmd.Args, " ") {
146+
case "git show-ref --verify --quiet refs/heads/feature":
147+
return &errorStub{"exit status: 1"}
148+
default:
149+
ranCommands = append(ranCommands, cmd.Args)
150+
return &outputStub{}
151+
}
152+
})
153+
defer restoreCmd()
154+
155+
output, err := RunCommand(prCheckoutCmd, `pr checkout hubot:feature`)
156+
eq(t, err, nil)
157+
eq(t, output, "")
158+
159+
eq(t, len(ranCommands), 5)
160+
eq(t, strings.Join(ranCommands[1], " "), "git fetch origin refs/pull/123/head:feature")
161+
}
162+
64163
func TestPRCheckout_existingBranch(t *testing.T) {
65164
ctx := context.NewBlank()
66165
ctx.SetBranch("master")
@@ -74,6 +173,7 @@ func TestPRCheckout_existingBranch(t *testing.T) {
74173

75174
http.StubResponse(200, bytes.NewBufferString(`
76175
{ "data": { "repository": { "pullRequest": {
176+
"number": 123,
77177
"headRefName": "feature",
78178
"headRepositoryOwner": {
79179
"login": "hubot"
@@ -125,6 +225,7 @@ func TestPRCheckout_differentRepo_remoteExists(t *testing.T) {
125225

126226
http.StubResponse(200, bytes.NewBufferString(`
127227
{ "data": { "repository": { "pullRequest": {
228+
"number": 123,
128229
"headRefName": "feature",
129230
"headRepositoryOwner": {
130231
"login": "hubot"
@@ -176,6 +277,7 @@ func TestPRCheckout_differentRepo(t *testing.T) {
176277

177278
http.StubResponse(200, bytes.NewBufferString(`
178279
{ "data": { "repository": { "pullRequest": {
280+
"number": 123,
179281
"headRefName": "feature",
180282
"headRepositoryOwner": {
181283
"login": "hubot"
@@ -227,6 +329,7 @@ func TestPRCheckout_differentRepo_existingBranch(t *testing.T) {
227329

228330
http.StubResponse(200, bytes.NewBufferString(`
229331
{ "data": { "repository": { "pullRequest": {
332+
"number": 123,
230333
"headRefName": "feature",
231334
"headRepositoryOwner": {
232335
"login": "hubot"
@@ -276,6 +379,7 @@ func TestPRCheckout_differentRepo_currentBranch(t *testing.T) {
276379

277380
http.StubResponse(200, bytes.NewBufferString(`
278381
{ "data": { "repository": { "pullRequest": {
382+
"number": 123,
279383
"headRefName": "feature",
280384
"headRepositoryOwner": {
281385
"login": "hubot"
@@ -325,6 +429,7 @@ func TestPRCheckout_maintainerCanModify(t *testing.T) {
325429

326430
http.StubResponse(200, bytes.NewBufferString(`
327431
{ "data": { "repository": { "pullRequest": {
432+
"number": 123,
328433
"headRefName": "feature",
329434
"headRepositoryOwner": {
330435
"login": "hubot"

0 commit comments

Comments
 (0)
X Tutup