馃嵃 Personal Multi-Git Remote Manager
go git
at main 219 lines 4.1 kB view raw
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}