package context import ( "fmt" "regexp" "strings" "github.com/github/gh-cli/git" ) const defaultHostname = "github.com" // Remotes represents a set of git remotes type Remotes []*Remote // FindByName returns the first Remote whose name matches the list func (r Remotes) FindByName(names ...string) (*Remote, error) { for _, name := range names { for _, rem := range r { if rem.Name == name || name == "*" { return rem, nil } } } return nil, fmt.Errorf("no GitHub remotes found") } // Remote represents a git remote mapped to a GitHub repository type Remote struct { Name string Owner string Repo string } func (r *Remote) String() string { return r.Name } // GitHubRepository represents a GitHub respository type GitHubRepository struct { Name string Owner string } func parseRemotes(gitRemotes []string) (remotes Remotes) { re := regexp.MustCompile(`(.+)\s+(.+)\s+\((push|fetch)\)`) names := []string{} remotesMap := make(map[string]map[string]string) for _, r := range gitRemotes { if re.MatchString(r) { match := re.FindStringSubmatch(r) name := strings.TrimSpace(match[1]) url := strings.TrimSpace(match[2]) urlType := strings.TrimSpace(match[3]) utm, ok := remotesMap[name] if !ok { utm = make(map[string]string) remotesMap[name] = utm names = append(names, name) } utm[urlType] = url } } for _, name := range names { urlMap := remotesMap[name] repo, err := repoFromURL(urlMap["fetch"]) if err != nil { repo, err = repoFromURL(urlMap["push"]) } if err == nil { remotes = append(remotes, &Remote{ Name: name, Owner: repo.Owner, Repo: repo.Name, }) } } return } func repoFromURL(u string) (*GitHubRepository, error) { url, err := git.ParseURL(u) if err != nil { return nil, err } if url.Hostname() != defaultHostname { return nil, fmt.Errorf("invalid hostname: %s", url.Hostname()) } parts := strings.SplitN(strings.TrimPrefix(url.Path, "/"), "/", 3) if len(parts) < 2 { return nil, fmt.Errorf("invalid path: %s", url.Path) } return &GitHubRepository{ Owner: parts[0], Name: strings.TrimSuffix(parts[1], ".git"), }, nil }