...
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
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 }
155
View as plain text