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 }