2013-05-29 22:58:46 +04:00
|
|
|
package git
|
2013-04-22 07:36:21 +04:00
|
|
|
|
|
|
|
import (
|
2013-04-29 00:41:45 +04:00
|
|
|
"fmt"
|
2013-12-28 13:14:07 +04:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2013-04-24 14:00:45 +04:00
|
|
|
"path/filepath"
|
2013-04-22 07:36:21 +04:00
|
|
|
"strings"
|
2014-02-10 20:22:36 +04:00
|
|
|
|
|
|
|
"github.com/github/hub/cmd"
|
2013-04-22 07:36:21 +04:00
|
|
|
)
|
|
|
|
|
2015-01-13 10:07:21 +03:00
|
|
|
var GlobalFlags []string
|
2015-01-13 09:13:08 +03:00
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
func Version() (string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("version")
|
2017-06-26 21:12:18 +03:00
|
|
|
if err == nil {
|
|
|
|
return output[0], nil
|
|
|
|
} else {
|
|
|
|
return "", fmt.Errorf("error running git version: %s", err)
|
2013-05-28 22:05:10 +04:00
|
|
|
}
|
2013-05-28 21:52:27 +04:00
|
|
|
}
|
|
|
|
|
2015-10-31 20:05:37 +03:00
|
|
|
var cachedDir string
|
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
func Dir() (string, error) {
|
2015-10-31 20:05:37 +03:00
|
|
|
if cachedDir != "" {
|
|
|
|
return cachedDir, nil
|
|
|
|
}
|
|
|
|
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("rev-parse", "-q", "--git-dir")
|
2013-04-24 14:00:45 +04:00
|
|
|
if err != nil {
|
2013-12-11 01:10:49 +04:00
|
|
|
return "", fmt.Errorf("Not a git repository (or any of the parent directories): .git")
|
2013-04-24 14:00:45 +04:00
|
|
|
}
|
|
|
|
|
2015-10-31 03:37:42 +03:00
|
|
|
var chdir string
|
|
|
|
for i, flag := range GlobalFlags {
|
2015-10-31 03:52:17 +03:00
|
|
|
if flag == "-C" {
|
2015-10-31 03:37:42 +03:00
|
|
|
dir := GlobalFlags[i+1]
|
2015-10-31 03:52:17 +03:00
|
|
|
if filepath.IsAbs(dir) {
|
2015-10-31 03:37:42 +03:00
|
|
|
chdir = dir
|
|
|
|
} else {
|
|
|
|
chdir = filepath.Join(chdir, dir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-24 09:41:06 +04:00
|
|
|
gitDir := output[0]
|
2015-10-31 03:37:42 +03:00
|
|
|
|
2015-10-31 03:52:17 +03:00
|
|
|
if !filepath.IsAbs(gitDir) {
|
|
|
|
if chdir != "" {
|
2015-10-31 03:37:42 +03:00
|
|
|
gitDir = filepath.Join(chdir, gitDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
gitDir, err = filepath.Abs(gitDir)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
gitDir = filepath.Clean(gitDir)
|
2013-05-24 09:41:06 +04:00
|
|
|
}
|
|
|
|
|
2015-10-31 20:05:37 +03:00
|
|
|
cachedDir = gitDir
|
2013-05-24 09:41:06 +04:00
|
|
|
return gitDir, nil
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
|
|
|
|
2016-02-28 14:51:21 +03:00
|
|
|
func WorkdirName() (string, error) {
|
|
|
|
output, err := gitOutput("rev-parse", "--show-toplevel")
|
|
|
|
if err == nil {
|
2017-06-26 19:12:13 +03:00
|
|
|
if len(output) > 0 {
|
|
|
|
return output[0], nil
|
|
|
|
} else {
|
|
|
|
return "", fmt.Errorf("unable to determine git working directory")
|
|
|
|
}
|
2016-02-28 14:51:21 +03:00
|
|
|
} else {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-28 13:14:07 +04:00
|
|
|
func HasFile(segments ...string) bool {
|
2015-08-06 09:17:44 +03:00
|
|
|
// The blessed way to resolve paths within git dir since Git 2.5.0
|
|
|
|
output, err := gitOutput("rev-parse", "-q", "--git-path", filepath.Join(segments...))
|
2015-09-26 18:58:15 +03:00
|
|
|
if err == nil && output[0] != "--git-path" {
|
2015-08-06 09:17:44 +03:00
|
|
|
if _, err := os.Stat(output[0]); err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback for older git versions
|
2013-12-28 13:14:07 +04:00
|
|
|
dir, err := Dir()
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
s := []string{dir}
|
|
|
|
s = append(s, segments...)
|
|
|
|
path := filepath.Join(s...)
|
|
|
|
if _, err := os.Stat(path); err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2013-12-28 13:49:59 +04:00
|
|
|
func BranchAtRef(paths ...string) (name string, err error) {
|
2013-12-28 13:14:07 +04:00
|
|
|
dir, err := Dir()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
segments := []string{dir}
|
2013-12-28 13:49:59 +04:00
|
|
|
segments = append(segments, paths...)
|
2013-12-28 13:14:07 +04:00
|
|
|
path := filepath.Join(segments...)
|
|
|
|
b, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n := string(b)
|
2013-12-28 13:49:59 +04:00
|
|
|
refPrefix := "ref: "
|
|
|
|
if strings.HasPrefix(n, refPrefix) {
|
|
|
|
name = strings.TrimPrefix(n, refPrefix)
|
2013-12-28 13:14:07 +04:00
|
|
|
name = strings.TrimSpace(name)
|
|
|
|
} else {
|
|
|
|
err = fmt.Errorf("No branch info in %s: %s", path, n)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
func Editor() (string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("var", "GIT_EDITOR")
|
2013-05-24 09:41:06 +04:00
|
|
|
if err != nil {
|
2013-12-11 01:10:49 +04:00
|
|
|
return "", fmt.Errorf("Can't load git var: GIT_EDITOR")
|
2013-05-24 09:41:06 +04:00
|
|
|
}
|
|
|
|
|
2016-08-07 23:40:38 +03:00
|
|
|
return os.ExpandEnv(output[0]), nil
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
|
|
|
|
2013-12-06 22:50:19 +04:00
|
|
|
func Head() (string, error) {
|
2013-12-28 13:14:07 +04:00
|
|
|
return BranchAtRef("HEAD")
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
func SymbolicFullName(name string) (string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("rev-parse", "--symbolic-full-name", name)
|
2013-12-06 22:50:19 +04:00
|
|
|
if err != nil {
|
2013-12-11 01:10:49 +04:00
|
|
|
return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", name)
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return output[0], nil
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
func Ref(ref string) (string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("rev-parse", "-q", ref)
|
2013-05-29 21:15:43 +04:00
|
|
|
if err != nil {
|
2013-12-11 01:10:49 +04:00
|
|
|
return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", ref)
|
2013-05-29 21:15:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return output[0], nil
|
|
|
|
}
|
|
|
|
|
2013-12-02 20:28:47 +04:00
|
|
|
func RefList(a, b string) ([]string, error) {
|
|
|
|
ref := fmt.Sprintf("%s...%s", a, b)
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput("rev-list", "--cherry-pick", "--right-only", "--no-merges", ref)
|
2013-12-02 20:28:47 +04:00
|
|
|
if err != nil {
|
2013-12-06 22:50:19 +04:00
|
|
|
return []string{}, fmt.Errorf("Can't load rev-list for %s", ref)
|
2013-12-02 20:28:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
}
|
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func NewRange(a, b string) (*Range, error) {
|
|
|
|
output, err := gitOutput("rev-parse", "-q", a, b)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Range{output[0], output[1]}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Range struct {
|
|
|
|
A string
|
|
|
|
B string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Range) IsIdentical() bool {
|
|
|
|
return strings.EqualFold(r.A, r.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Range) IsAncestor() bool {
|
|
|
|
cmd := gitCmd("merge-base", "--is-ancestor", r.A, r.B)
|
|
|
|
return cmd.Success()
|
|
|
|
}
|
|
|
|
|
2017-07-31 18:06:56 +03:00
|
|
|
func CommentChar(text string) (string, error) {
|
2014-07-16 02:44:48 +04:00
|
|
|
char, err := Config("core.commentchar")
|
|
|
|
if err != nil {
|
2017-07-31 19:09:20 +03:00
|
|
|
return "#", nil
|
|
|
|
} else if char == "auto" {
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
commentCharCandidates := strings.Split("#;@!$%^&|:", "")
|
|
|
|
candidateLoop:
|
|
|
|
for _, candidate := range commentCharCandidates {
|
|
|
|
for _, line := range lines {
|
|
|
|
if strings.HasPrefix(line, candidate) {
|
|
|
|
continue candidateLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("unable to select a comment character that is not used in the current message")
|
|
|
|
} else {
|
|
|
|
return char, nil
|
2014-07-16 02:44:48 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-02 20:51:45 +04:00
|
|
|
func Show(sha string) (string, error) {
|
2013-12-31 09:01:34 +04:00
|
|
|
cmd := cmd.New("git")
|
2018-01-30 00:40:36 +03:00
|
|
|
cmd.WithArg("-c").WithArg("log.showSignature=false")
|
2014-07-16 02:44:48 +04:00
|
|
|
cmd.WithArg("show").WithArg("-s").WithArg("--format=%s%n%+b").WithArg(sha)
|
2013-12-02 20:51:45 +04:00
|
|
|
|
2014-11-30 23:16:53 +03:00
|
|
|
output, err := cmd.CombinedOutput()
|
2013-12-31 09:01:34 +04:00
|
|
|
output = strings.TrimSpace(output)
|
|
|
|
|
|
|
|
return output, err
|
2013-12-02 20:51:45 +04:00
|
|
|
}
|
|
|
|
|
2013-05-29 22:58:46 +04:00
|
|
|
func Log(sha1, sha2 string) (string, error) {
|
|
|
|
execCmd := cmd.New("git")
|
2016-09-09 02:21:13 +03:00
|
|
|
execCmd.WithArg("-c").WithArg("log.showSignature=false").WithArg("log").WithArg("--no-color")
|
2013-04-29 00:41:45 +04:00
|
|
|
execCmd.WithArg("--format=%h (%aN, %ar)%n%w(78,3,3)%s%n%+b")
|
|
|
|
execCmd.WithArg("--cherry")
|
|
|
|
shaRange := fmt.Sprintf("%s...%s", sha1, sha2)
|
|
|
|
execCmd.WithArg(shaRange)
|
|
|
|
|
2014-11-30 23:16:53 +03:00
|
|
|
outputs, err := execCmd.CombinedOutput()
|
2013-04-29 00:41:45 +04:00
|
|
|
if err != nil {
|
2013-12-11 01:10:49 +04:00
|
|
|
return "", fmt.Errorf("Can't load git log %s..%s", sha1, sha2)
|
2013-04-29 00:41:45 +04:00
|
|
|
}
|
|
|
|
|
2013-05-24 09:41:06 +04:00
|
|
|
return outputs, nil
|
2013-04-29 00:41:45 +04:00
|
|
|
}
|
|
|
|
|
2013-12-28 13:14:07 +04:00
|
|
|
func Remotes() ([]string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
return gitOutput("remote", "-v")
|
2013-12-28 13:14:07 +04:00
|
|
|
}
|
|
|
|
|
2013-06-29 02:15:41 +04:00
|
|
|
func Config(name string) (string, error) {
|
2014-01-06 06:26:41 +04:00
|
|
|
return gitGetConfig(name)
|
|
|
|
}
|
|
|
|
|
2016-07-12 17:30:12 +03:00
|
|
|
func ConfigAll(name string) ([]string, error) {
|
2016-09-12 07:29:53 +03:00
|
|
|
mode := "--get-all"
|
|
|
|
if strings.Contains(name, "*") {
|
|
|
|
mode = "--get-regexp"
|
|
|
|
}
|
|
|
|
|
|
|
|
lines, err := gitOutput(gitConfigCommand([]string{mode, name})...)
|
2016-07-12 17:30:12 +03:00
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("Unknown config %s", name)
|
|
|
|
}
|
|
|
|
return lines, err
|
|
|
|
}
|
|
|
|
|
2014-01-06 06:26:41 +04:00
|
|
|
func GlobalConfig(name string) (string, error) {
|
|
|
|
return gitGetConfig("--global", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetGlobalConfig(name, value string) error {
|
|
|
|
_, err := gitConfig("--global", name, value)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func gitGetConfig(args ...string) (string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
output, err := gitOutput(gitConfigCommand(args)...)
|
2013-06-29 02:15:41 +04:00
|
|
|
if err != nil {
|
2014-01-06 06:26:41 +04:00
|
|
|
return "", fmt.Errorf("Unknown config %s", args[len(args)-1])
|
2013-06-29 02:15:41 +04:00
|
|
|
}
|
|
|
|
|
2015-04-07 00:10:34 +03:00
|
|
|
if len(output) == 0 {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2013-06-29 02:15:41 +04:00
|
|
|
return output[0], nil
|
|
|
|
}
|
|
|
|
|
2014-01-06 06:26:41 +04:00
|
|
|
func gitConfig(args ...string) ([]string, error) {
|
2015-01-13 09:13:23 +03:00
|
|
|
return gitOutput(gitConfigCommand(args)...)
|
2014-01-06 06:49:06 +04:00
|
|
|
}
|
2014-01-06 06:26:41 +04:00
|
|
|
|
2014-01-06 06:49:06 +04:00
|
|
|
func gitConfigCommand(args []string) []string {
|
|
|
|
cmd := []string{"config"}
|
|
|
|
return append(cmd, args...)
|
2014-01-06 06:26:41 +04:00
|
|
|
}
|
|
|
|
|
2013-12-04 19:08:52 +04:00
|
|
|
func Alias(name string) (string, error) {
|
|
|
|
return Config(fmt.Sprintf("alias.%s", name))
|
|
|
|
}
|
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func Run(args ...string) error {
|
|
|
|
cmd := gitCmd(args...)
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
2015-01-13 09:13:08 +03:00
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func Spawn(args ...string) error {
|
|
|
|
cmd := gitCmd(args...)
|
|
|
|
return cmd.Spawn()
|
|
|
|
}
|
2013-06-22 05:02:29 +04:00
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func Quiet(args ...string) bool {
|
|
|
|
cmd := gitCmd(args...)
|
|
|
|
return cmd.Success()
|
2013-06-22 05:02:29 +04:00
|
|
|
}
|
|
|
|
|
2016-01-22 12:47:51 +03:00
|
|
|
func IsGitDir(dir string) bool {
|
|
|
|
cmd := cmd.New("git")
|
2016-01-24 11:57:46 +03:00
|
|
|
cmd.WithArgs("--git-dir="+dir, "rev-parse", "--git-dir")
|
2016-01-22 12:47:51 +03:00
|
|
|
return cmd.Success()
|
|
|
|
}
|
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func LocalBranches() ([]string, error) {
|
|
|
|
lines, err := gitOutput("branch", "--list")
|
|
|
|
if err == nil {
|
|
|
|
for i, line := range lines {
|
|
|
|
lines[i] = strings.TrimPrefix(line, "* ")
|
2016-11-05 09:53:36 +03:00
|
|
|
lines[i] = strings.TrimPrefix(lines[i], " ")
|
2016-09-12 07:29:53 +03:00
|
|
|
}
|
2015-01-13 09:13:08 +03:00
|
|
|
}
|
2016-09-12 07:29:53 +03:00
|
|
|
return lines, err
|
|
|
|
}
|
2015-01-13 09:13:08 +03:00
|
|
|
|
2016-09-12 07:29:53 +03:00
|
|
|
func gitOutput(input ...string) (outputs []string, err error) {
|
|
|
|
cmd := gitCmd(input...)
|
2013-04-22 07:36:21 +04:00
|
|
|
|
2014-11-30 23:16:53 +03:00
|
|
|
out, err := cmd.CombinedOutput()
|
2013-04-30 06:45:57 +04:00
|
|
|
for _, line := range strings.Split(out, "\n") {
|
2016-11-05 09:53:36 +03:00
|
|
|
if strings.TrimSpace(line) != "" {
|
2013-12-02 20:51:45 +04:00
|
|
|
outputs = append(outputs, string(line))
|
|
|
|
}
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
|
|
|
|
2013-06-25 00:43:59 +04:00
|
|
|
return outputs, err
|
2013-04-22 07:36:21 +04:00
|
|
|
}
|
2016-09-12 07:29:53 +03:00
|
|
|
|
|
|
|
func gitCmd(args ...string) *cmd.Cmd {
|
|
|
|
cmd := cmd.New("git")
|
|
|
|
|
|
|
|
for _, v := range GlobalFlags {
|
|
|
|
cmd.WithArg(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, a := range args {
|
|
|
|
cmd.WithArg(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
2016-11-05 09:53:36 +03:00
|
|
|
|
|
|
|
func IsBuiltInGitCommand(command string) bool {
|
2018-12-11 01:33:46 +03:00
|
|
|
helpCommandOutput, err := gitOutput("help", "--no-verbose", "-a")
|
2018-12-11 13:30:24 +03:00
|
|
|
if err != nil {
|
|
|
|
// support git versions that don't recognize --no-verbose
|
|
|
|
helpCommandOutput, err = gitOutput("help", "-a")
|
|
|
|
}
|
2016-11-05 09:53:36 +03:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, helpCommandOutputLine := range helpCommandOutput {
|
|
|
|
if strings.HasPrefix(helpCommandOutputLine, " ") {
|
|
|
|
for _, gitCommand := range strings.Split(helpCommandOutputLine, " ") {
|
|
|
|
if gitCommand == command {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|