github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/git.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "os/exec" 8 "strings" 9 "syscall" 10 11 "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/heroku-go" 12 ) 13 14 import "fmt" 15 import "os" 16 17 var alwaysUseHTTPGit bool 18 19 const ( 20 gitURLSuf = ".git" 21 ) 22 23 func init() { 24 alwaysUseHTTPGit = os.Getenv("HEROKU_HTTP_GIT_ALWAYS") == "1" 25 } 26 27 func gitHost() string { 28 if herokuGitHost := os.Getenv("HEROKU_GIT_HOST"); herokuGitHost != "" { 29 return herokuGitHost 30 } 31 if herokuHost := os.Getenv("HEROKU_HOST"); herokuHost != "" { 32 return herokuHost 33 } 34 return "heroku.com" 35 } 36 37 func httpGitHost() string { 38 if herokuHTTPGitHost := os.Getenv("HEROKU_HTTP_GIT_HOST"); herokuHTTPGitHost != "" { 39 return herokuHTTPGitHost 40 } 41 return "git." + gitHost() 42 } 43 44 func sshGitURLPre() string { 45 return "git@" + gitHost() + ":" 46 } 47 48 func httpGitURLPre() string { 49 return "https://" + httpGitHost() + "/" 50 } 51 52 func gitDescribe(rels []*Release) error { 53 args := []string{"name-rev", "--tags", "--no-undefined", "--always", "--"} 54 for _, r := range rels { 55 if isDeploy(r.Description) { 56 r.Commit = r.Description[len(r.Description)-7:] 57 } 58 if r.Commit != "" { 59 args = append(args, r.Commit) 60 } 61 } 62 out, err := exec.Command("git", args...).Output() 63 names := mapOutput(out, " ", "\n") 64 for _, r := range rels { 65 if name, ok := names[r.Commit]; ok { 66 if strings.HasPrefix(name, "tags/") { 67 name = name[5:] 68 } 69 if strings.HasSuffix(name, "^0") { 70 name = name[:len(name)-2] 71 } 72 r.Commit = name 73 } 74 } 75 return err 76 } 77 78 func isDeploy(s string) bool { 79 return len(s) == len("Deploy 0000000") && strings.HasPrefix(s, "Deploy ") 80 } 81 82 func mapOutput(out []byte, sep, term string) map[string]string { 83 m := make(map[string]string) 84 lines := strings.Split(string(out), term) 85 for _, line := range lines[:len(lines)-1] { // omit trailing "" 86 parts := strings.SplitN(line, sep, 2) 87 if len(parts) == 2 { 88 m[parts[0]] = parts[1] 89 } 90 } 91 return m 92 } 93 94 func gitRemotes() (map[string]string, error) { 95 b, err := exec.Command("git", "remote", "-v").Output() 96 if err != nil { 97 return nil, err 98 } 99 100 return parseGitRemoteOutput(b) 101 } 102 103 func appNameFromGitURL(remote string) string { 104 if !strings.HasSuffix(remote, gitURLSuf) { 105 return "" 106 } 107 108 if strings.HasPrefix(remote, sshGitURLPre()) { 109 return remote[len(sshGitURLPre()) : len(remote)-len(gitURLSuf)] 110 } 111 112 if strings.HasPrefix(remote, httpGitURLPre()) { 113 return remote[len(httpGitURLPre()) : len(remote)-len(gitURLSuf)] 114 } 115 116 return "" 117 } 118 119 func parseGitRemoteOutput(b []byte) (results map[string]string, err error) { 120 s := bufio.NewScanner(bytes.NewBuffer(b)) 121 s.Split(bufio.ScanLines) 122 123 results = make(map[string]string) 124 125 for s.Scan() { 126 by := s.Bytes() 127 f := bytes.Fields(by) 128 if len(f) != 3 || string(f[2]) != "(push)" { 129 // this should have 3 tuples + be a push remote, skip it if not 130 continue 131 } 132 133 if appName := appNameFromGitURL(string(f[1])); appName != "" { 134 results[string(f[0])] = appName 135 } 136 } 137 if err = s.Err(); err != nil { 138 return nil, err 139 } 140 return 141 } 142 143 func gitConfigBool(name string) bool { 144 b, err := exec.Command("git", "config", name).Output() 145 if err != nil { 146 return false 147 } 148 return strings.TrimSpace(string(b)) == "true" 149 } 150 151 func remoteFromGitConfig() string { 152 b, err := exec.Command("git", "config", "heroku.remote").Output() 153 if err != nil { 154 return "" 155 } 156 return strings.TrimSpace(string(b)) 157 } 158 159 var errMultipleHerokuRemotes = errors.New("multiple apps in git remotes") 160 161 func appFromGitRemote(remote string) (string, error) { 162 if remote != "" { 163 b, err := exec.Command("git", "config", "remote."+remote+".url").Output() 164 if err != nil { 165 if isNotFound(err) { 166 wdir, _ := os.Getwd() 167 return "", fmt.Errorf("could not find git remote "+remote+" in %s", wdir) 168 } 169 return "", err 170 } 171 172 out := strings.TrimSpace(string(b)) 173 174 appName := appNameFromGitURL(out) 175 if appName == "" { 176 return "", fmt.Errorf("could not find app name in " + remote + " git remote") 177 } 178 return appName, nil 179 } 180 181 // no remote specified, see if there is a single Heroku app remote 182 remotes, err := gitRemotes() 183 if err != nil { 184 return "", nil // hide this error 185 } 186 if len(remotes) > 1 { 187 return "", errMultipleHerokuRemotes 188 } 189 for _, v := range remotes { 190 return v, nil 191 } 192 return "", fmt.Errorf("no apps in git remotes") 193 } 194 195 func isNotFound(err error) bool { 196 if ee, ok := err.(*exec.ExitError); ok { 197 if ws, ok := ee.ProcessState.Sys().(syscall.WaitStatus); ok { 198 return ws.ExitStatus() == 1 199 } 200 } 201 return false 202 } 203 204 func addGitRemote(app *heroku.OrganizationApp, useHTTPGit bool) { 205 url := app.GitURL 206 207 if alwaysUseHTTPGit || useHTTPGit { 208 url = httpGitURLPre() + app.Name + gitURLSuf 209 } 210 211 exec.Command("git", "remote", "add", "heroku", url).Run() 212 }