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 }