Skip to content

Commit 9786498

Browse files
committed
Respect base repository when passing URL argument to pr checkout/view
Previously, the repository owner+name component of the URL was ignored and only the pull request number was read. Now, the URL dictates which base repository will be used.
1 parent 2660561 commit 9786498

File tree

3 files changed

+122
-18
lines changed

3 files changed

+122
-18
lines changed

command/pr.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,21 @@ func prView(cmd *cobra.Command, args []string) error {
255255
return err
256256
}
257257

258-
baseRepo, err := determineBaseRepo(cmd, ctx)
259-
if err != nil {
260-
return err
258+
var baseRepo ghrepo.Interface
259+
var prArg string
260+
if len(args) > 0 {
261+
prArg = args[0]
262+
if prNum, repo := prFromURL(prArg); repo != nil {
263+
prArg = prNum
264+
baseRepo = repo
265+
}
266+
}
267+
268+
if baseRepo == nil {
269+
baseRepo, err = determineBaseRepo(cmd, ctx)
270+
if err != nil {
271+
return err
272+
}
261273
}
262274

263275
preview, err := cmd.Flags().GetBool("preview")
@@ -268,7 +280,7 @@ func prView(cmd *cobra.Command, args []string) error {
268280
var openURL string
269281
var pr *api.PullRequest
270282
if len(args) > 0 {
271-
pr, err = prFromArg(apiClient, baseRepo, args[0])
283+
pr, err = prFromArg(apiClient, baseRepo, prArg)
272284
if err != nil {
273285
return err
274286
}
@@ -331,13 +343,15 @@ func printPrPreview(out io.Writer, pr *api.PullRequest) error {
331343

332344
var prURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/pull/(\d+)`)
333345

334-
func prFromArg(apiClient *api.Client, baseRepo ghrepo.Interface, arg string) (*api.PullRequest, error) {
335-
if prNumber, err := strconv.Atoi(strings.TrimPrefix(arg, "#")); err == nil {
336-
return api.PullRequestByNumber(apiClient, baseRepo, prNumber)
346+
func prFromURL(arg string) (string, ghrepo.Interface) {
347+
if m := prURLRE.FindStringSubmatch(arg); m != nil {
348+
return m[3], ghrepo.New(m[1], m[2])
337349
}
350+
return "", nil
351+
}
338352

339-
if m := prURLRE.FindStringSubmatch(arg); m != nil {
340-
prNumber, _ := strconv.Atoi(m[3])
353+
func prFromArg(apiClient *api.Client, baseRepo ghrepo.Interface, arg string) (*api.PullRequest, error) {
354+
if prNumber, err := strconv.Atoi(strings.TrimPrefix(arg, "#")); err == nil {
341355
return api.PullRequestByNumber(apiClient, baseRepo, prNumber)
342356
}
343357

command/pr_checkout.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/exec"
88

99
"github.com/cli/cli/git"
10+
"github.com/cli/cli/internal/ghrepo"
1011
"github.com/cli/cli/utils"
1112
"github.com/spf13/cobra"
1213
)
@@ -18,21 +19,38 @@ func prCheckout(cmd *cobra.Command, args []string) error {
1819
if err != nil {
1920
return err
2021
}
21-
// FIXME: duplicates logic from fsContext.BaseRepo
22-
baseRemote, err := remotes.FindByName("upstream", "github", "origin", "*")
23-
if err != nil {
24-
return err
25-
}
22+
2623
apiClient, err := apiClientForContext(ctx)
2724
if err != nil {
2825
return err
2926
}
3027

31-
pr, err := prFromArg(apiClient, baseRemote, args[0])
28+
var baseRepo ghrepo.Interface
29+
prArg := args[0]
30+
if prNum, repo := prFromURL(prArg); repo != nil {
31+
prArg = prNum
32+
baseRepo = repo
33+
}
34+
35+
if baseRepo == nil {
36+
baseRepo, err = determineBaseRepo(cmd, ctx)
37+
if err != nil {
38+
return err
39+
}
40+
}
41+
42+
pr, err := prFromArg(apiClient, baseRepo, prArg)
3243
if err != nil {
3344
return err
3445
}
3546

47+
baseRemote, _ := remotes.FindByRepo(baseRepo.RepoOwner(), baseRepo.RepoName())
48+
// baseRemoteSpec is a repository URL or a remote name to be used in git fetch
49+
baseURLOrName := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(baseRepo))
50+
if baseRemote != nil {
51+
baseURLOrName = baseRemote.Name
52+
}
53+
3654
headRemote := baseRemote
3755
if pr.IsCrossRepository {
3856
headRemote, _ = remotes.FindByRepo(pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name)
@@ -67,15 +85,15 @@ func prCheckout(cmd *cobra.Command, args []string) error {
6785
ref := fmt.Sprintf("refs/pull/%d/head", pr.Number)
6886
if newBranchName == currentBranch {
6987
// PR head matches currently checked out branch
70-
cmdQueue = append(cmdQueue, []string{"git", "fetch", baseRemote.Name, ref})
88+
cmdQueue = append(cmdQueue, []string{"git", "fetch", baseURLOrName, ref})
7189
cmdQueue = append(cmdQueue, []string{"git", "merge", "--ff-only", "FETCH_HEAD"})
7290
} else {
7391
// create a new branch
74-
cmdQueue = append(cmdQueue, []string{"git", "fetch", baseRemote.Name, fmt.Sprintf("%s:%s", ref, newBranchName)})
92+
cmdQueue = append(cmdQueue, []string{"git", "fetch", baseURLOrName, fmt.Sprintf("%s:%s", ref, newBranchName)})
7593
cmdQueue = append(cmdQueue, []string{"git", "checkout", newBranchName})
7694
}
7795

78-
remote := baseRemote.Name
96+
remote := baseURLOrName
7997
mergeRef := ref
8098
if pr.MaintainerCanModify {
8199
remote = fmt.Sprintf("https://github.com/%s/%s.git", pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name)

command/pr_checkout_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package command
22

33
import (
44
"bytes"
5+
"encoding/json"
6+
"io/ioutil"
57
"os/exec"
68
"strings"
79
"testing"
@@ -21,6 +23,7 @@ func TestPRCheckout_sameRepo(t *testing.T) {
2123
return ctx
2224
}
2325
http := initFakeHTTP()
26+
http.StubRepoResponse("OWNER", "REPO")
2427

2528
http.StubResponse(200, bytes.NewBufferString(`
2629
{ "data": { "repository": { "pullRequest": {
@@ -112,6 +115,68 @@ func TestPRCheckout_urlArg(t *testing.T) {
112115
eq(t, strings.Join(ranCommands[1], " "), "git checkout -b feature --no-track origin/feature")
113116
}
114117

118+
func TestPRCheckout_urlArg_differentBase(t *testing.T) {
119+
ctx := context.NewBlank()
120+
ctx.SetBranch("master")
121+
ctx.SetRemotes(map[string]string{
122+
"origin": "OWNER/REPO",
123+
})
124+
initContext = func() context.Context {
125+
return ctx
126+
}
127+
http := initFakeHTTP()
128+
129+
http.StubResponse(200, bytes.NewBufferString(`
130+
{ "data": { "repository": { "pullRequest": {
131+
"number": 123,
132+
"headRefName": "feature",
133+
"headRepositoryOwner": {
134+
"login": "hubot"
135+
},
136+
"headRepository": {
137+
"name": "POE",
138+
"defaultBranchRef": {
139+
"name": "master"
140+
}
141+
},
142+
"isCrossRepository": false,
143+
"maintainerCanModify": false
144+
} } } }
145+
`))
146+
147+
ranCommands := [][]string{}
148+
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
149+
switch strings.Join(cmd.Args, " ") {
150+
case "git show-ref --verify --quiet refs/heads/feature":
151+
return &errorStub{"exit status: 1"}
152+
default:
153+
ranCommands = append(ranCommands, cmd.Args)
154+
return &test.OutputStub{}
155+
}
156+
})
157+
defer restoreCmd()
158+
159+
output, err := RunCommand(prCheckoutCmd, `pr checkout https://github.com/OTHER/POE/pull/123/files`)
160+
eq(t, err, nil)
161+
eq(t, output.String(), "")
162+
163+
bodyBytes, _ := ioutil.ReadAll(http.Requests[0].Body)
164+
reqBody := struct {
165+
Variables struct {
166+
Owner string
167+
Repo string
168+
}
169+
}{}
170+
json.Unmarshal(bodyBytes, &reqBody)
171+
172+
eq(t, reqBody.Variables.Owner, "OTHER")
173+
eq(t, reqBody.Variables.Repo, "POE")
174+
175+
eq(t, len(ranCommands), 5)
176+
eq(t, strings.Join(ranCommands[1], " "), "git fetch https://github.com/OTHER/POE.git refs/pull/123/head:feature")
177+
eq(t, strings.Join(ranCommands[3], " "), "git config branch.feature.remote https://github.com/OTHER/POE.git")
178+
}
179+
115180
func TestPRCheckout_branchArg(t *testing.T) {
116181
ctx := context.NewBlank()
117182
ctx.SetBranch("master")
@@ -122,6 +187,7 @@ func TestPRCheckout_branchArg(t *testing.T) {
122187
return ctx
123188
}
124189
http := initFakeHTTP()
190+
http.StubRepoResponse("OWNER", "REPO")
125191

126192
http.StubResponse(200, bytes.NewBufferString(`
127193
{ "data": { "repository": { "pullRequests": { "nodes": [
@@ -171,6 +237,7 @@ func TestPRCheckout_existingBranch(t *testing.T) {
171237
return ctx
172238
}
173239
http := initFakeHTTP()
240+
http.StubRepoResponse("OWNER", "REPO")
174241

175242
http.StubResponse(200, bytes.NewBufferString(`
176243
{ "data": { "repository": { "pullRequest": {
@@ -223,6 +290,7 @@ func TestPRCheckout_differentRepo_remoteExists(t *testing.T) {
223290
return ctx
224291
}
225292
http := initFakeHTTP()
293+
http.StubRepoResponse("OWNER", "REPO")
226294

227295
http.StubResponse(200, bytes.NewBufferString(`
228296
{ "data": { "repository": { "pullRequest": {
@@ -275,6 +343,7 @@ func TestPRCheckout_differentRepo(t *testing.T) {
275343
return ctx
276344
}
277345
http := initFakeHTTP()
346+
http.StubRepoResponse("OWNER", "REPO")
278347

279348
http.StubResponse(200, bytes.NewBufferString(`
280349
{ "data": { "repository": { "pullRequest": {
@@ -327,6 +396,7 @@ func TestPRCheckout_differentRepo_existingBranch(t *testing.T) {
327396
return ctx
328397
}
329398
http := initFakeHTTP()
399+
http.StubRepoResponse("OWNER", "REPO")
330400

331401
http.StubResponse(200, bytes.NewBufferString(`
332402
{ "data": { "repository": { "pullRequest": {
@@ -377,6 +447,7 @@ func TestPRCheckout_differentRepo_currentBranch(t *testing.T) {
377447
return ctx
378448
}
379449
http := initFakeHTTP()
450+
http.StubRepoResponse("OWNER", "REPO")
380451

381452
http.StubResponse(200, bytes.NewBufferString(`
382453
{ "data": { "repository": { "pullRequest": {
@@ -427,6 +498,7 @@ func TestPRCheckout_maintainerCanModify(t *testing.T) {
427498
return ctx
428499
}
429500
http := initFakeHTTP()
501+
http.StubRepoResponse("OWNER", "REPO")
430502

431503
http.StubResponse(200, bytes.NewBufferString(`
432504
{ "data": { "repository": { "pullRequest": {

0 commit comments

Comments
 (0)