github.com/chelnak/go-gh@v0.0.2/internal/git/remote.go (about)

     1  package git
     2  
     3  import (
     4  	"net/url"
     5  	"regexp"
     6  	"sort"
     7  	"strings"
     8  )
     9  
    10  var remoteRE = regexp.MustCompile(`(.+)\s+(.+)\s+\((push|fetch)\)`)
    11  
    12  type RemoteSet []*Remote
    13  
    14  type Remote struct {
    15  	Name     string
    16  	FetchURL *url.URL
    17  	PushURL  *url.URL
    18  	Resolved string
    19  	Host     string
    20  	Owner    string
    21  	Repo     string
    22  }
    23  
    24  func (r RemoteSet) Len() int      { return len(r) }
    25  func (r RemoteSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
    26  func (r RemoteSet) Less(i, j int) bool {
    27  	return remoteNameSortScore(r[i].Name) > remoteNameSortScore(r[j].Name)
    28  }
    29  
    30  func remoteNameSortScore(name string) int {
    31  	switch strings.ToLower(name) {
    32  	case "upstream":
    33  		return 3
    34  	case "github":
    35  		return 2
    36  	case "origin":
    37  		return 1
    38  	default:
    39  		return 0
    40  	}
    41  }
    42  
    43  func Remotes() (RemoteSet, error) {
    44  	list, err := listRemotes()
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	remotes := parseRemotes(list)
    49  	setResolvedRemotes(remotes)
    50  	sort.Sort(remotes)
    51  	return remotes, nil
    52  }
    53  
    54  // Filter remotes by given hostnames, maintains original order.
    55  func (rs RemoteSet) FilterByHosts(hosts []string) RemoteSet {
    56  	filtered := make(RemoteSet, 0)
    57  	for _, remote := range rs {
    58  		for _, host := range hosts {
    59  			if strings.EqualFold(remote.Host, host) {
    60  				filtered = append(filtered, remote)
    61  				break
    62  			}
    63  		}
    64  	}
    65  	return filtered
    66  }
    67  
    68  func listRemotes() ([]string, error) {
    69  	stdOut, _, err := Exec("remote", "-v")
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return toLines(stdOut.String()), nil
    74  }
    75  
    76  func parseRemotes(gitRemotes []string) RemoteSet {
    77  	remotes := RemoteSet{}
    78  	for _, r := range gitRemotes {
    79  		match := remoteRE.FindStringSubmatch(r)
    80  		if match == nil {
    81  			continue
    82  		}
    83  		name := strings.TrimSpace(match[1])
    84  		urlStr := strings.TrimSpace(match[2])
    85  		urlType := strings.TrimSpace(match[3])
    86  
    87  		url, err := parseURL(urlStr)
    88  		if err != nil {
    89  			continue
    90  		}
    91  		host, owner, repo, _ := repoInfoFromURL(url)
    92  
    93  		var rem *Remote
    94  		if len(remotes) > 0 {
    95  			rem = remotes[len(remotes)-1]
    96  			if name != rem.Name {
    97  				rem = nil
    98  			}
    99  		}
   100  		if rem == nil {
   101  			rem = &Remote{Name: name}
   102  			remotes = append(remotes, rem)
   103  		}
   104  
   105  		switch urlType {
   106  		case "fetch":
   107  			rem.FetchURL = url
   108  			rem.Host = host
   109  			rem.Owner = owner
   110  			rem.Repo = repo
   111  		case "push":
   112  			rem.PushURL = url
   113  			if rem.Host == "" {
   114  				rem.Host = host
   115  			}
   116  			if rem.Owner == "" {
   117  				rem.Owner = owner
   118  			}
   119  			if rem.Repo == "" {
   120  				rem.Repo = repo
   121  			}
   122  		}
   123  	}
   124  	return remotes
   125  }
   126  
   127  func setResolvedRemotes(remotes RemoteSet) {
   128  	stdOut, _, err := Exec("config", "--get-regexp", `^remote\..*\.gh-resolved$`)
   129  	if err != nil {
   130  		return
   131  	}
   132  	for _, l := range toLines(stdOut.String()) {
   133  		parts := strings.SplitN(l, " ", 2)
   134  		if len(parts) < 2 {
   135  			continue
   136  		}
   137  		rp := strings.SplitN(parts[0], ".", 3)
   138  		if len(rp) < 2 {
   139  			continue
   140  		}
   141  		name := rp[1]
   142  		for _, r := range remotes {
   143  			if r.Name == name {
   144  				r.Resolved = parts[1]
   145  				break
   146  			}
   147  		}
   148  	}
   149  }
   150  
   151  func toLines(output string) []string {
   152  	lines := strings.TrimSuffix(output, "\n")
   153  	return strings.Split(lines, "\n")
   154  }