cli/internal/ghrepo/repo.go
Benjamin Chadwick c581a093ed
Encode segments of gh browse resulting URL (#4663)
- URLs are now always generated without a trailing slash
- Windows-style paths are normalized before processing
- Special characters in branch names are escaped

Co-authored-by: Mislav Marohnić <mislav@github.com>
2021-11-18 13:10:55 +00:00

146 lines
3.8 KiB
Go

package ghrepo
import (
"fmt"
"net/url"
"strings"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/ghinstance"
)
// Interface describes an object that represents a GitHub repository
type Interface interface {
RepoName() string
RepoOwner() string
RepoHost() string
}
// New instantiates a GitHub repository from owner and name arguments
func New(owner, repo string) Interface {
return NewWithHost(owner, repo, ghinstance.Default())
}
// NewWithHost is like New with an explicit host name
func NewWithHost(owner, repo, hostname string) Interface {
return &ghRepo{
owner: owner,
name: repo,
hostname: normalizeHostname(hostname),
}
}
// FullName serializes a GitHub repository into an "OWNER/REPO" string
func FullName(r Interface) string {
return fmt.Sprintf("%s/%s", r.RepoOwner(), r.RepoName())
}
var defaultHostOverride string
func defaultHost() string {
if defaultHostOverride != "" {
return defaultHostOverride
}
return ghinstance.Default()
}
// SetDefaultHost overrides the default GitHub hostname for FromFullName.
// TODO: remove after FromFullName approach is revisited
func SetDefaultHost(host string) {
defaultHostOverride = host
}
// FromFullName extracts the GitHub repository information from the following
// formats: "OWNER/REPO", "HOST/OWNER/REPO", and a full URL.
func FromFullName(nwo string) (Interface, error) {
return FromFullNameWithHost(nwo, defaultHost())
}
// FromFullNameWithHost is like FromFullName that defaults to a specific host for values that don't
// explicitly include a hostname.
func FromFullNameWithHost(nwo, fallbackHost string) (Interface, error) {
if git.IsURL(nwo) {
u, err := git.ParseURL(nwo)
if err != nil {
return nil, err
}
return FromURL(u)
}
parts := strings.SplitN(nwo, "/", 4)
for _, p := range parts {
if len(p) == 0 {
return nil, fmt.Errorf(`expected the "[HOST/]OWNER/REPO" format, got %q`, nwo)
}
}
switch len(parts) {
case 3:
return NewWithHost(parts[1], parts[2], parts[0]), nil
case 2:
return NewWithHost(parts[0], parts[1], fallbackHost), nil
default:
return nil, fmt.Errorf(`expected the "[HOST/]OWNER/REPO" format, got %q`, nwo)
}
}
// FromURL extracts the GitHub repository information from a git remote URL
func FromURL(u *url.URL) (Interface, error) {
if u.Hostname() == "" {
return nil, fmt.Errorf("no hostname detected")
}
parts := strings.SplitN(strings.Trim(u.Path, "/"), "/", 3)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid path: %s", u.Path)
}
return NewWithHost(parts[0], strings.TrimSuffix(parts[1], ".git"), u.Hostname()), nil
}
func normalizeHostname(h string) string {
return strings.ToLower(strings.TrimPrefix(h, "www."))
}
// IsSame compares two GitHub repositories
func IsSame(a, b Interface) bool {
return strings.EqualFold(a.RepoOwner(), b.RepoOwner()) &&
strings.EqualFold(a.RepoName(), b.RepoName()) &&
normalizeHostname(a.RepoHost()) == normalizeHostname(b.RepoHost())
}
func GenerateRepoURL(repo Interface, p string, args ...interface{}) string {
baseURL := fmt.Sprintf("%s%s/%s", ghinstance.HostPrefix(repo.RepoHost()), repo.RepoOwner(), repo.RepoName())
if p != "" {
if path := fmt.Sprintf(p, args...); path != "" {
return baseURL + "/" + path
}
}
return baseURL
}
// TODO there is a parallel implementation for non-isolated commands
func FormatRemoteURL(repo Interface, protocol string) string {
if protocol == "ssh" {
return fmt.Sprintf("git@%s:%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
}
return fmt.Sprintf("%s%s/%s.git", ghinstance.HostPrefix(repo.RepoHost()), repo.RepoOwner(), repo.RepoName())
}
type ghRepo struct {
owner string
name string
hostname string
}
func (r ghRepo) RepoOwner() string {
return r.owner
}
func (r ghRepo) RepoName() string {
return r.name
}
func (r ghRepo) RepoHost() string {
return r.hostname
}