mirror of
https://github.com/gabrie30/ghorg.git
synced 2026-05-17 01:26:10 +02:00
364 lines
8.6 KiB
Go
364 lines
8.6 KiB
Go
package git
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/gabrie30/ghorg/scm"
|
|
)
|
|
|
|
type Gitter interface {
|
|
Clone(scm.Repo) error
|
|
Reset(scm.Repo) error
|
|
Pull(scm.Repo) error
|
|
SetOrigin(scm.Repo) error
|
|
SetOriginWithCredentials(scm.Repo) error
|
|
Clean(scm.Repo) error
|
|
Checkout(scm.Repo) error
|
|
CheckoutBranch(scm.Repo, string) error
|
|
GetCurrentBranch(scm.Repo) (string, error)
|
|
RevListCompare(scm.Repo, string, string) (string, error)
|
|
ShortStatus(scm.Repo) (string, error)
|
|
Branch(scm.Repo) (string, error)
|
|
UpdateRemote(scm.Repo) error
|
|
FetchAll(scm.Repo) error
|
|
FetchCloneBranch(scm.Repo) error
|
|
RepoCommitCount(scm.Repo) (int, error)
|
|
HasRemoteHeads(scm.Repo) (bool, error)
|
|
}
|
|
|
|
type GitClient struct{}
|
|
|
|
func NewGit() GitClient {
|
|
return GitClient{}
|
|
}
|
|
|
|
func printDebugCmd(cmd *exec.Cmd, repo scm.Repo) error {
|
|
fmt.Println("------------- GIT DEBUG -------------")
|
|
fmt.Printf("GHORG_OUTPUT_DIR=%v\n", os.Getenv("GHORG_OUTPUT_DIR"))
|
|
fmt.Printf("GHORG_ABSOLUTE_PATH_TO_CLONE_TO=%v\n", os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"))
|
|
fmt.Print("Repo Data: ")
|
|
spew.Dump(repo)
|
|
fmt.Print("Command Ran: ")
|
|
spew.Dump(*cmd)
|
|
fmt.Println("")
|
|
output, err := cmd.CombinedOutput()
|
|
fmt.Printf("Command Output: %s\n", string(output))
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (g GitClient) HasRemoteHeads(repo scm.Repo) (bool, error) {
|
|
cmd := exec.Command("git", "ls-remote", "--heads", "--quiet", "--exit-code")
|
|
cmd.Dir = repo.HostPath
|
|
|
|
err := cmd.Run()
|
|
if err == nil {
|
|
// successfully listed the remote heads
|
|
return true, nil
|
|
}
|
|
|
|
var exitError *exec.ExitError
|
|
if !errors.As(err, &exitError) {
|
|
// error, but no exit code, return err
|
|
return false, err
|
|
}
|
|
|
|
exitCode := exitError.ExitCode()
|
|
if exitCode == 0 {
|
|
// ls-remote did successfully list the remote heads
|
|
return true, nil
|
|
} else if exitCode == 2 {
|
|
// repository is empty
|
|
return false, nil
|
|
} else {
|
|
// another exit code, simply return err
|
|
return false, err
|
|
}
|
|
|
|
}
|
|
|
|
func cloneGitArgs(repo scm.Repo) []string {
|
|
args := []string{"clone", repo.CloneURL, repo.HostPath}
|
|
|
|
if os.Getenv("GHORG_INCLUDE_SUBMODULES") == "true" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = "--recursive"
|
|
}
|
|
|
|
if os.Getenv("GHORG_CLONE_DEPTH") != "" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = fmt.Sprintf("--depth=%v", os.Getenv("GHORG_CLONE_DEPTH"))
|
|
}
|
|
|
|
if os.Getenv("GHORG_GIT_FILTER") != "" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = fmt.Sprintf("--filter=%v", os.Getenv("GHORG_GIT_FILTER"))
|
|
}
|
|
|
|
if os.Getenv("GHORG_BACKUP") == "true" {
|
|
args = append(args, "--mirror")
|
|
}
|
|
return args
|
|
}
|
|
|
|
// cloneRetryDelays are sleeps before each retry after a failed git clone (three retries, two attempts total).
|
|
var cloneRetryDelays = []time.Duration{1 * time.Second}
|
|
|
|
func (g GitClient) Clone(repo scm.Repo) error {
|
|
args := cloneGitArgs(repo)
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
cmd := exec.Command("git", args...)
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
|
|
maxAttempts := 1 + len(cloneRetryDelays)
|
|
var lastErr error
|
|
for attempt := 0; attempt < maxAttempts; attempt++ {
|
|
if attempt > 0 {
|
|
_ = os.RemoveAll(repo.HostPath)
|
|
time.Sleep(cloneRetryDelays[attempt-1])
|
|
}
|
|
cmd := exec.Command("git", args...)
|
|
lastErr = cmd.Run()
|
|
if lastErr == nil {
|
|
return nil
|
|
}
|
|
}
|
|
return lastErr
|
|
}
|
|
|
|
func (g GitClient) SetOriginWithCredentials(repo scm.Repo) error {
|
|
args := []string{"remote", "set-url", "origin", repo.CloneURL}
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) SetOrigin(repo scm.Repo) error {
|
|
args := []string{"remote", "set-url", "origin", repo.URL}
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) Checkout(repo scm.Repo) error {
|
|
cmd := exec.Command("git", "checkout", repo.CloneBranch)
|
|
cmd.Dir = repo.HostPath
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) CheckoutBranch(repo scm.Repo, branch string) error {
|
|
cmd := exec.Command("git", "checkout", branch)
|
|
cmd.Dir = repo.HostPath
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) GetCurrentBranch(repo scm.Repo) (string, error) {
|
|
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
|
cmd.Dir = repo.HostPath
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
if err := printDebugCmd(cmd, repo); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
func (g GitClient) Clean(repo scm.Repo) error {
|
|
cmd := exec.Command("git", "clean", "-f", "-d")
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) UpdateRemote(repo scm.Repo) error {
|
|
cmd := exec.Command("git", "remote", "update")
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) Pull(repo scm.Repo) error {
|
|
args := []string{"pull", "origin", repo.CloneBranch}
|
|
|
|
if os.Getenv("GHORG_INCLUDE_SUBMODULES") == "true" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = "--recurse-submodules"
|
|
}
|
|
|
|
if os.Getenv("GHORG_CLONE_DEPTH") != "" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = fmt.Sprintf("--depth=%v", os.Getenv("GHORG_CLONE_DEPTH"))
|
|
}
|
|
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) Reset(repo scm.Repo) error {
|
|
cmd := exec.Command("git", "reset", "--hard", "origin/"+repo.CloneBranch)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) FetchAll(repo scm.Repo) error {
|
|
args := []string{"fetch", "--all"}
|
|
|
|
if os.Getenv("GHORG_CLONE_DEPTH") != "" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = fmt.Sprintf("--depth=%v", os.Getenv("GHORG_CLONE_DEPTH"))
|
|
}
|
|
|
|
if os.Getenv("GHORG_FETCH_PRUNE") == "true" {
|
|
args = append(args, "--prune")
|
|
}
|
|
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) Branch(repo scm.Repo) (string, error) {
|
|
args := []string{"branch"}
|
|
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
if err := printDebugCmd(cmd, repo); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
// RevListCompare returns the list of commits in the local branch that are not in the remote branch.
|
|
func (g GitClient) RevListCompare(repo scm.Repo, localBranch string, remoteBranch string) (string, error) {
|
|
cmd := exec.Command("git", "-C", repo.HostPath, "rev-list", localBranch, "^"+remoteBranch)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
func (g GitClient) FetchCloneBranch(repo scm.Repo) error {
|
|
args := []string{"fetch", "origin", repo.CloneBranch}
|
|
|
|
if os.Getenv("GHORG_CLONE_DEPTH") != "" {
|
|
index := 1
|
|
args = append(args[:index+1], args[index:]...)
|
|
args[index] = fmt.Sprintf("--depth=%v", os.Getenv("GHORG_CLONE_DEPTH"))
|
|
}
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
return printDebugCmd(cmd, repo)
|
|
}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (g GitClient) ShortStatus(repo scm.Repo) (string, error) {
|
|
args := []string{"status", "--short"}
|
|
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
if err := printDebugCmd(cmd, repo); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
func (g GitClient) RepoCommitCount(repo scm.Repo) (int, error) {
|
|
args := []string{"rev-list", "--count", repo.CloneBranch, "--"}
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repo.HostPath
|
|
|
|
if os.Getenv("GHORG_DEBUG") != "" {
|
|
err := printDebugCmd(cmd, repo)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
count, err := strconv.Atoi(strings.TrimSpace(string(output)))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|