diff options
| author | Fuwn <[email protected]> | 2026-01-26 05:13:25 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-26 05:13:25 +0000 |
| commit | 567e5b51d32450999935bcf3af143248888e12e9 (patch) | |
| tree | 41bb8b2a8fcae168bcb7044adb84f08dc89aaec0 /internal/git | |
| download | mugi-567e5b51d32450999935bcf3af143248888e12e9.tar.xz mugi-567e5b51d32450999935bcf3af143248888e12e9.zip | |
feat: Initial commit
Diffstat (limited to 'internal/git')
| -rw-r--r-- | internal/git/git.go | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/internal/git/git.go b/internal/git/git.go new file mode 100644 index 0000000..13e9cb2 --- /dev/null +++ b/internal/git/git.go @@ -0,0 +1,214 @@ +package git + +import ( + "bytes" + "context" + "errors" + "os" + "os/exec" + "strings" + + "github.com/ebisu/mugi/internal/remote" +) + +const sshEnv = "GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=accept-new" + +func gitEnv() []string { + return append(os.Environ(), sshEnv) +} + +type Result struct { + Repo string + Remote string + Output string + Error error + ExitCode int +} + +func (r *Result) setError(err error) { + r.Error = err + + var exitErr *exec.ExitError + + if errors.As(err, &exitErr) { + r.ExitCode = exitErr.ExitCode() + } else { + r.ExitCode = 1 + } + + if r.Output == "" { + r.Output = err.Error() + } +} + +func Execute(ctx context.Context, op remote.Operation, repoPath, remoteName string) Result { + result := Result{ + Repo: repoPath, + Remote: remoteName, + } + + args := buildArgs(op, remoteName, repoPath) + cmd := exec.CommandContext(ctx, "git", args...) + cmd.Dir = repoPath + cmd.Env = gitEnv() + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + result.Output = strings.TrimSpace(stdout.String() + stderr.String()) + + if err != nil { + result.setError(err) + } + + return result +} + +func buildArgs(op remote.Operation, remoteName, repoPath string) []string { + switch op { + case remote.Pull: + branch := currentBranch(repoPath) + if branch == "" { + branch = "HEAD" + } + return []string{"pull", remoteName, branch} + case remote.Push: + return []string{"push", remoteName} + case remote.Fetch: + return []string{"fetch", remoteName} + default: + return []string{} + } +} + +func currentBranch(repoPath string) string { + cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") + cmd.Dir = repoPath + + out, err := cmd.Output() + if err != nil { + return "" + } + + return strings.TrimSpace(string(out)) +} + +func IsRepo(path string) bool { + cmd := exec.Command("git", "rev-parse", "--git-dir") + cmd.Dir = path + + return cmd.Run() == nil +} + +func Clone(ctx context.Context, url, path string) Result { + result := Result{ + Repo: path, + Remote: "origin", + } + + cmd := exec.CommandContext(ctx, "git", "clone", url, path) + cmd.Env = gitEnv() + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + result.Output = strings.TrimSpace(stdout.String() + stderr.String()) + + if err != nil { + result.setError(err) + } + + return result +} + +func AddRemote(ctx context.Context, repoPath, name, url string) Result { + result := Result{ + Repo: repoPath, + Remote: name, + } + + cmd := exec.CommandContext(ctx, "git", "remote", "add", name, url) + cmd.Dir = repoPath + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + result.Output = strings.TrimSpace(stdout.String() + stderr.String()) + + if err != nil { + result.setError(err) + } + + return result +} + +func RenameRemote(ctx context.Context, repoPath, oldName, newName string) Result { + result := Result{ + Repo: repoPath, + Remote: newName, + } + + cmd := exec.CommandContext(ctx, "git", "remote", "rename", oldName, newName) + cmd.Dir = repoPath + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + result.Output = strings.TrimSpace(stdout.String() + stderr.String()) + + if err != nil { + result.setError(err) + } + + return result +} + +func HasRemote(repoPath, name string) bool { + cmd := exec.Command("git", "remote", "get-url", name) + cmd.Dir = repoPath + + return cmd.Run() == nil +} + +func SetRemoteURL(ctx context.Context, repoPath, name, url string) Result { + result := Result{ + Repo: repoPath, + Remote: name, + } + + cmd := exec.CommandContext(ctx, "git", "remote", "set-url", name, url) + cmd.Dir = repoPath + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + result.Output = strings.TrimSpace(stdout.String() + stderr.String()) + + if err != nil { + result.setError(err) + } + + return result +} + +func GetRemoteURL(repoPath, name string) string { + cmd := exec.Command("git", "remote", "get-url", name) + cmd.Dir = repoPath + + out, err := cmd.Output() + if err != nil { + return "" + } + + return strings.TrimSpace(string(out)) +} |