github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/internal/ghrepo/repo.go (about) 1 package ghrepo 2 3 import ( 4 "fmt" 5 "net/url" 6 "strings" 7 8 "github.com/cli/cli/git" 9 "github.com/cli/cli/internal/ghinstance" 10 ) 11 12 // Interface describes an object that represents a GitHub repository 13 type Interface interface { 14 RepoName() string 15 RepoOwner() string 16 RepoHost() string 17 } 18 19 // New instantiates a GitHub repository from owner and name arguments 20 func New(owner, repo string) Interface { 21 return NewWithHost(owner, repo, ghinstance.Default()) 22 } 23 24 // NewWithHost is like New with an explicit host name 25 func NewWithHost(owner, repo, hostname string) Interface { 26 return &ghRepo{ 27 owner: owner, 28 name: repo, 29 hostname: normalizeHostname(hostname), 30 } 31 } 32 33 // FullName serializes a GitHub repository into an "OWNER/REPO" string 34 func FullName(r Interface) string { 35 return fmt.Sprintf("%s/%s", r.RepoOwner(), r.RepoName()) 36 } 37 38 var defaultHostOverride string 39 40 func defaultHost() string { 41 if defaultHostOverride != "" { 42 return defaultHostOverride 43 } 44 return ghinstance.Default() 45 } 46 47 // SetDefaultHost overrides the default GitHub hostname for FromFullName. 48 // TODO: remove after FromFullName approach is revisited 49 func SetDefaultHost(host string) { 50 defaultHostOverride = host 51 } 52 53 // FromFullName extracts the GitHub repository information from the following 54 // formats: "OWNER/REPO", "HOST/OWNER/REPO", and a full URL. 55 func FromFullName(nwo string) (Interface, error) { 56 if git.IsURL(nwo) { 57 u, err := git.ParseURL(nwo) 58 if err != nil { 59 return nil, err 60 } 61 return FromURL(u) 62 } 63 64 parts := strings.SplitN(nwo, "/", 4) 65 for _, p := range parts { 66 if len(p) == 0 { 67 return nil, fmt.Errorf(`expected the "[HOST/]OWNER/REPO" format, got %q`, nwo) 68 } 69 } 70 switch len(parts) { 71 case 3: 72 return NewWithHost(parts[1], parts[2], parts[0]), nil 73 case 2: 74 return NewWithHost(parts[0], parts[1], defaultHost()), nil 75 default: 76 return nil, fmt.Errorf(`expected the "[HOST/]OWNER/REPO" format, got %q`, nwo) 77 } 78 } 79 80 // FromURL extracts the GitHub repository information from a git remote URL 81 func FromURL(u *url.URL) (Interface, error) { 82 if u.Hostname() == "" { 83 return nil, fmt.Errorf("no hostname detected") 84 } 85 86 parts := strings.SplitN(strings.Trim(u.Path, "/"), "/", 3) 87 if len(parts) != 2 { 88 return nil, fmt.Errorf("invalid path: %s", u.Path) 89 } 90 91 return NewWithHost(parts[0], strings.TrimSuffix(parts[1], ".git"), u.Hostname()), nil 92 } 93 94 func normalizeHostname(h string) string { 95 return strings.ToLower(strings.TrimPrefix(h, "www.")) 96 } 97 98 // IsSame compares two GitHub repositories 99 func IsSame(a, b Interface) bool { 100 return strings.EqualFold(a.RepoOwner(), b.RepoOwner()) && 101 strings.EqualFold(a.RepoName(), b.RepoName()) && 102 normalizeHostname(a.RepoHost()) == normalizeHostname(b.RepoHost()) 103 } 104 105 func GenerateRepoURL(repo Interface, p string, args ...interface{}) string { 106 baseURL := fmt.Sprintf("https://%s/%s/%s", repo.RepoHost(), repo.RepoOwner(), repo.RepoName()) 107 if p != "" { 108 return baseURL + "/" + fmt.Sprintf(p, args...) 109 } 110 return baseURL 111 } 112 113 // TODO there is a parallel implementation for non-isolated commands 114 func FormatRemoteURL(repo Interface, protocol string) string { 115 if protocol == "ssh" { 116 return fmt.Sprintf("git@%s:%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName()) 117 } 118 119 return fmt.Sprintf("https://%s/%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName()) 120 } 121 122 type ghRepo struct { 123 owner string 124 name string 125 hostname string 126 } 127 128 func (r ghRepo) RepoOwner() string { 129 return r.owner 130 } 131 132 func (r ghRepo) RepoName() string { 133 return r.name 134 } 135 136 func (r ghRepo) RepoHost() string { 137 return r.hostname 138 }