func GetLocalRemotes() (string, error) {
	cmd := New("remote", "-v")
	cmd.Stdout = nil
	remotes, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return string(remotes), nil
}
GetLocalRemotes returns a string of local remote names and URLs
package git
import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	retry "github.com/avast/retry-go"
	gogit "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/pkg/errors"
	gitconfig "github.com/tcnksm/go-gitconfig"
	"github.com/zaquestion/lab/internal/logger"
)
var log = logger.GetInstance()
func New(args ...string) *exec.Cmd {
	gitPath, err := exec.LookPath("git")
	if err != nil {
		log.Fatal(err)
	}
	cmd := exec.Command(gitPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}
func Dir() (string, error) {
	cmd := New("rev-parse", "-q", "--git-dir")
	cmd.Stdout = nil
	cmd.Stderr = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	dir := string(d)
	dir = strings.TrimSpace(dir)
	if !filepath.IsAbs(dir) {
		dir, err = filepath.Abs(dir)
		if err != nil {
			return "", err
		}
	}
	return filepath.Clean(dir), nil
}
func WorkingDir() (string, error) {
	cmd := New("rev-parse", "--show-toplevel")
	cmd.Stdout = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(d)), nil
}
func CommentChar() string {
	char, err := gitconfig.Entire("core.commentchar")
	if err == nil {
		return char
	}
	return "#"
}
func PagerCommand() (string, []string) {
	env := os.Environ()
	if _, ok := os.LookupEnv("LESS"); !ok {
		env = append(env, "LESS=FRX")
	}
	if _, ok := os.LookupEnv("LESSSECURE"); !ok {
		env = append(env, "LESSSECURE=1")
	}
	if _, ok := os.LookupEnv("LV"); !ok {
		env = append(env, "LV=-c")
	}
	cmd, ok := os.LookupEnv("GIT_PAGER")
	if ok {
		return cmd, env
	}
	cmd, err := gitconfig.Entire("core.pager")
	if err == nil {
		return cmd, env
	}
	cmd, ok = os.LookupEnv("PAGER")
	if ok {
		return cmd, env
	}
	return "less", env
}
func LastCommitMessage(sha string) (string, error) {
	cmd := New("show", "-s", "--format=%s%n%+b", sha)
	cmd.Stdout = nil
	msg, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(msg)), nil
}
func Log(sha1, sha2 string) (string, error) {
	cmd := New("-c", "log.showSignature=false",
		"log",
		"--no-color",
		"--format=%h (%aN)%n%w(78,3,3)%s%n",
		"--cherry",
		fmt.Sprintf("%s..%s", sha1, sha2))
	cmd.Stdout = nil
	outputs, err := cmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load git log %s..%s", sha1, sha2)
	}
	diffCmd := New("diff", "--stat", fmt.Sprintf("%s...%s", sha1, sha2))
	diffCmd.Stdout = nil
	diffOutput, err := diffCmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load diffstat")
	}
	return string(outputs) + string(diffOutput), nil
}
func CurrentBranch() (string, error) {
	cmd := New("rev-parse", "--abbrev-ref", "HEAD")
	cmd.Stdout = nil
	branch, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(branch)), nil
}
func UpstreamBranch(branch string) (string, error) {
	upstreamBranch, err := gitconfig.Local("branch." + branch + ".merge")
	if err != nil {
		return "", errors.Errorf("No upstream for branch '%s'", branch)
	}
	return strings.TrimPrefix(upstreamBranch, "refs/heads/"), nil
}
func PathWithNamespace(remote string) (string, error) {
	remoteURL, err := gitconfig.Local("remote." + remote + ".pushurl")
	if err != nil || remoteURL == "" {
		remoteURL, err = gitconfig.Local("remote." + remote + ".url")
		if err != nil {
			return "", err
		}
	}
	parts := strings.Split(remoteURL, "//")
	if len(parts) == 1 {
		part := parts[0]
		parts = strings.Split(part, ":")
	} else if len(parts) == 2 {
		part := parts[1]
		parts = strings.SplitN(part, "/", 2)
	} else {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}
	if len(parts) != 2 {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}
	path := parts[1]
	path = strings.TrimSuffix(path, ".git")
	return path, nil
}
func RepoName() (string, error) {
	o, err := PathWithNamespace("origin")
	if err != nil {
		return "", err
	}
	parts := strings.Split(o, "/")
	return parts[len(parts)-1:][0], nil
}
func RemoteAdd(name, url, dir string) error {
	cmd := New("remote", "add", name, url)
	cmd.Dir = dir
	if err := cmd.Run(); err != nil {
		return err
	}
	fmt.Println("Updating", name)
	err := retry.Do(func() error {
		cmd = New("fetch", name)
		cmd.Dir = dir
		return cmd.Run()
	}, retry.Attempts(3), retry.Delay(time.Second))
	if err != nil {
		return err
	}
	fmt.Println("new remote:", name)
	return nil
}
func Remotes() ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}
	remotes, err := repo.Remotes()
	if err != nil {
		return []string{}, err
	}
	names := make([]string, len(remotes))
	for i, r := range remotes {
		names[i] = r.Config().Name
	}
	return names, nil
}
func RemoteBranches(remote string) ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}
	branches, err := repo.References()  
	if err != nil {
		return []string{}, err
	}
	reg := regexp.MustCompile(`^refs/remotes/[^/]+/`)
	names := []string{}
	branches.ForEach(func(ref *plumbing.Reference) error {
		if ref.Name().IsRemote() && strings.HasPrefix(ref.Name().String(), "refs/remotes/"+remote) {
			names = append(names, reg.ReplaceAllString(ref.Name().String(), ""))
		}
		return nil
	})
	return names, nil
}
func IsRemote(remote string) (bool, error) {
	cmd := New("remote")
	cmd.Stdout = nil
	cmd.Stderr = nil
	remotes, err := cmd.Output()
	if err != nil {
		return false, err
	}
	return bytes.Contains(remotes, []byte(remote+"\n")), nil
}
func InsideGitRepo() bool {
	cmd := New("rev-parse", "--is-inside-work-tree")
	cmd.Stdout = nil
	cmd.Stderr = nil
	out, _ := cmd.CombinedOutput()
	return bytes.Contains(out, []byte("true\n"))
}
func Fetch(remote, commit string) error {
	gitcmd := []string{"fetch", remote, commit}
	cmd := New(gitcmd...)
	cmd.Stdout = nil
	cmd.Stderr = nil
	err := cmd.Run()
	if err != nil {
		return errors.Errorf("Can't fetch git commit %s from remote %s", commit, remote)
	}
	return nil
}
func Show(commit1, commit2 string, reverse bool) {
	gitcmd := []string{"show"}
	if reverse {
		gitcmd = append(gitcmd, "--reverse")
	}
	gitcmd = append(gitcmd, fmt.Sprintf("%s..%s", commit1, commit2))
	New(gitcmd...).Run()
}