-
Notifications
You must be signed in to change notification settings - Fork 13
/
git.go
125 lines (104 loc) · 3.63 KB
/
git.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package main
import (
"bytes"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
)
// ResolveRedirect resolves URL redirects and returns parsed URL
func ResolveRedirect(url string) (*url.URL, error) {
url = strings.TrimSuffix(url, ".git")
res, err := http.Head(url)
if err != nil {
return nil, fmt.Errorf("could not send HEAD request to Git remote URL %q for following repository redirect: %w", url, err)
}
// GitHub returns 404 when the repository is private. GHE would do the same since all GHE repositories
// basically require authentication. 403 may be returned as well. (#19)
if res.StatusCode != 200 && res.StatusCode != 404 && res.StatusCode != 403 {
return nil, fmt.Errorf("HEAD request to Git remote URL %q for following repository redirect was not successful: %s", url, res.Status)
}
return res.Request.URL, nil
}
// Git represents Git command for specific repository
type Git struct {
bin string
root string
}
// Command returns exec.Command instance which runs given Git subcommand with given arguments
func (git *Git) Command(subcmd string, args ...string) *exec.Cmd {
// e.g. 'git diff --cached' -> 'git -C /path/to/repo diff --cached'
a := append([]string{"-C", git.root, subcmd}, args...)
cmd := exec.Command(git.bin, a...)
return cmd
}
// Exec runs runs given Git subcommand with given arguments
func (git *Git) Exec(subcmd string, args ...string) (string, error) {
out, err := git.Command(subcmd, args...).CombinedOutput()
out = bytes.TrimSpace(out)
if err != nil {
for i, c := range out {
if c == '\n' || c == '\r' {
out[i] = ' '
}
}
return "", fmt.Errorf("Git command %q with args %v failed with output %q: %w", subcmd, args, out, err)
}
return string(out), nil
}
// FirstRemoteName returns remote name of current Git repository. When multiple remotes are
// configured, the first one will be chosen.
func (git *Git) FirstRemoteName() (string, error) {
s, err := git.Exec("remote")
if err != nil {
return "", fmt.Errorf("could not retrieve remote name: %w", err)
}
if i := strings.IndexAny(s, "\r\n"); i >= 0 {
s = s[:i]
}
if s == "" {
return "", fmt.Errorf("no remote is configured in this repository")
}
return s, nil
}
// FirstRemoteURL returns a URL of remote repository. When multiple remotes are configured, the
// first one will be chosen.
func (git *Git) FirstRemoteURL() (*url.URL, error) {
r, err := git.FirstRemoteName()
if err != nil {
return nil, fmt.Errorf("could not get URL of remote repository: %w", err)
}
s, err := git.Exec("config", fmt.Sprintf("remote.%s.url", r))
if err != nil {
return nil, fmt.Errorf("could not get URL of remote %q: %w", r, err)
}
if strings.HasPrefix(s, "git@") && strings.ContainsRune(s, ':') {
// git@github.com:user/repo.git → https://github.com/user/repo.git
s = "https://" + strings.Replace(strings.TrimPrefix(s, "git@"), ":", "/", 1)
} else if strings.HasPrefix(s, "ssh://git@") {
// ssh://git@github.com/user/repo.git → https://github.com/user/repo.git
s = "https://" + strings.TrimPrefix(s, "ssh://git@")
}
if !strings.HasPrefix(s, "https://") && !strings.HasPrefix(s, "http://") {
return nil, fmt.Errorf("repository URL is neither HTTP nor HTTPS: %s", s)
}
u, err := ResolveRedirect(s)
if err != nil {
return nil, err
}
return u, nil
}
// NewGitForCwd creates Git instance from Config value. Home directory is assumed to be a root of Git repository
func NewGitForCwd() (*Git, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("cannot get cwd: %w", err)
}
exe, err := exec.LookPath("git")
if err != nil {
return nil, fmt.Errorf("'git' executable not found: %w", err)
}
return &Git{exe, cwd}, nil
}