Skip to content

Commit c8cf54c

Browse files
authored
Merge pull request cli#1258 from cli/ghe-remotes
Parse and respect non-github.com git remotes
2 parents 6e9fab6 + 87a9dc8 commit c8cf54c

File tree

17 files changed

+288
-145
lines changed

17 files changed

+288
-145
lines changed

api/queries_repo.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type Repository struct {
3434
}
3535

3636
Parent *Repository
37+
38+
// pseudo-field that keeps track of host name of this repo
39+
hostname string
3740
}
3841

3942
// RepositoryOwner is the owner of a GitHub repository
@@ -51,6 +54,11 @@ func (r Repository) RepoName() string {
5154
return r.Name
5255
}
5356

57+
// RepoHost is the GitHub hostname of the repository
58+
func (r Repository) RepoHost() string {
59+
return r.hostname
60+
}
61+
5462
// IsFork is true when this repository has a parent repository
5563
func (r Repository) IsFork() bool {
5664
return r.Parent != nil
@@ -103,7 +111,7 @@ func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
103111
return nil, err
104112
}
105113

106-
return &result.Repository, nil
114+
return initRepoHostname(&result.Repository, repo.RepoHost()), nil
107115
}
108116

109117
// RepoParent finds out the parent repository of a fork
@@ -133,7 +141,7 @@ func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error)
133141
return nil, nil
134142
}
135143

136-
parent := ghrepo.New(query.Repository.Parent.Owner.Login, query.Repository.Parent.Name)
144+
parent := ghrepo.NewWithHost(query.Repository.Parent.Owner.Login, query.Repository.Parent.Name, repo.RepoHost())
137145
return parent, nil
138146
}
139147

@@ -145,6 +153,11 @@ type RepoNetworkResult struct {
145153

146154
// RepoNetwork inspects the relationship between multiple GitHub repositories
147155
func RepoNetwork(client *Client, repos []ghrepo.Interface) (RepoNetworkResult, error) {
156+
var hostname string
157+
if len(repos) > 0 {
158+
hostname = repos[0].RepoHost()
159+
}
160+
148161
queries := make([]string, 0, len(repos))
149162
for i, repo := range repos {
150163
queries = append(queries, fmt.Sprintf(`
@@ -227,14 +240,22 @@ func RepoNetwork(client *Client, repos []ghrepo.Interface) (RepoNetworkResult, e
227240
if err := decoder.Decode(&repo); err != nil {
228241
return result, err
229242
}
230-
result.Repositories = append(result.Repositories, &repo)
243+
result.Repositories = append(result.Repositories, initRepoHostname(&repo, hostname))
231244
} else {
232245
return result, fmt.Errorf("unknown GraphQL result key %q", name)
233246
}
234247
}
235248
return result, nil
236249
}
237250

251+
func initRepoHostname(repo *Repository, hostname string) *Repository {
252+
repo.hostname = hostname
253+
if repo.Parent != nil {
254+
repo.Parent.hostname = hostname
255+
}
256+
return repo
257+
}
258+
238259
// repositoryV3 is the repository result from GitHub API v3
239260
type repositoryV3 struct {
240261
NodeID string
@@ -265,6 +286,7 @@ func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
265286
Login: result.Owner.Login,
266287
},
267288
ViewerPermission: "WRITE",
289+
hostname: repo.RepoHost(),
268290
}, nil
269291
}
270292

@@ -306,7 +328,7 @@ func RepoFindFork(client *Client, repo ghrepo.Interface) (*Repository, error) {
306328
// `affiliations` condition, to guard against versions of GitHub with a
307329
// faulty `affiliations` implementation
308330
if len(forks) > 0 && forks[0].ViewerCanPush() {
309-
return &forks[0], nil
331+
return initRepoHostname(&forks[0], repo.RepoHost()), nil
310332
}
311333
return nil, &NotFoundError{errors.New("no fork found")}
312334
}
@@ -368,7 +390,8 @@ func RepoCreate(client *Client, input RepoCreateInput) (*Repository, error) {
368390
return nil, err
369391
}
370392

371-
return &response.CreateRepository.Repository, nil
393+
// FIXME: support Enterprise hosts
394+
return initRepoHostname(&response.CreateRepository.Repository, "github.com"), nil
372395
}
373396

374397
func RepositoryReadme(client *Client, fullName string) (string, error) {

command/issue.go

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package command
22

33
import (
4-
"errors"
54
"fmt"
65
"io"
76
"net/url"
8-
"regexp"
97
"strconv"
108
"strings"
119
"time"
@@ -259,12 +257,7 @@ func issueView(cmd *cobra.Command, args []string) error {
259257
return err
260258
}
261259

262-
baseRepo, err := determineBaseRepo(apiClient, cmd, ctx)
263-
if err != nil {
264-
return err
265-
}
266-
267-
issue, err := issueFromArg(apiClient, baseRepo, args[0])
260+
issue, _, err := issueFromArg(ctx, apiClient, cmd, args[0])
268261
if err != nil {
269262
return err
270263
}
@@ -380,21 +373,6 @@ func printHumanIssuePreview(out io.Writer, issue *api.Issue) error {
380373
return nil
381374
}
382375

383-
var issueURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/issues/(\d+)`)
384-
385-
func issueFromArg(apiClient *api.Client, baseRepo ghrepo.Interface, arg string) (*api.Issue, error) {
386-
if issueNumber, err := strconv.Atoi(strings.TrimPrefix(arg, "#")); err == nil {
387-
return api.IssueByNumber(apiClient, baseRepo, issueNumber)
388-
}
389-
390-
if m := issueURLRE.FindStringSubmatch(arg); m != nil {
391-
issueNumber, _ := strconv.Atoi(m[3])
392-
return api.IssueByNumber(apiClient, baseRepo, issueNumber)
393-
}
394-
395-
return nil, fmt.Errorf("invalid issue format: %q", arg)
396-
}
397-
398376
func issueCreate(cmd *cobra.Command, args []string) error {
399377
ctx := contextForCommand(cmd)
400378
apiClient, err := apiClientForContext(ctx)
@@ -450,8 +428,7 @@ func issueCreate(cmd *cobra.Command, args []string) error {
450428
}
451429

452430
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
453-
// TODO: move URL generation into GitHubRepository
454-
openURL := fmt.Sprintf("https://github.com/%s/issues/new", ghrepo.FullName(baseRepo))
431+
openURL := generateRepoURL(baseRepo, "issues/new")
455432
if title != "" || body != "" {
456433
milestone := ""
457434
if len(milestoneTitles) > 0 {
@@ -527,7 +504,7 @@ func issueCreate(cmd *cobra.Command, args []string) error {
527504
}
528505

529506
if action == PreviewAction {
530-
openURL := fmt.Sprintf("https://github.com/%s/issues/new/", ghrepo.FullName(baseRepo))
507+
openURL := generateRepoURL(baseRepo, "issues/new")
531508
milestone := ""
532509
if len(milestoneTitles) > 0 {
533510
milestone = milestoneTitles[0]
@@ -563,6 +540,14 @@ func issueCreate(cmd *cobra.Command, args []string) error {
563540
return nil
564541
}
565542

543+
func generateRepoURL(repo ghrepo.Interface, p string, args ...interface{}) string {
544+
baseURL := fmt.Sprintf("https://%s/%s/%s", repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
545+
if p != "" {
546+
return baseURL + "/" + fmt.Sprintf(p, args...)
547+
}
548+
return baseURL
549+
}
550+
566551
func addMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, params map[string]interface{}, tb *issueMetadataState) error {
567552
if !tb.HasMetadata() {
568553
return nil
@@ -735,27 +720,19 @@ func issueClose(cmd *cobra.Command, args []string) error {
735720
return err
736721
}
737722

738-
baseRepo, err := determineBaseRepo(apiClient, cmd, ctx)
723+
issue, baseRepo, err := issueFromArg(ctx, apiClient, cmd, args[0])
739724
if err != nil {
740725
return err
741726
}
742727

743-
issue, err := issueFromArg(apiClient, baseRepo, args[0])
744-
var idErr *api.IssuesDisabledError
745-
if errors.As(err, &idErr) {
746-
return fmt.Errorf("issues disabled for %s", ghrepo.FullName(baseRepo))
747-
} else if err != nil {
748-
return err
749-
}
750-
751728
if issue.Closed {
752729
fmt.Fprintf(colorableErr(cmd), "%s Issue #%d (%s) is already closed\n", utils.Yellow("!"), issue.Number, issue.Title)
753730
return nil
754731
}
755732

756733
err = api.IssueClose(apiClient, baseRepo, *issue)
757734
if err != nil {
758-
return fmt.Errorf("API call failed:%w", err)
735+
return err
759736
}
760737

761738
fmt.Fprintf(colorableErr(cmd), "%s Closed issue #%d (%s)\n", utils.Red("✔"), issue.Number, issue.Title)
@@ -770,27 +747,19 @@ func issueReopen(cmd *cobra.Command, args []string) error {
770747
return err
771748
}
772749

773-
baseRepo, err := determineBaseRepo(apiClient, cmd, ctx)
750+
issue, baseRepo, err := issueFromArg(ctx, apiClient, cmd, args[0])
774751
if err != nil {
775752
return err
776753
}
777754

778-
issue, err := issueFromArg(apiClient, baseRepo, args[0])
779-
var idErr *api.IssuesDisabledError
780-
if errors.As(err, &idErr) {
781-
return fmt.Errorf("issues disabled for %s", ghrepo.FullName(baseRepo))
782-
} else if err != nil {
783-
return err
784-
}
785-
786755
if !issue.Closed {
787756
fmt.Fprintf(colorableErr(cmd), "%s Issue #%d (%s) is already open\n", utils.Yellow("!"), issue.Number, issue.Title)
788757
return nil
789758
}
790759

791760
err = api.IssueReopen(apiClient, baseRepo, *issue)
792761
if err != nil {
793-
return fmt.Errorf("API call failed:%w", err)
762+
return err
794763
}
795764

796765
fmt.Fprintf(colorableErr(cmd), "%s Reopened issue #%d (%s)\n", utils.Green("✔"), issue.Number, issue.Title)

command/issue_lookup.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"regexp"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/cli/cli/api"
11+
"github.com/cli/cli/context"
12+
"github.com/cli/cli/internal/ghrepo"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
func issueFromArg(ctx context.Context, apiClient *api.Client, cmd *cobra.Command, arg string) (*api.Issue, ghrepo.Interface, error) {
17+
issue, baseRepo, err := issueFromURL(apiClient, arg)
18+
if err != nil {
19+
return nil, nil, err
20+
}
21+
if issue != nil {
22+
return issue, baseRepo, nil
23+
}
24+
25+
baseRepo, err = determineBaseRepo(apiClient, cmd, ctx)
26+
if err != nil {
27+
return nil, nil, fmt.Errorf("could not determine base repo: %w", err)
28+
}
29+
30+
issueNumber, err := strconv.Atoi(strings.TrimPrefix(arg, "#"))
31+
if err != nil {
32+
return nil, nil, fmt.Errorf("invalid issue format: %q", arg)
33+
}
34+
35+
issue, err = issueFromNumber(apiClient, baseRepo, issueNumber)
36+
return issue, baseRepo, err
37+
}
38+
39+
var issueURLRE = regexp.MustCompile(`^/([^/]+)/([^/]+)/issues/(\d+)`)
40+
41+
func issueFromURL(apiClient *api.Client, s string) (*api.Issue, ghrepo.Interface, error) {
42+
u, err := url.Parse(s)
43+
if err != nil {
44+
return nil, nil, nil
45+
}
46+
47+
if u.Scheme != "https" && u.Scheme != "http" {
48+
return nil, nil, nil
49+
}
50+
51+
m := issueURLRE.FindStringSubmatch(u.Path)
52+
if m == nil {
53+
return nil, nil, nil
54+
}
55+
56+
repo := ghrepo.NewWithHost(m[1], m[2], u.Hostname())
57+
issueNumber, _ := strconv.Atoi(m[3])
58+
issue, err := issueFromNumber(apiClient, repo, issueNumber)
59+
return issue, repo, err
60+
}
61+
62+
func issueFromNumber(apiClient *api.Client, repo ghrepo.Interface, issueNumber int) (*api.Issue, error) {
63+
return api.IssueByNumber(apiClient, repo, issueNumber)
64+
}

command/issue_test.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ func TestIssueView_tty_Preview(t *testing.T) {
473473
func TestIssueView_web_notFound(t *testing.T) {
474474
initBlankContext("", "OWNER/REPO", "master")
475475
http := initFakeHTTP()
476+
http.StubRepoResponse("OWNER", "REPO")
476477

477478
http.StubResponse(200, bytes.NewBufferString(`
478479
{ "errors": [
@@ -518,7 +519,6 @@ func TestIssueView_disabledIssues(t *testing.T) {
518519
func TestIssueView_web_urlArg(t *testing.T) {
519520
initBlankContext("", "OWNER/REPO", "master")
520521
http := initFakeHTTP()
521-
http.StubRepoResponse("OWNER", "REPO")
522522

523523
http.StubResponse(200, bytes.NewBufferString(`
524524
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
@@ -965,12 +965,8 @@ func TestIssueClose_issuesDisabled(t *testing.T) {
965965
`))
966966

967967
_, err := RunCommand("issue close 13")
968-
if err == nil {
969-
t.Fatalf("expected error when issues are disabled")
970-
}
971-
972-
if !strings.Contains(err.Error(), "issues disabled") {
973-
t.Fatalf("got unexpected error: %s", err)
968+
if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
969+
t.Fatalf("got error: %v", err)
974970
}
975971
}
976972

@@ -1038,11 +1034,7 @@ func TestIssueReopen_issuesDisabled(t *testing.T) {
10381034
`))
10391035

10401036
_, err := RunCommand("issue reopen 2")
1041-
if err == nil {
1042-
t.Fatalf("expected error when issues are disabled")
1043-
}
1044-
1045-
if !strings.Contains(err.Error(), "issues disabled") {
1046-
t.Fatalf("got unexpected error: %s", err)
1037+
if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
1038+
t.Fatalf("got error: %v", err)
10471039
}
10481040
}

command/pr_checkout.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func prCheckout(cmd *cobra.Command, args []string) error {
3333

3434
baseRemote, _ := remotes.FindByRepo(baseRepo.RepoOwner(), baseRepo.RepoName())
3535
// baseRemoteSpec is a repository URL or a remote name to be used in git fetch
36-
baseURLOrName := formatRemoteURL(cmd, ghrepo.FullName(baseRepo))
36+
baseURLOrName := formatRemoteURL(cmd, baseRepo)
3737
if baseRemote != nil {
3838
baseURLOrName = baseRemote.Name
3939
}
@@ -84,7 +84,8 @@ func prCheckout(cmd *cobra.Command, args []string) error {
8484
remote := baseURLOrName
8585
mergeRef := ref
8686
if pr.MaintainerCanModify {
87-
remote = formatRemoteURL(cmd, fmt.Sprintf("%s/%s", pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name))
87+
headRepo := ghrepo.NewWithHost(pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name, baseRepo.RepoHost())
88+
remote = formatRemoteURL(cmd, headRepo)
8889
mergeRef = fmt.Sprintf("refs/heads/%s", pr.HeadRefName)
8990
}
9091
if mc, err := git.Config(fmt.Sprintf("branch.%s.merge", newBranchName)); err != nil || mc == "" {

0 commit comments

Comments
 (0)