package git import ( "bytes" "errors" "fmt" "net/url" "os/exec" "regexp" "strings" "github.com/github/gh-cli/utils" ) func VerifyRef(ref string) bool { showRef := exec.Command("git", "show-ref", "--verify", "--quiet", ref) err := utils.PrepareCmd(showRef).Run() return err == nil } func CurrentBranch() (string, error) { branchCmd := exec.Command("git", "branch", "--show-current") output, err := utils.PrepareCmd(branchCmd).Output() branchName := firstLine(output) if err == nil && branchName == "" { return "", errors.New("git: not on any branch") } return branchName, err } func listRemotes() ([]string, error) { remoteCmd := exec.Command("git", "remote", "-v") output, err := utils.PrepareCmd(remoteCmd).Output() return outputLines(output), err } func Config(name string) (string, error) { configCmd := exec.Command("git", "config", name) output, err := utils.PrepareCmd(configCmd).Output() if err != nil { return "", fmt.Errorf("unknown config key: %s", name) } return firstLine(output), nil } var GitCommand = func(args ...string) *exec.Cmd { return exec.Command("git", args...) } func UncommittedChangeCount() (int, error) { statusCmd := GitCommand("status", "--porcelain") output, err := utils.PrepareCmd(statusCmd).Output() if err != nil { return 0, err } lines := strings.Split(string(output), "\n") count := 0 for _, l := range lines { if l != "" { count++ } } return count, nil } func Push(remote string, ref string) error { pushCmd := GitCommand("push", "--set-upstream", remote, ref) return utils.PrepareCmd(pushCmd).Run() } type BranchConfig struct { RemoteName string RemoteURL *url.URL MergeRef string } // ReadBranchConfig parses the `branch.BRANCH.(remote|merge)` part of git config func ReadBranchConfig(branch string) (cfg BranchConfig) { prefix := regexp.QuoteMeta(fmt.Sprintf("branch.%s.", branch)) configCmd := GitCommand("config", "--get-regexp", fmt.Sprintf("^%s(remote|merge)$", prefix)) output, err := utils.PrepareCmd(configCmd).Output() if err != nil { return } for _, line := range outputLines(output) { parts := strings.SplitN(line, " ", 2) if len(parts) < 2 { continue } keys := strings.Split(parts[0], ".") switch keys[len(keys)-1] { case "remote": if strings.Contains(parts[1], ":") { u, err := ParseURL(parts[1]) if err != nil { continue } cfg.RemoteURL = u } else { cfg.RemoteName = parts[1] } case "merge": cfg.MergeRef = parts[1] } } return } // ToplevelDir returns the top-level directory path of the current repository func ToplevelDir() (string, error) { showCmd := exec.Command("git", "rev-parse", "--show-toplevel") output, err := utils.PrepareCmd(showCmd).Output() return firstLine(output), err } func outputLines(output []byte) []string { lines := strings.TrimSuffix(string(output), "\n") return strings.Split(lines, "\n") } func firstLine(output []byte) string { if i := bytes.IndexAny(output, "\n"); i >= 0 { return string(output)[0:i] } return string(output) }