package git import ( "errors" "fmt" "os" "os/exec" "strconv" "strings" "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 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") 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 (g GitClient) Clone(repo scm.Repo) error { 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") } cmd := exec.Command("git", args...) if os.Getenv("GHORG_DEBUG") != "" { return printDebugCmd(cmd, repo) } err := cmd.Run() return err } 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) 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")) } 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 }