github.com/ianfoo/lab@v0.9.5-0.20180123060006-5ed79f2ccfc7/internal/git/git.go (about)

     1  package git
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/tcnksm/go-gitconfig"
    14  )
    15  
    16  // IsHub is true when using "hub" as the git binary
    17  var IsHub bool
    18  
    19  func init() {
    20  	_, err := exec.LookPath("hub")
    21  	if err == nil {
    22  		IsHub = true
    23  	}
    24  }
    25  
    26  // New looks up the hub or git binary and returns a cmd which outputs to stdout
    27  func New(args ...string) *exec.Cmd {
    28  	gitPath, err := exec.LookPath("hub")
    29  	if err != nil {
    30  		gitPath, err = exec.LookPath("git")
    31  		if err != nil {
    32  			log.Fatal(err)
    33  		}
    34  	}
    35  
    36  	cmd := exec.Command(gitPath, args...)
    37  	cmd.Stdin = os.Stdin
    38  	cmd.Stdout = os.Stdout
    39  	cmd.Stderr = os.Stderr
    40  	return cmd
    41  }
    42  
    43  // GitDir returns the full path to the .git directory
    44  func GitDir() (string, error) {
    45  	cmd := New("rev-parse", "-q", "--git-dir")
    46  	cmd.Stdout = nil
    47  	d, err := cmd.Output()
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  	dir := string(d)
    52  	dir = strings.TrimSpace(dir)
    53  	if !filepath.IsAbs(dir) {
    54  		dir, err = filepath.Abs(dir)
    55  		if err != nil {
    56  			return "", err
    57  		}
    58  	}
    59  
    60  	return filepath.Clean(dir), nil
    61  }
    62  
    63  // WorkingDir returns the full pall to the root of the current git repository
    64  func WorkingDir() (string, error) {
    65  	cmd := New("rev-parse", "--show-toplevel")
    66  	cmd.Stdout = nil
    67  	d, err := cmd.Output()
    68  	if err != nil {
    69  		return "", err
    70  	}
    71  	return strings.TrimSpace(string(d)), nil
    72  }
    73  
    74  // CommentChar returns active comment char and defaults to '#'
    75  func CommentChar() string {
    76  	char, err := gitconfig.Entire("core.commentchar")
    77  	if err == nil {
    78  		return char
    79  	}
    80  
    81  	return "#"
    82  }
    83  
    84  // LastCommitMessage returns the last commits message as one line
    85  func LastCommitMessage() (string, error) {
    86  	cmd := New("show", "-s", "--format=%s%n%+b", "HEAD")
    87  	cmd.Stdout = nil
    88  	msg, err := cmd.Output()
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	return strings.TrimSpace(string(msg)), nil
    93  }
    94  
    95  // Log produces a a formatted gitlog between 2 git shas
    96  func Log(sha1, sha2 string) (string, error) {
    97  	cmd := New("-c", "log.showSignature=false",
    98  		"log",
    99  		"--no-color",
   100  		"--format=%h (%aN, %ar)%n%w(78,3,3)%s%n",
   101  		"--cherry",
   102  		fmt.Sprintf("%s...%s", sha1, sha2))
   103  	cmd.Stdout = nil
   104  	outputs, err := cmd.Output()
   105  	if err != nil {
   106  		return "", errors.Errorf("Can't load git log %s..%s", sha1, sha2)
   107  	}
   108  
   109  	return string(outputs), nil
   110  }
   111  
   112  // CurrentBranch returns the currently checked out branch and strips away all
   113  // but the branchname itself.
   114  func CurrentBranch() (string, error) {
   115  	cmd := New("branch")
   116  	cmd.Stdout = nil
   117  	gBranches, err := cmd.Output()
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  	branches := strings.Split(string(gBranches), "\n")
   122  	var branch string
   123  	for _, b := range branches {
   124  		if strings.HasPrefix(b, "* ") {
   125  			branch = b
   126  			break
   127  		}
   128  	}
   129  	if branch == "" {
   130  		return "", errors.New("current branch could not be determined")
   131  	}
   132  	branch = strings.TrimPrefix(branch, "* ")
   133  	branch = strings.TrimSpace(branch)
   134  	return branch, nil
   135  }
   136  
   137  // PathWithNameSpace returns the owner/repository for the current repo
   138  // Such as zaquestion/lab
   139  func PathWithNameSpace(remote string) (string, error) {
   140  	remoteURL, err := gitconfig.Local("remote." + remote + ".url")
   141  	if err != nil {
   142  		return "", err
   143  	}
   144  	parts := strings.Split(remoteURL, ":")
   145  	if len(parts) == 0 {
   146  		return "", errors.New("remote." + remote + ".url missing repository")
   147  	}
   148  	return strings.TrimSuffix(parts[len(parts)-1:][0], ".git"), nil
   149  }
   150  
   151  // RepoName returns the name of the repository, such as "lab"
   152  func RepoName() (string, error) {
   153  	o, err := PathWithNameSpace("origin")
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	parts := strings.Split(o, "/")
   158  	return parts[len(parts)-1:][0], nil
   159  }
   160  
   161  // RemoteAdd both adds a remote and fetches it
   162  func RemoteAdd(name, url, dir string) error {
   163  	cmd := New("remote", "add", name, url)
   164  	cmd.Dir = dir
   165  	if err := cmd.Run(); err != nil {
   166  		return err
   167  	}
   168  	fmt.Println("Updating", name)
   169  	cmd = New("fetch", name)
   170  	cmd.Dir = dir
   171  	if err := cmd.Run(); err != nil {
   172  		return err
   173  	}
   174  	fmt.Println("new remote:", name)
   175  	return nil
   176  }
   177  
   178  // IsRemote returns true when passed a valid remote in the git repo
   179  func IsRemote(remote string) (bool, error) {
   180  	cmd := New("remote")
   181  	cmd.Stdout = nil
   182  	remotes, err := cmd.Output()
   183  	if err != nil {
   184  		return false, err
   185  	}
   186  
   187  	return bytes.Contains(remotes, []byte(remote+"\n")), nil
   188  }
   189  
   190  // InsideGitRepo returns true when the current working directory is inside the
   191  // working tree of a git repo
   192  func InsideGitRepo() bool {
   193  	cmd := New("rev-parse", "--is-inside-work-tree")
   194  	cmd.Stdout = nil
   195  	cmd.Stderr = nil
   196  	out, _ := cmd.CombinedOutput()
   197  	return bytes.Contains(out, []byte("true\n"))
   198  }