github.com/engineyard/workflow-cli@v2.21.6+incompatible/pkg/git/git.go (about)

     1  package git
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  var (
    13  	// ErrRemoteNotFound is returned when the remote cannot be found in git
    14  	ErrRemoteNotFound = errors.New("Could not find remote matching app in 'git remote -v'")
    15  	// ErrInvalidRepositoryList is an error returned if git returns unparsible output
    16  	ErrInvalidRepositoryList = errors.New("Invalid output in 'git remote -v'")
    17  )
    18  
    19  // Cmd is a method the exeutes the given git command and returns the output or the error.
    20  type Cmd func(cmd []string) (string, error)
    21  
    22  // remote defines a git remote's name and its url.
    23  type remote struct {
    24  	Name string
    25  	URL  string
    26  }
    27  
    28  // DefaultCmd is an implementation of Cmd that calls git.
    29  func DefaultCmd(cmd []string) (string, error) {
    30  	out, err := exec.Command("git", cmd...).Output()
    31  	if err != nil {
    32  		return string(out), gitError(err.(*exec.ExitError), cmd)
    33  	}
    34  
    35  	return string(out), nil
    36  }
    37  
    38  func gitError(err *exec.ExitError, cmd []string) error {
    39  	msg := fmt.Sprintf("Error when running 'git %s'\n", strings.Join(cmd, " "))
    40  	out := string(err.Stderr)
    41  	if out != "" {
    42  		msg += strings.TrimSpace(out)
    43  	}
    44  
    45  	return errors.New(msg)
    46  }
    47  
    48  // CreateRemote adds a git remote in the current directory.
    49  func CreateRemote(cmd Cmd, host, name, appID string) error {
    50  	_, err := cmd([]string{"remote", "add", name, RepositoryURL(host, appID)})
    51  	return err
    52  }
    53  
    54  // Init creates a new git repository in the local directory.
    55  func Init(cmd Cmd) error {
    56  	_, err := cmd([]string{"init"})
    57  	return err
    58  }
    59  
    60  // DeleteAppRemotes removes all git remotes corresponding to an app in the repository.
    61  func DeleteAppRemotes(cmd Cmd, host, appID string) error {
    62  	names, err := remoteNamesFromAppID(cmd, host, appID)
    63  
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	for _, name := range names {
    69  		if err := DeleteRemote(cmd, name); err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // DeleteRemote removes a remote from the repository
    78  func DeleteRemote(cmd Cmd, name string) error {
    79  	_, err := cmd([]string{"remote", "remove", name})
    80  	return err
    81  }
    82  
    83  // remoteNamesFromAppID returns the git remote names for an app
    84  func remoteNamesFromAppID(cmd Cmd, host, appID string) ([]string, error) {
    85  	remotes, err := getRemotes(cmd)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	var matchedRemotes []string
    91  
    92  	for _, r := range remotes {
    93  		if r.URL == RepositoryURL(host, appID) {
    94  			matchedRemotes = append(matchedRemotes, r.Name)
    95  		}
    96  	}
    97  
    98  	if len(matchedRemotes) == 0 {
    99  		return nil, ErrRemoteNotFound
   100  	}
   101  
   102  	return matchedRemotes, nil
   103  }
   104  
   105  // DetectAppName detects if there is deis remote in git.
   106  func DetectAppName(cmd Cmd, host string) (string, error) {
   107  	remote, err := findRemote(cmd, host)
   108  
   109  	// Don't return an error if remote can't be found, return directory name instead.
   110  	if err != nil {
   111  		dir, err := os.Getwd()
   112  		return strings.ToLower(filepath.Base(dir)), err
   113  	}
   114  
   115  	ss := strings.Split(remote, "/")
   116  	return strings.Split(ss[len(ss)-1], ".")[0], nil
   117  }
   118  
   119  // findRemote finds a remote name the uses a workflow git repository.
   120  func findRemote(cmd Cmd, host string) (string, error) {
   121  	remotes, err := getRemotes(cmd)
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  
   126  	// strip port from controller url and use it to find builder hostname
   127  	builderHost := getBuilderHostname(strings.Split(host, ":")[0])
   128  
   129  	// search for builder hostname in remote url
   130  	for _, r := range remotes {
   131  		if strings.Contains(r.URL, builderHost) {
   132  			return r.URL, nil
   133  		}
   134  	}
   135  
   136  	return "", ErrRemoteNotFound
   137  }
   138  
   139  // RepositoryURL returns the git repository of an app.
   140  func RepositoryURL(host, appID string) string {
   141  	// Strip off any trailing :port number after the host name.
   142  	host = strings.Split(host, ":")[0]
   143  	return fmt.Sprintf("ssh://git@%s:2222/%s.git", getBuilderHostname(host), appID)
   144  }
   145  
   146  // getBuilderHostname derives the builder host name from the controller host name.
   147  func getBuilderHostname(host string) string {
   148  	hostTokens := strings.Split(host, ".")
   149  	hostTokens[0] = fmt.Sprintf("%s-builder", hostTokens[0])
   150  	return strings.Join(hostTokens, ".")
   151  }
   152  
   153  // RemoteURL retrives the url that a git remote is set to.
   154  func RemoteURL(cmd Cmd, name string) (string, error) {
   155  	remotes, err := getRemotes(cmd)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  
   160  	for _, r := range remotes {
   161  		if r.Name == name {
   162  			return r.URL, nil
   163  		}
   164  	}
   165  
   166  	return "", ErrRemoteNotFound
   167  }
   168  
   169  // getRemotes retrives all the git remotes from a repository
   170  func getRemotes(cmd Cmd) ([]remote, error) {
   171  	out, err := cmd([]string{"remote", "-v"})
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	var remotes []remote
   177  
   178  	for _, line := range strings.Split(out, "\n") {
   179  		// git remote -v contains both push and fetch remotes.
   180  		// They're generally identical, and deis only cares about push.
   181  		if strings.HasSuffix(line, "(push)") {
   182  			parts := strings.Split(line, "\t")
   183  			if len(parts) < 2 {
   184  				return remotes, ErrInvalidRepositoryList
   185  			}
   186  
   187  			remotes = append(remotes, remote{Name: parts[0], URL: strings.Split(parts[1], " ")[0]})
   188  		}
   189  	}
   190  
   191  	return remotes, nil
   192  }