Skip to content

Commit a330059

Browse files
feat(git): clean up git package and write tests (#227)
1 parent 6b13756 commit a330059

8 files changed

Lines changed: 369 additions & 98 deletions

File tree

cmd/apps.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (d *DeisCmd) AppCreate(id, buildpack, remote string, noRemote bool) error {
4848
}
4949

5050
if !noRemote {
51-
if err = git.CreateRemote(s.Client.ControllerURL.Host, remote, app.ID); err != nil {
51+
if err = git.CreateRemote(git.DefaultCmd, s.Client.ControllerURL.Host, remote, app.ID); err != nil {
5252
if strings.Contains(err.Error(), fmt.Sprintf("fatal: remote %s already exists.", remote)) {
5353
msg := "A git remote with the name %s already exists. To overwrite this remote run:\n"
5454
msg += "deis git:remote --force --remote %s --app %s"
@@ -219,7 +219,7 @@ func (d *DeisCmd) AppDestroy(appID, confirm string) error {
219219
}
220220

221221
if appID == "" {
222-
appID, err = git.DetectAppName(s.Client.ControllerURL.Host)
222+
appID, err = git.DetectAppName(git.DefaultCmd, s.Client.ControllerURL.Host)
223223

224224
if err != nil {
225225
return err

cmd/apps_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func TestRemoteExists(t *testing.T) {
6666

6767
assert.NoErr(t, os.Chdir(dir))
6868

69-
assert.NoErr(t, git.Init())
70-
assert.NoErr(t, git.CreateRemote("localhost", "deis", "appname"))
69+
assert.NoErr(t, git.Init(git.DefaultCmd))
70+
assert.NoErr(t, git.CreateRemote(git.DefaultCmd, "localhost", "deis", "appname"))
7171

7272
var b bytes.Buffer
7373
cmdr := DeisCmd{WOut: &b, ConfigFile: cf}

cmd/git.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ const remoteDeletionMsg = "Git remotes for app %s removed.\n"
1313
func (d *DeisCmd) GitRemote(appID, remote string, force bool) error {
1414
s, appID, err := load(d.ConfigFile, appID)
1515

16-
remoteURL, err := git.RemoteValue(remote)
16+
remoteURL, err := git.RemoteURL(git.DefaultCmd, remote)
1717

1818
if err != nil {
1919
//If git remote doesn't exist, create it without issue
2020
if err == git.ErrRemoteNotFound {
21-
err := git.CreateRemote(s.Client.ControllerURL.Host, remote, appID)
21+
err := git.CreateRemote(git.DefaultCmd, s.Client.ControllerURL.Host, remote, appID)
2222
if err != nil {
2323
return err
2424
}
@@ -29,7 +29,7 @@ func (d *DeisCmd) GitRemote(appID, remote string, force bool) error {
2929
return err
3030
}
3131

32-
expectedURL := git.RemoteURL(s.Client.ControllerURL.Host, appID)
32+
expectedURL := git.RepositoryURL(s.Client.ControllerURL.Host, appID)
3333

3434
if remoteURL == expectedURL {
3535
d.Printf("Remote %s already exists and is correctly configured for app %s.\n", remote, appID)
@@ -38,11 +38,11 @@ func (d *DeisCmd) GitRemote(appID, remote string, force bool) error {
3838

3939
if force {
4040
d.Printf("Deleting git remote %s.\n", remote)
41-
err := git.DeleteRemote(remote)
41+
err := git.DeleteRemote(git.DefaultCmd, remote)
4242
if err != nil {
4343
return err
4444
}
45-
err = git.CreateRemote(s.Client.ControllerURL.Host, remote, appID)
45+
err = git.CreateRemote(git.DefaultCmd, s.Client.ControllerURL.Host, remote, appID)
4646
if err != nil {
4747
return err
4848
}
@@ -65,7 +65,7 @@ func (d *DeisCmd) GitRemove(appID string) error {
6565
return err
6666
}
6767

68-
err = git.DeleteAppRemotes(s.Client.ControllerURL.Host, appID)
68+
err = git.DeleteAppRemotes(git.DefaultCmd, s.Client.ControllerURL.Host, appID)
6969

7070
if err != nil {
7171
return err

cmd/perms.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func permsLoad(cf, appID string, admin bool) (*settings.Settings, string, error)
103103
}
104104

105105
if !admin && appID == "" {
106-
appID, err = git.DetectAppName(s.Client.ControllerURL.Host)
106+
appID, err = git.DetectAppName(git.DefaultCmd, s.Client.ControllerURL.Host)
107107

108108
if err != nil {
109109
return nil, "", err

cmd/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func load(cf string, appID string) (*settings.Settings, string, error) {
4646
}
4747

4848
if appID == "" {
49-
appID, err = git.DetectAppName(s.Client.ControllerURL.Host)
49+
appID, err = git.DetectAppName(git.DefaultCmd, s.Client.ControllerURL.Host)
5050

5151
if err != nil {
5252
return nil, "", err

cmd/utils_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func TestLoad(t *testing.T) {
4040
assert.NoErr(t, err)
4141
assert.Equal(t, appID, filepath.Base(name), "app")
4242

43-
assert.NoErr(t, git.Init())
44-
assert.NoErr(t, git.CreateRemote(host, "deis", "testing"))
43+
assert.NoErr(t, git.Init(git.DefaultCmd))
44+
assert.NoErr(t, git.CreateRemote(git.DefaultCmd, host, "deis", "testing"))
4545

4646
_, appID, err = load(filename, "")
4747
assert.NoErr(t, err)

pkg/git/git.go

Lines changed: 97 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,36 @@ import (
77
"os/exec"
88
"path/filepath"
99
"strings"
10-
"syscall"
1110
)
1211

13-
// ErrRemoteNotFound is returned when the remote cannot be found in git
14-
var ErrRemoteNotFound = errors.New("Could not find remote matching app in 'git remote -v'")
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+
}
1537

1638
func gitError(err *exec.ExitError, cmd []string) error {
17-
msg := fmt.Sprintf("Error when running '%s'\n", strings.Join(cmd, " "))
39+
msg := fmt.Sprintf("Error when running 'git %s'\n", strings.Join(cmd, " "))
1840
out := string(err.Stderr)
1941
if out != "" {
2042
msg += strings.TrimSpace(out)
@@ -24,35 +46,27 @@ func gitError(err *exec.ExitError, cmd []string) error {
2446
}
2547

2648
// CreateRemote adds a git remote in the current directory.
27-
func CreateRemote(host, remote, appID string) error {
28-
cmd := []string{"git", "remote", "add", remote, RemoteURL(host, appID)}
29-
if _, err := exec.Command(cmd[0], cmd[1:]...).Output(); err != nil {
30-
return gitError(err.(*exec.ExitError), cmd)
31-
}
32-
33-
return nil
49+
func CreateRemote(cmd Cmd, host, name, appID string) error {
50+
_, err := cmd([]string{"remote", "add", name, RepositoryURL(host, appID)})
51+
return err
3452
}
3553

3654
// Init creates a new git repository in the local directory.
37-
func Init() error {
38-
cmd := []string{"git", "init"}
39-
if _, err := exec.Command(cmd[0], cmd[1:]...).Output(); err != nil {
40-
return gitError(err.(*exec.ExitError), cmd)
41-
}
42-
43-
return nil
55+
func Init(cmd Cmd) error {
56+
_, err := cmd([]string{"init"})
57+
return err
4458
}
4559

4660
// DeleteAppRemotes removes all git remotes corresponding to an app in the repository.
47-
func DeleteAppRemotes(host, appID string) error {
48-
names, err := remoteNamesFromAppID(host, appID)
61+
func DeleteAppRemotes(cmd Cmd, host, appID string) error {
62+
names, err := remoteNamesFromAppID(cmd, host, appID)
4963

5064
if err != nil {
5165
return err
5266
}
5367

5468
for _, name := range names {
55-
if err := DeleteRemote(name); err != nil {
69+
if err := DeleteRemote(cmd, name); err != nil {
5670
return err
5771
}
5872
}
@@ -61,50 +75,36 @@ func DeleteAppRemotes(host, appID string) error {
6175
}
6276

6377
// DeleteRemote removes a remote from the repository
64-
func DeleteRemote(name string) error {
65-
cmd := []string{"git", "remote", "remove", name}
66-
if _, err := exec.Command(cmd[0], cmd[1:]...).Output(); err != nil {
67-
return gitError(err.(*exec.ExitError), cmd)
68-
}
69-
70-
return nil
78+
func DeleteRemote(cmd Cmd, name string) error {
79+
_, err := cmd([]string{"remote", "remove", name})
80+
return err
7181
}
7282

7383
// remoteNamesFromAppID returns the git remote names for an app
74-
func remoteNamesFromAppID(host, appID string) ([]string, error) {
75-
cmd := []string{"git", "remote", "-v"}
76-
out, err := exec.Command(cmd[0], cmd[1:]...).Output()
77-
84+
func remoteNamesFromAppID(cmd Cmd, host, appID string) ([]string, error) {
85+
remotes, err := getRemotes(cmd)
7886
if err != nil {
79-
return []string{}, gitError(err.(*exec.ExitError), cmd)
87+
return nil, err
8088
}
8189

82-
remotes := []string{}
83-
84-
lines:
85-
for _, line := range strings.Split(string(out), "\n") {
86-
if strings.Contains(line, RemoteURL(host, appID)) {
87-
name := strings.Split(line, "\t")[0]
88-
// git remote -v can show duplicate remotes, so don't add a remote if it already has been added
89-
for _, remote := range remotes {
90-
if remote == name {
91-
continue lines
92-
}
93-
}
94-
remotes = append(remotes, name)
90+
var matchedRemotes []string
91+
92+
for _, r := range remotes {
93+
if r.URL == RepositoryURL(host, appID) {
94+
matchedRemotes = append(matchedRemotes, r.Name)
9595
}
9696
}
9797

98-
if len(remotes) == 0 {
99-
return remotes, ErrRemoteNotFound
98+
if len(matchedRemotes) == 0 {
99+
return nil, ErrRemoteNotFound
100100
}
101101

102-
return remotes, nil
102+
return matchedRemotes, nil
103103
}
104104

105105
// DetectAppName detects if there is deis remote in git.
106-
func DetectAppName(host string) (string, error) {
107-
remote, err := findRemote(host)
106+
func DetectAppName(cmd Cmd, host string) (string, error) {
107+
remote, err := findRemote(cmd, host)
108108

109109
// Don't return an error if remote can't be found, return directory name instead.
110110
if err != nil {
@@ -116,30 +116,28 @@ func DetectAppName(host string) (string, error) {
116116
return strings.Split(ss[len(ss)-1], ".")[0], nil
117117
}
118118

119-
func findRemote(host string) (string, error) {
120-
cmd := []string{"git", "remote", "-v"}
121-
out, err := exec.Command(cmd[0], cmd[1:]...).Output()
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)
122122
if err != nil {
123-
return "", gitError(err.(*exec.ExitError), cmd)
123+
return "", err
124124
}
125125

126-
// Strip off any trailing :port number after the host name.
127-
host = strings.Split(host, ":")[0]
128-
builderHost := getBuilderHostname(host)
126+
// strip port from controller url and use it to find builder hostname
127+
builderHost := getBuilderHostname(strings.Split(host, ":")[0])
129128

130-
for _, line := range strings.Split(string(out), "\n") {
131-
for _, remote := range strings.Split(line, " ") {
132-
if strings.Contains(remote, host) || strings.Contains(remote, builderHost) {
133-
return strings.Split(remote, "\t")[1], nil
134-
}
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
135133
}
136134
}
137135

138136
return "", ErrRemoteNotFound
139137
}
140138

141-
// RemoteURL returns the git URL of app.
142-
func RemoteURL(host, appID string) string {
139+
// RepositoryURL returns the git repository of an app.
140+
func RepositoryURL(host, appID string) string {
143141
// Strip off any trailing :port number after the host name.
144142
host = strings.Split(host, ":")[0]
145143
return fmt.Sprintf("ssh://git@%s:2222/%s.git", getBuilderHostname(host), appID)
@@ -152,18 +150,43 @@ func getBuilderHostname(host string) string {
152150
return strings.Join(hostTokens, ".")
153151
}
154152

155-
// RemoteValue gets the url that a git remote is set to.
156-
func RemoteValue(name string) (string, error) {
157-
cmd := []string{"git", "remote", "get-url", name}
158-
out, err := exec.Command(cmd[0], cmd[1:]...).Output()
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+
}
159165

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"})
160172
if err != nil {
161-
// get the return code of the program and see if it equals not found
162-
if err.(*exec.ExitError).Sys().(syscall.WaitStatus).ExitStatus() == 128 {
163-
return "", ErrRemoteNotFound
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]})
164188
}
165-
return "", gitError(err.(*exec.ExitError), cmd)
166189
}
167190

168-
return strings.Trim(string(out), "\n"), nil
191+
return remotes, nil
169192
}

0 commit comments

Comments
 (0)