馃嵃 Personal Multi-Git Remote Manager
go
git
1package git
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "os"
8 "os/exec"
9 "strings"
10
11 "github.com/ebisu/mugi/internal/remote"
12)
13
14const sshEnv = "GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=accept-new"
15
16func gitEnv() []string {
17 return append(os.Environ(), sshEnv)
18}
19
20type Result struct {
21 Repo string
22 Remote string
23 Output string
24 Error error
25 ExitCode int
26}
27
28func (r *Result) setError(err error) {
29 r.Error = err
30
31 var exitErr *exec.ExitError
32
33 if errors.As(err, &exitErr) {
34 r.ExitCode = exitErr.ExitCode()
35 } else {
36 r.ExitCode = 1
37 }
38
39 if r.Output == "" {
40 r.Output = err.Error()
41 }
42}
43
44func Execute(ctx context.Context, op remote.Operation, repoPath, remoteName string, force bool) Result {
45 result := Result{
46 Repo: repoPath,
47 Remote: remoteName,
48 }
49
50 args := buildArgs(op, remoteName, repoPath, force)
51 cmd := exec.CommandContext(ctx, "git", args...)
52 cmd.Dir = repoPath
53 cmd.Env = gitEnv()
54
55 var stdout, stderr bytes.Buffer
56 cmd.Stdout = &stdout
57 cmd.Stderr = &stderr
58
59 err := cmd.Run()
60 result.Output = strings.TrimSpace(stdout.String() + stderr.String())
61
62 if err != nil {
63 result.setError(err)
64 }
65
66 return result
67}
68
69func buildArgs(op remote.Operation, remoteName, repoPath string, force bool) []string {
70 switch op {
71 case remote.Pull:
72 branch := currentBranch(repoPath)
73 if branch == "" {
74 branch = "HEAD"
75 }
76
77 return []string{"pull", remoteName, branch}
78 case remote.Push:
79 if force {
80 return []string{"push", "--force", remoteName}
81 }
82
83 return []string{"push", remoteName}
84 case remote.Fetch:
85 return []string{"fetch", remoteName}
86 default:
87 return nil
88 }
89}
90
91func currentBranch(repoPath string) string {
92 cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
93 cmd.Dir = repoPath
94
95 out, err := cmd.Output()
96 if err != nil {
97 return ""
98 }
99
100 return strings.TrimSpace(string(out))
101}
102
103func IsRepo(path string) bool {
104 cmd := exec.Command("git", "rev-parse", "--git-dir")
105 cmd.Dir = path
106
107 return cmd.Run() == nil
108}
109
110func Clone(ctx context.Context, url, path string) Result {
111 result := Result{
112 Repo: path,
113 Remote: "origin",
114 }
115
116 cmd := exec.CommandContext(ctx, "git", "clone", url, path)
117 cmd.Env = gitEnv()
118
119 var stdout, stderr bytes.Buffer
120 cmd.Stdout = &stdout
121 cmd.Stderr = &stderr
122
123 err := cmd.Run()
124 result.Output = strings.TrimSpace(stdout.String() + stderr.String())
125
126 if err != nil {
127 result.setError(err)
128 }
129
130 return result
131}
132
133func AddRemote(ctx context.Context, repoPath, name, url string) Result {
134 result := Result{
135 Repo: repoPath,
136 Remote: name,
137 }
138
139 cmd := exec.CommandContext(ctx, "git", "remote", "add", name, url)
140 cmd.Dir = repoPath
141
142 var stdout, stderr bytes.Buffer
143 cmd.Stdout = &stdout
144 cmd.Stderr = &stderr
145
146 err := cmd.Run()
147 result.Output = strings.TrimSpace(stdout.String() + stderr.String())
148
149 if err != nil {
150 result.setError(err)
151 }
152
153 return result
154}
155
156func RenameRemote(ctx context.Context, repoPath, oldName, newName string) Result {
157 result := Result{
158 Repo: repoPath,
159 Remote: newName,
160 }
161
162 cmd := exec.CommandContext(ctx, "git", "remote", "rename", oldName, newName)
163 cmd.Dir = repoPath
164
165 var stdout, stderr bytes.Buffer
166 cmd.Stdout = &stdout
167 cmd.Stderr = &stderr
168
169 err := cmd.Run()
170 result.Output = strings.TrimSpace(stdout.String() + stderr.String())
171
172 if err != nil {
173 result.setError(err)
174 }
175
176 return result
177}
178
179func HasRemote(repoPath, name string) bool {
180 cmd := exec.Command("git", "remote", "get-url", name)
181 cmd.Dir = repoPath
182
183 return cmd.Run() == nil
184}
185
186func SetRemoteURL(ctx context.Context, repoPath, name, url string) Result {
187 result := Result{
188 Repo: repoPath,
189 Remote: name,
190 }
191
192 cmd := exec.CommandContext(ctx, "git", "remote", "set-url", name, url)
193 cmd.Dir = repoPath
194
195 var stdout, stderr bytes.Buffer
196 cmd.Stdout = &stdout
197 cmd.Stderr = &stderr
198
199 err := cmd.Run()
200 result.Output = strings.TrimSpace(stdout.String() + stderr.String())
201
202 if err != nil {
203 result.setError(err)
204 }
205
206 return result
207}
208
209func GetRemoteURL(repoPath, name string) string {
210 cmd := exec.Command("git", "remote", "get-url", name)
211 cmd.Dir = repoPath
212
213 out, err := cmd.Output()
214 if err != nil {
215 return ""
216 }
217
218 return strings.TrimSpace(string(out))
219}