-
Notifications
You must be signed in to change notification settings - Fork 65
/
git.go
151 lines (124 loc) · 3.41 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package cmdgit
import (
"bytes"
"context"
"fmt"
"os/exec"
"regexp"
"strings"
"github.com/lindell/multi-gitter/internal/git"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// Git is an implementation of git that executes git as commands
type Git struct {
Directory string // The (temporary) directory that should be worked within
FetchDepth int // Limit fetching to the specified number of commits
}
var errRe = regexp.MustCompile(`(^|\n)(error|fatal): (.+)`)
func (g *Git) run(cmd *exec.Cmd) (string, error) {
stderr := &bytes.Buffer{}
stdout := &bytes.Buffer{}
cmd.Dir = g.Directory
cmd.Stderr = stderr
cmd.Stdout = stdout
err := cmd.Run()
if err != nil {
matches := errRe.FindStringSubmatch(stderr.String())
if matches != nil {
return "", errors.New(matches[3])
}
msg := fmt.Sprintf(`git command exited with %d (%s)`,
cmd.ProcessState.ExitCode(),
stderr.String(),
)
return "", errors.New(msg)
}
return stdout.String(), nil
}
// Clone a repository
func (g *Git) Clone(ctx context.Context, url string, baseName string) error {
args := []string{"clone", url, "--branch", baseName, "--single-branch"}
if g.FetchDepth > 0 {
args = append(args, "--depth", fmt.Sprint(g.FetchDepth))
}
args = append(args, g.Directory)
cmd := exec.CommandContext(ctx, "git", args...)
_, err := g.run(cmd)
return err
}
// ChangeBranch changes the branch
func (g *Git) ChangeBranch(branchName string) error {
cmd := exec.Command("git", "checkout", "-b", branchName)
_, err := g.run(cmd)
return err
}
// Changes detect if any changes has been made in the directory
func (g *Git) Changes() (bool, error) {
cmd := exec.Command("git", "status", "-s")
stdOut, err := g.run(cmd)
return len(stdOut) > 0, err
}
// Commit and push all changes
func (g *Git) Commit(commitAuthor *git.CommitAuthor, commitMessage string) error {
cmd := exec.Command("git", "add", ".")
_, err := g.run(cmd)
if err != nil {
return err
}
cmd = exec.Command("git", "commit", "--no-verify", "-m", commitMessage)
if commitAuthor != nil {
cmd.Env = append(cmd.Env,
"GIT_AUTHOR_NAME="+commitAuthor.Name,
"GIT_AUTHOR_EMAIL="+commitAuthor.Email,
"GIT_COMMITTER_NAME="+commitAuthor.Name,
"GIT_COMMITTER_EMAIL="+commitAuthor.Email,
)
}
_, err = g.run(cmd)
if err != nil {
return err
}
if err := g.logDiff(); err != nil {
return err
}
return err
}
func (g *Git) logDiff() error {
if !log.IsLevelEnabled(log.DebugLevel) {
return nil
}
cmd := exec.Command("git", "diff", "HEAD~1")
stdout, err := g.run(cmd)
if err != nil {
return err
}
log.Debug(stdout)
return nil
}
// BranchExist checks if the new branch exists
func (g *Git) BranchExist(remoteName, branchName string) (bool, error) {
cmd := exec.Command("git", "ls-remote", "-q", "-h", remoteName)
stdOut, err := g.run(cmd)
if err != nil {
return false, err
}
return strings.Contains(stdOut, fmt.Sprintf("\trefs/heads/%s\n", branchName)), nil
}
// Push the committed changes to the remote
func (g *Git) Push(ctx context.Context, remoteName string, force bool) error {
args := []string{"push", "--no-verify", remoteName}
if force {
args = append(args, "--force")
}
args = append(args, "HEAD")
cmd := exec.CommandContext(ctx, "git", args...)
_, err := g.run(cmd)
return err
}
// AddRemote adds a new remote
func (g *Git) AddRemote(name, url string) error {
cmd := exec.Command("git", "remote", "add", name, url)
_, err := g.run(cmd)
return err
}