X Tutup
Skip to content

Commit 44acdd4

Browse files
committed
Merge branch 'master' into pr-status-no-commits
2 parents eb403a3 + 1fb0eef commit 44acdd4

File tree

17 files changed

+579
-127
lines changed

17 files changed

+579
-127
lines changed

api/pull_request_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ func TestPullRequest_ChecksStatus(t *testing.T) {
2222
{ "status": "COMPLETED",
2323
"conclusion": "FAILURE" },
2424
{ "status": "COMPLETED",
25-
"conclusion": "ACTION_REQUIRED" }
25+
"conclusion": "ACTION_REQUIRED" },
26+
{ "status": "COMPLETED",
27+
"conclusion": "STALE" }
2628
]
2729
}
2830
}
@@ -32,8 +34,8 @@ func TestPullRequest_ChecksStatus(t *testing.T) {
3234
eq(t, err, nil)
3335

3436
checks := pr.ChecksStatus()
35-
eq(t, checks.Total, 7)
36-
eq(t, checks.Pending, 2)
37+
eq(t, checks.Total, 8)
38+
eq(t, checks.Pending, 3)
3739
eq(t, checks.Failing, 3)
3840
eq(t, checks.Passing, 2)
3941
}

api/queries_pr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (pr *PullRequest) ChecksStatus() (summary PullRequestChecksStatus) {
120120
summary.Passing++
121121
case "ERROR", "FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED":
122122
summary.Failing++
123-
case "EXPECTED", "REQUESTED", "QUEUED", "PENDING", "IN_PROGRESS":
123+
case "EXPECTED", "REQUESTED", "QUEUED", "PENDING", "IN_PROGRESS", "STALE":
124124
summary.Pending++
125125
default:
126126
panic(fmt.Errorf("unsupported status: %q", state))

api/queries_repo.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/base64"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"sort"
910
"strings"
@@ -224,6 +225,49 @@ func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
224225
}, nil
225226
}
226227

228+
// RepoFindFork finds a fork of repo affiliated with the viewer
229+
func RepoFindFork(client *Client, repo ghrepo.Interface) (*Repository, error) {
230+
result := struct {
231+
Repository struct {
232+
Forks struct {
233+
Nodes []Repository
234+
}
235+
}
236+
}{}
237+
238+
variables := map[string]interface{}{
239+
"owner": repo.RepoOwner(),
240+
"repo": repo.RepoName(),
241+
}
242+
243+
if err := client.GraphQL(`
244+
query($owner: String!, $repo: String!) {
245+
repository(owner: $owner, name: $repo) {
246+
forks(first: 1, affiliations: [OWNER, COLLABORATOR]) {
247+
nodes {
248+
id
249+
name
250+
owner { login }
251+
url
252+
viewerPermission
253+
}
254+
}
255+
}
256+
}
257+
`, variables, &result); err != nil {
258+
return nil, err
259+
}
260+
261+
forks := result.Repository.Forks.Nodes
262+
// we check ViewerCanPush, even though we expect it to always be true per
263+
// `affiliations` condition, to guard against versions of GitHub with a
264+
// faulty `affiliations` implementation
265+
if len(forks) > 0 && forks[0].ViewerCanPush() {
266+
return &forks[0], nil
267+
}
268+
return nil, &NotFoundError{errors.New("no fork found")}
269+
}
270+
227271
// RepoCreateInput represents input parameters for RepoCreate
228272
type RepoCreateInput struct {
229273
Name string `json:"name"`

command/pr.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -418,32 +418,39 @@ func printPrs(w io.Writer, totalCount int, prs ...api.PullRequest) {
418418
reviews := pr.ReviewStatus()
419419

420420
if pr.State == "OPEN" {
421-
if checks.Total > 0 || reviews.ChangesRequested || reviews.Approved {
421+
reviewStatus := reviews.ChangesRequested || reviews.Approved || reviews.ReviewRequired
422+
if checks.Total > 0 || reviewStatus {
423+
// show checks & reviews on their own line
422424
fmt.Fprintf(w, "\n ")
423425
}
424426

425427
if checks.Total > 0 {
426428
var summary string
427429
if checks.Failing > 0 {
428430
if checks.Failing == checks.Total {
429-
summary = utils.Red("All checks failing")
431+
summary = utils.Red("× All checks failing")
430432
} else {
431-
summary = utils.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total))
433+
summary = utils.Red(fmt.Sprintf("× %d/%d checks failing", checks.Failing, checks.Total))
432434
}
433435
} else if checks.Pending > 0 {
434-
summary = utils.Yellow("Checks pending")
436+
summary = utils.Yellow("- Checks pending")
435437
} else if checks.Passing == checks.Total {
436-
summary = utils.Green("Checks passing")
438+
summary = utils.Green("Checks passing")
437439
}
438-
fmt.Fprintf(w, " - %s", summary)
440+
fmt.Fprint(w, summary)
441+
}
442+
443+
if checks.Total > 0 && reviewStatus {
444+
// add padding between checks & reviews
445+
fmt.Fprint(w, " ")
439446
}
440447

441448
if reviews.ChangesRequested {
442-
fmt.Fprintf(w, " - %s", utils.Red("Changes requested"))
449+
fmt.Fprint(w, utils.Red("+ Changes requested"))
443450
} else if reviews.ReviewRequired {
444-
fmt.Fprintf(w, " - %s", utils.Yellow("Review required"))
451+
fmt.Fprint(w, utils.Yellow("- Review required"))
445452
} else if reviews.Approved {
446-
fmt.Fprintf(w, " - %s", utils.Green("Approved"))
453+
fmt.Fprint(w, utils.Green("Approved"))
447454
}
448455
} else {
449456
s := strings.Title(strings.ToLower(pr.State))

command/pr_checkout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func prCheckout(cmd *cobra.Command, args []string) error {
6767
cmdQueue = append(cmdQueue, []string{"git", "fetch", headRemote.Name, refSpec})
6868

6969
// local branch already exists
70-
if git.VerifyRef("refs/heads/" + newBranchName) {
70+
if _, err := git.ShowRefs("refs/heads/" + newBranchName); err == nil {
7171
cmdQueue = append(cmdQueue, []string{"git", "checkout", newBranchName})
7272
cmdQueue = append(cmdQueue, []string{"git", "merge", "--ff-only", fmt.Sprintf("refs/remotes/%s", remoteBranch)})
7373
} else {

command/pr_checkout_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestPRCheckout_sameRepo(t *testing.T) {
4646
ranCommands := [][]string{}
4747
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
4848
switch strings.Join(cmd.Args, " ") {
49-
case "git show-ref --verify --quiet refs/heads/feature":
49+
case "git show-ref --verify -- refs/heads/feature":
5050
return &errorStub{"exit status: 1"}
5151
default:
5252
ranCommands = append(ranCommands, cmd.Args)
@@ -98,7 +98,7 @@ func TestPRCheckout_urlArg(t *testing.T) {
9898
ranCommands := [][]string{}
9999
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
100100
switch strings.Join(cmd.Args, " ") {
101-
case "git show-ref --verify --quiet refs/heads/feature":
101+
case "git show-ref --verify -- refs/heads/feature":
102102
return &errorStub{"exit status: 1"}
103103
default:
104104
ranCommands = append(ranCommands, cmd.Args)
@@ -147,7 +147,7 @@ func TestPRCheckout_urlArg_differentBase(t *testing.T) {
147147
ranCommands := [][]string{}
148148
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
149149
switch strings.Join(cmd.Args, " ") {
150-
case "git show-ref --verify --quiet refs/heads/feature":
150+
case "git show-ref --verify -- refs/heads/feature":
151151
return &errorStub{"exit status: 1"}
152152
default:
153153
ranCommands = append(ranCommands, cmd.Args)
@@ -210,7 +210,7 @@ func TestPRCheckout_branchArg(t *testing.T) {
210210
ranCommands := [][]string{}
211211
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
212212
switch strings.Join(cmd.Args, " ") {
213-
case "git show-ref --verify --quiet refs/heads/feature":
213+
case "git show-ref --verify -- refs/heads/feature":
214214
return &errorStub{"exit status: 1"}
215215
default:
216216
ranCommands = append(ranCommands, cmd.Args)
@@ -260,7 +260,7 @@ func TestPRCheckout_existingBranch(t *testing.T) {
260260
ranCommands := [][]string{}
261261
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
262262
switch strings.Join(cmd.Args, " ") {
263-
case "git show-ref --verify --quiet refs/heads/feature":
263+
case "git show-ref --verify -- refs/heads/feature":
264264
return &test.OutputStub{}
265265
default:
266266
ranCommands = append(ranCommands, cmd.Args)
@@ -313,7 +313,7 @@ func TestPRCheckout_differentRepo_remoteExists(t *testing.T) {
313313
ranCommands := [][]string{}
314314
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
315315
switch strings.Join(cmd.Args, " ") {
316-
case "git show-ref --verify --quiet refs/heads/feature":
316+
case "git show-ref --verify -- refs/heads/feature":
317317
return &errorStub{"exit status: 1"}
318318
default:
319319
ranCommands = append(ranCommands, cmd.Args)

command/pr_create.go

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"net/url"
7+
"strings"
78
"time"
89

910
"github.com/cli/cli/api"
@@ -75,7 +76,27 @@ func prCreate(cmd *cobra.Command, _ []string) error {
7576
if err != nil {
7677
return fmt.Errorf("could not determine the current branch: %w", err)
7778
}
78-
headRepo, headRepoErr := repoContext.HeadRepo()
79+
80+
var headRepo ghrepo.Interface
81+
var headRemote *context.Remote
82+
83+
// determine whether the head branch is already pushed to a remote
84+
headBranchPushedTo := determineTrackingBranch(remotes, headBranch)
85+
if headBranchPushedTo != nil {
86+
for _, r := range remotes {
87+
if r.Name != headBranchPushedTo.RemoteName {
88+
continue
89+
}
90+
headRepo = r
91+
headRemote = r
92+
break
93+
}
94+
}
95+
96+
// otherwise, determine the head repository with info obtained from the API
97+
if headRepo == nil {
98+
headRepo, _ = repoContext.HeadRepo()
99+
}
79100

80101
baseBranch, err := cmd.Flags().GetString("base")
81102
if err != nil {
@@ -193,8 +214,9 @@ func prCreate(cmd *cobra.Command, _ []string) error {
193214
}
194215

195216
didForkRepo := false
196-
var headRemote *context.Remote
197-
if headRepoErr != nil {
217+
// if a head repository could not be determined so far, automatically create
218+
// one by forking the base repository
219+
if headRepo == nil {
198220
if baseRepo.IsPrivate {
199221
return fmt.Errorf("cannot fork private repository '%s'", ghrepo.FullName(baseRepo))
200222
}
@@ -203,11 +225,25 @@ func prCreate(cmd *cobra.Command, _ []string) error {
203225
return fmt.Errorf("error forking repo: %w", err)
204226
}
205227
didForkRepo = true
228+
}
229+
230+
headBranchLabel := headBranch
231+
if !ghrepo.IsSame(baseRepo, headRepo) {
232+
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
233+
}
234+
235+
// There are two cases when an existing remote for the head repo will be
236+
// missing:
237+
// 1. the head repo was just created by auto-forking;
238+
// 2. an existing fork was discovered by quering the API.
239+
//
240+
// In either case, we want to add the head repo as a new git remote so we
241+
// can push to it.
242+
if err != nil {
206243
// TODO: support non-HTTPS git remote URLs
207-
baseRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(baseRepo))
208244
headRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(headRepo))
209-
// TODO: figure out what to name the new git remote
210-
gitRemote, err := git.AddRemote("fork", baseRepoURL, headRepoURL)
245+
// TODO: prevent clashes with another remote of a same name
246+
gitRemote, err := git.AddRemote("fork", headRepoURL)
211247
if err != nil {
212248
return fmt.Errorf("error adding remote: %w", err)
213249
}
@@ -218,34 +254,31 @@ func prCreate(cmd *cobra.Command, _ []string) error {
218254
}
219255
}
220256

221-
headBranchLabel := headBranch
222-
if !ghrepo.IsSame(baseRepo, headRepo) {
223-
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
224-
}
225-
226-
if headRemote == nil {
227-
headRemote, err = repoContext.RemoteForRepo(headRepo)
228-
if err != nil {
229-
return fmt.Errorf("git remote not found for head repository: %w", err)
257+
// automatically push the branch if it hasn't been pushed anywhere yet
258+
if headBranchPushedTo == nil {
259+
if headRemote == nil {
260+
headRemote, err = repoContext.RemoteForRepo(headRepo)
261+
if err != nil {
262+
return fmt.Errorf("git remote not found for head repository: %w", err)
263+
}
230264
}
231-
}
232265

233-
pushTries := 0
234-
maxPushTries := 3
235-
for {
236-
// TODO: respect existing upstream configuration of the current branch
237-
if err := git.Push(headRemote.Name, fmt.Sprintf("HEAD:%s", headBranch)); err != nil {
238-
if didForkRepo && pushTries < maxPushTries {
239-
pushTries++
240-
// first wait 2 seconds after forking, then 4s, then 6s
241-
waitSeconds := 2 * pushTries
242-
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
243-
time.Sleep(time.Duration(waitSeconds) * time.Second)
244-
continue
266+
pushTries := 0
267+
maxPushTries := 3
268+
for {
269+
if err := git.Push(headRemote.Name, fmt.Sprintf("HEAD:%s", headBranch)); err != nil {
270+
if didForkRepo && pushTries < maxPushTries {
271+
pushTries++
272+
// first wait 2 seconds after forking, then 4s, then 6s
273+
waitSeconds := 2 * pushTries
274+
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
275+
time.Sleep(time.Duration(waitSeconds) * time.Second)
276+
continue
277+
}
278+
return err
245279
}
246-
return err
280+
break
247281
}
248-
break
249282
}
250283

251284
if action == SubmitAction {
@@ -275,6 +308,47 @@ func prCreate(cmd *cobra.Command, _ []string) error {
275308
return nil
276309
}
277310

311+
func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.TrackingRef {
312+
refsForLookup := []string{"HEAD"}
313+
var trackingRefs []git.TrackingRef
314+
315+
headBranchConfig := git.ReadBranchConfig(headBranch)
316+
if headBranchConfig.RemoteName != "" {
317+
tr := git.TrackingRef{
318+
RemoteName: headBranchConfig.RemoteName,
319+
BranchName: strings.TrimPrefix(headBranchConfig.MergeRef, "refs/heads/"),
320+
}
321+
trackingRefs = append(trackingRefs, tr)
322+
refsForLookup = append(refsForLookup, tr.String())
323+
}
324+
325+
for _, remote := range remotes {
326+
tr := git.TrackingRef{
327+
RemoteName: remote.Name,
328+
BranchName: headBranch,
329+
}
330+
trackingRefs = append(trackingRefs, tr)
331+
refsForLookup = append(refsForLookup, tr.String())
332+
}
333+
334+
resolvedRefs, _ := git.ShowRefs(refsForLookup...)
335+
if len(resolvedRefs) > 1 {
336+
for _, r := range resolvedRefs[1:] {
337+
if r.Hash != resolvedRefs[0].Hash {
338+
continue
339+
}
340+
for _, tr := range trackingRefs {
341+
if tr.String() != r.Name {
342+
continue
343+
}
344+
return &tr
345+
}
346+
}
347+
}
348+
349+
return nil
350+
}
351+
278352
func generateCompareURL(r ghrepo.Interface, base, head, title, body string) string {
279353
u := fmt.Sprintf(
280354
"https://github.com/%s/compare/%s...%s?expand=1",

0 commit comments

Comments
 (0)
X Tutup