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  }