diff options
author | Slack Coder <slackcoder@server.ky> | 2024-04-08 15:29:11 -0500 |
---|---|---|
committer | Slack Coder <slackcoder@server.ky> | 2024-07-18 11:47:49 -0500 |
commit | ae748859be8d6d3ed3c0929770f0c287ab6d6460 (patch) | |
tree | 284d3bf3d7fa496a9d334391eac996affc5a01b9 /internal/service/git.go | |
parent | c2267767ca8ed06018d26a45b483c44b7c4234cf (diff) | |
download | mirror-ae748859be8d6d3ed3c0929770f0c287ab6d6460.tar.xz |
Port to Golang
Diffstat (limited to 'internal/service/git.go')
-rw-r--r-- | internal/service/git.go | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/internal/service/git.go b/internal/service/git.go new file mode 100644 index 0000000..ad3a653 --- /dev/null +++ b/internal/service/git.go @@ -0,0 +1,134 @@ +package service + +import ( + "errors" + "fmt" + "net/url" + "os" + "os/exec" + "path" + "strings" +) + +const gitDescriptionFile = "description" + +func mapExecError(err error) error { + if ee, ok := err.(*exec.ExitError); ok { + return errors.New(string(ee.Stderr)) + } + return err +} + +// Set the description for the projects repository. +func setDescription(repo string, desc string) error { + descPath := path.Join(repo, gitDescriptionFile) + + var curDesc string + buf, err := os.ReadFile(descPath) + if os.IsNotExist(err) { + // empty + } else if err != nil { + return err + } else { + curDesc = string(buf) + } + + if curDesc != desc { + err = os.WriteFile(descPath, []byte(desc), 0750) + if err != nil { + return err + } + } + + return nil +} + +// Set the remote origin's URL for the projects repository. +func setRemoteOrigin(repo string, origin *url.URL) error { + cmd := exec.Command("git", "remote", "get-url", "origin") + cmd.Dir = repo + buf, err := cmd.Output() + if err != nil { + return fmt.Errorf("getting current project remote origin: %w", err) + } + currentOrigin := strings.TrimSpace(string(buf)) + + if currentOrigin != origin.String() { + cmd = exec.Command("git", "remote", "set-url", "origin", origin.String()) + cmd.Dir = repo + err = cmd.Run() + if err != nil { + err = mapExecError(err) + return fmt.Errorf("setting project remote origin: %w", err) + } + } + + return nil +} + +func getRemoteHeadReference(repo string) (string, error) { + cmd := exec.Command("git", "ls-remote", "--symref", "origin", "HEAD") + cmd.Dir = repo + buf, err := cmd.Output() + if err != nil { + return "", mapExecError(err) + } + + for _, l := range strings.Split(string(buf), "\n") { + fields := strings.Fields(l) + if len(fields) != 3 { + return "", errors.New("unexpected output from 'git ls-remote'") + } + if fields[0] == "ref:" { + return strings.TrimPrefix(fields[1], "refs/heads/"), nil + } + } + + return "", errors.New("not found") +} + +func MirrorGit(dst *url.URL, src *url.URL, description string) error { + if dst.Scheme != "" && dst.Scheme != "file://" { + return fmt.Errorf("'%s' scheme not supported", dst.Scheme) + } + + if _, err := os.Stat(dst.Path); os.IsNotExist(err) { + err = os.MkdirAll(path.Join(dst.Path, ".."), 0750) + if err != nil { + return fmt.Errorf("creating new mirror: %w", err) + } + + cmd := exec.Command("git", "clone", "--bare", "--single-branch", src.String(), dst.String()) + cmd.Dir = path.Join(dst.Path, "..") + err := cmd.Run() + if err != nil { + err = mapExecError(err) + return fmt.Errorf("cloning: %s", err) + } + } + + err := setDescription(dst.Path, description) + if err != nil { + return err + } + + err = setRemoteOrigin(dst.Path, src) + if err != nil { + return err + } + + branch, err := getRemoteHeadReference(dst.Path) + if err != nil { + return fmt.Errorf("guessing remote default branch: %w", err) + } + + cmd := exec.Command("git", "fetch", "--tags", "origin", branch+":"+branch) + cmd.Dir = dst.Path + err = cmd.Run() + if err != nil { + err = mapExecError(err) + return fmt.Errorf("fetching project: %s", err) + } + + return nil +} |