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 }