Skip to content

Commit 88cbcc9

Browse files
committed
Merge remote-tracking branch 'origin/master' into pr-status-unicode
2 parents 1132703 + bc3f964 commit 88cbcc9

File tree

13 files changed

+177
-39
lines changed

13 files changed

+177
-39
lines changed

api/queries_pr.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ func PullRequestByNumber(client *Client, repo ghrepo.Interface, number int) (*Pu
340340
return &resp.Repository.PullRequest, nil
341341
}
342342

343-
func PullRequestForBranch(client *Client, repo ghrepo.Interface, branch string) (*PullRequest, error) {
343+
func PullRequestForBranch(client *Client, repo ghrepo.Interface, baseBranch, headBranch string) (*PullRequest, error) {
344344
type response struct {
345345
Repository struct {
346346
PullRequests struct {
@@ -376,9 +376,9 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, branch string)
376376
}
377377
}`
378378

379-
branchWithoutOwner := branch
380-
if idx := strings.Index(branch, ":"); idx >= 0 {
381-
branchWithoutOwner = branch[idx+1:]
379+
branchWithoutOwner := headBranch
380+
if idx := strings.Index(headBranch, ":"); idx >= 0 {
381+
branchWithoutOwner = headBranch[idx+1:]
382382
}
383383

384384
variables := map[string]interface{}{
@@ -394,12 +394,17 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, branch string)
394394
}
395395

396396
for _, pr := range resp.Repository.PullRequests.Nodes {
397-
if pr.HeadLabel() == branch {
397+
if pr.HeadLabel() == headBranch {
398+
if baseBranch != "" {
399+
if pr.BaseRefName != baseBranch {
400+
continue
401+
}
402+
}
398403
return &pr, nil
399404
}
400405
}
401406

402-
return nil, &NotFoundError{fmt.Errorf("no open pull requests found for branch %q", branch)}
407+
return nil, &NotFoundError{fmt.Errorf("no open pull requests found for branch %q", headBranch)}
403408
}
404409

405410
// CreatePullRequest creates a pull request in a GitHub repository

api/queries_repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func RepoNetwork(client *Client, repos []ghrepo.Interface) (RepoNetworkResult, e
154154
keys = append(keys, key)
155155
}
156156
// sort keys to ensure `repo_{N}` entries are processed in order
157-
sort.Sort(sort.StringSlice(keys))
157+
sort.Strings(keys)
158158

159159
// Iterate over keys of GraphQL response data and, based on its name,
160160
// dynamically allocate the target struct an individual message gets decoded to.

command/pr.go

Lines changed: 25 additions & 11 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
}
@@ -288,7 +300,7 @@ func prView(cmd *cobra.Command, args []string) error {
288300
}
289301
}
290302
} else {
291-
pr, err = api.PullRequestForBranch(apiClient, baseRepo, branchWithOwner)
303+
pr, err = api.PullRequestForBranch(apiClient, baseRepo, "", branchWithOwner)
292304
if err != nil {
293305
return err
294306
}
@@ -331,17 +343,19 @@ 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

344-
return api.PullRequestForBranch(apiClient, baseRepo, arg)
358+
return api.PullRequestForBranch(apiClient, baseRepo, "", arg)
345359
}
346360

347361
func prSelectorForCurrentBranch(ctx context.Context, baseRepo ghrepo.Interface) (prNumber int, prHeadRef string, err error) {

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": {

command/pr_create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ func prCreate(cmd *cobra.Command, _ []string) error {
132132
if headRepo != nil && !ghrepo.IsSame(baseRepo, headRepo) {
133133
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
134134
}
135-
existingPR, err := api.PullRequestForBranch(client, baseRepo, headBranchLabel)
135+
existingPR, err := api.PullRequestForBranch(client, baseRepo, baseBranch, headBranchLabel)
136136
var notFound *api.NotFoundError
137137
if err != nil && !errors.As(err, &notFound) {
138138
return fmt.Errorf("error checking for existing pull request: %w", err)
139139
}
140140
if err == nil {
141-
return fmt.Errorf("a pull request for branch %q already exists:\n%s", headBranchLabel, existingPR.URL)
141+
return fmt.Errorf("a pull request for branch %q into branch %q already exists:\n%s", headBranchLabel, baseBranch, existingPR.URL)
142142
}
143143
}
144144

command/pr_create_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ func TestPRCreate_alreadyExists(t *testing.T) {
6464
http.StubResponse(200, bytes.NewBufferString(`
6565
{ "data": { "repository": { "pullRequests": { "nodes": [
6666
{ "url": "https://github.com/OWNER/REPO/pull/123",
67-
"headRefName": "feature" }
67+
"headRefName": "feature",
68+
"baseRefName": "master" }
6869
] } } } }
6970
`))
7071

@@ -78,11 +79,37 @@ func TestPRCreate_alreadyExists(t *testing.T) {
7879
if err == nil {
7980
t.Fatal("error expected, got nil")
8081
}
81-
if err.Error() != "a pull request for branch \"feature\" already exists:\nhttps://github.com/OWNER/REPO/pull/123" {
82+
if err.Error() != "a pull request for branch \"feature\" into branch \"master\" already exists:\nhttps://github.com/OWNER/REPO/pull/123" {
8283
t.Errorf("got error %q", err)
8384
}
8485
}
8586

87+
func TestPRCreate_alreadyExistsDifferentBase(t *testing.T) {
88+
initBlankContext("OWNER/REPO", "feature")
89+
http := initFakeHTTP()
90+
http.StubRepoResponse("OWNER", "REPO")
91+
http.StubResponse(200, bytes.NewBufferString(`
92+
{ "data": { "repository": { "pullRequests": { "nodes": [
93+
{ "url": "https://github.com/OWNER/REPO/pull/123",
94+
"headRefName": "feature",
95+
"baseRefName": "master" }
96+
] } } } }
97+
`))
98+
http.StubResponse(200, bytes.NewBufferString("{}"))
99+
100+
cs, cmdTeardown := initCmdStubber()
101+
defer cmdTeardown()
102+
103+
cs.Stub("") // git status
104+
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
105+
cs.Stub("") // git rev-parse
106+
107+
_, err := RunCommand(prCreateCmd, `pr create -BanotherBase -t"cool" -b"nah"`)
108+
if err != nil {
109+
t.Errorf("got unexpected error %q", err)
110+
}
111+
}
112+
86113
func TestPRCreate_web(t *testing.T) {
87114
initBlankContext("OWNER/REPO", "feature")
88115
http := initFakeHTTP()

0 commit comments

Comments
 (0)