If multiple templates are found, the user is prompted to select one. The templates are searched for, in order of preference: - issues: 1. `.github/ISSUE_TEMPLATE/*.md` 2. `.github/ISSUE_TEMPLATE.md` 3. `ISSUE_TEMPLATE/*.md` 4. `ISSUE_TEMPLATE.md` 5. `docs/ISSUE_TEMPLATE/*.md` 6. `docs/ISSUE_TEMPLATE.md` - pull requests: 1. `.github/PULL_REQUEST_TEMPLATE/*.md` 2. `.github/PULL_REQUEST_TEMPLATE.md` 3. `PULL_REQUEST_TEMPLATE/*.md` 4. `PULL_REQUEST_TEMPLATE.md` 5. `docs/PULL_REQUEST_TEMPLATE/*.md` 6. `docs/PULL_REQUEST_TEMPLATE.md` The filename matches are case-insensitive.
173 lines
3.7 KiB
Go
173 lines
3.7 KiB
Go
package git
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/github/gh-cli/utils"
|
|
)
|
|
|
|
func Dir() (string, error) {
|
|
dirCmd := exec.Command("git", "rev-parse", "-q", "--git-dir")
|
|
output, err := utils.PrepareCmd(dirCmd).Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("Not a git repository (or any of the parent directories): .git")
|
|
}
|
|
|
|
gitDir := firstLine(output)
|
|
if !filepath.IsAbs(gitDir) {
|
|
gitDir, err = filepath.Abs(gitDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
gitDir = filepath.Clean(gitDir)
|
|
}
|
|
|
|
return gitDir, nil
|
|
}
|
|
|
|
func VerifyRef(ref string) bool {
|
|
showRef := exec.Command("git", "show-ref", "--verify", "--quiet", ref)
|
|
err := utils.PrepareCmd(showRef).Run()
|
|
return err == nil
|
|
}
|
|
|
|
func BranchAtRef(paths ...string) (name string, err error) {
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
segments := []string{dir}
|
|
segments = append(segments, paths...)
|
|
path := filepath.Join(segments...)
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
n := string(b)
|
|
refPrefix := "ref: "
|
|
if strings.HasPrefix(n, refPrefix) {
|
|
name = strings.TrimPrefix(n, refPrefix)
|
|
name = strings.TrimSpace(name)
|
|
} else {
|
|
err = fmt.Errorf("No branch info in %s: %s", path, n)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func Head() (string, error) {
|
|
return BranchAtRef("HEAD")
|
|
}
|
|
|
|
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)
|
|
}
|