aboutsummaryrefslogtreecommitdiff
path: root/internal/git
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-26 05:13:25 +0000
committerFuwn <[email protected]>2026-01-26 05:13:25 +0000
commit567e5b51d32450999935bcf3af143248888e12e9 (patch)
tree41bb8b2a8fcae168bcb7044adb84f08dc89aaec0 /internal/git
downloadmugi-567e5b51d32450999935bcf3af143248888e12e9.tar.xz
mugi-567e5b51d32450999935bcf3af143248888e12e9.zip
feat: Initial commit
Diffstat (limited to 'internal/git')
-rw-r--r--internal/git/git.go214
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))
+}