Browse code

Fix handling of remote "git@" notation

`docker build` accepts remote repositories
using either the `git://` notation, or `git@`.

Docker attempted to parse both as an URL, however,
`git@` is not an URL, but an argument to `git clone`.

Go 1.7 silently ignored this, and managed to
extract the needed information from these
remotes, however, Go 1.8 does a more strict
validation, and invalidated these.

This patch adds a different path for `git@` remotes,
to prevent them from being handled as URL (and
invalidated).

A test is also added, because there were no
tests for handling of `git@` remotes.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2017/06/15 23:29:18
Showing 2 changed files
... ...
@@ -15,18 +15,24 @@ import (
15 15
 	"github.com/pkg/errors"
16 16
 )
17 17
 
18
+type gitRepo struct {
19
+	remote string
20
+	ref    string
21
+	subdir string
22
+}
23
+
18 24
 // Clone clones a repository into a newly created directory which
19 25
 // will be under "docker-build-git"
20 26
 func Clone(remoteURL string) (string, error) {
21
-	if !urlutil.IsGitTransport(remoteURL) {
22
-		remoteURL = "https://" + remoteURL
23
-	}
24
-	root, err := ioutil.TempDir("", "docker-build-git")
27
+	repo, err := parseRemoteURL(remoteURL)
28
+
25 29
 	if err != nil {
26 30
 		return "", err
27 31
 	}
28 32
 
29
-	u, err := url.Parse(remoteURL)
33
+	fetch := fetchArgs(repo.remote, repo.ref)
34
+
35
+	root, err := ioutil.TempDir("", "docker-build-git")
30 36
 	if err != nil {
31 37
 		return "", err
32 38
 	}
... ...
@@ -35,22 +41,47 @@ func Clone(remoteURL string) (string, error) {
35 35
 		return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out)
36 36
 	}
37 37
 
38
-	ref, subdir := getRefAndSubdir(u.Fragment)
39
-	fetch := fetchArgs(u, ref)
40
-
41
-	u.Fragment = ""
42
-
43 38
 	// Add origin remote for compatibility with previous implementation that
44 39
 	// used "git clone" and also to make sure local refs are created for branches
45
-	if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil {
46
-		return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out)
40
+	if out, err := gitWithinDir(root, "remote", "add", "origin", repo.remote); err != nil {
41
+		return "", errors.Wrapf(err, "failed add origin repo at %s: %s", repo.remote, out)
47 42
 	}
48 43
 
49 44
 	if output, err := gitWithinDir(root, fetch...); err != nil {
50 45
 		return "", errors.Wrapf(err, "error fetching: %s", output)
51 46
 	}
52 47
 
53
-	return checkoutGit(root, ref, subdir)
48
+	return checkoutGit(root, repo.ref, repo.subdir)
49
+}
50
+
51
+func parseRemoteURL(remoteURL string) (gitRepo, error) {
52
+	repo := gitRepo{}
53
+
54
+	if !urlutil.IsGitTransport(remoteURL) {
55
+		remoteURL = "https://" + remoteURL
56
+	}
57
+
58
+	var fragment string
59
+	if strings.HasPrefix(remoteURL, "git@") {
60
+		// git@.. is not an URL, so cannot be parsed as URL
61
+		parts := strings.SplitN(remoteURL, "#", 2)
62
+
63
+		repo.remote = parts[0]
64
+		if len(parts) == 2 {
65
+			fragment = parts[1]
66
+		}
67
+		repo.ref, repo.subdir = getRefAndSubdir(fragment)
68
+	} else {
69
+		u, err := url.Parse(remoteURL)
70
+		if err != nil {
71
+			return repo, err
72
+		}
73
+
74
+		repo.ref, repo.subdir = getRefAndSubdir(u.Fragment)
75
+		u.Fragment = ""
76
+		repo.remote = u.String()
77
+	}
78
+	return repo, nil
54 79
 }
55 80
 
56 81
 func getRefAndSubdir(fragment string) (ref string, subdir string) {
... ...
@@ -65,11 +96,11 @@ func getRefAndSubdir(fragment string) (ref string, subdir string) {
65 65
 	return
66 66
 }
67 67
 
68
-func fetchArgs(remoteURL *url.URL, ref string) []string {
68
+func fetchArgs(remoteURL string, ref string) []string {
69 69
 	args := []string{"fetch", "--recurse-submodules=yes"}
70 70
 	shallow := true
71 71
 
72
-	if strings.HasPrefix(remoteURL.Scheme, "http") {
72
+	if urlutil.IsURL(remoteURL) {
73 73
 		res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
74 74
 		if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
75 75
 			shallow = false
... ...
@@ -16,6 +16,38 @@ import (
16 16
 	"github.com/stretchr/testify/require"
17 17
 )
18 18
 
19
+func TestParseRemoteURL(t *testing.T) {
20
+	dir, err := parseRemoteURL("git://github.com/user/repo.git")
21
+	require.NoError(t, err)
22
+	assert.NotEmpty(t, dir)
23
+	assert.Equal(t, gitRepo{"git://github.com/user/repo.git", "master", ""}, dir)
24
+
25
+	dir, err = parseRemoteURL("git://github.com/user/repo.git#mybranch:mydir/mysubdir/")
26
+	require.NoError(t, err)
27
+	assert.NotEmpty(t, dir)
28
+	assert.Equal(t, gitRepo{"git://github.com/user/repo.git", "mybranch", "mydir/mysubdir/"}, dir)
29
+
30
+	dir, err = parseRemoteURL("https://github.com/user/repo.git")
31
+	require.NoError(t, err)
32
+	assert.NotEmpty(t, dir)
33
+	assert.Equal(t, gitRepo{"https://github.com/user/repo.git", "master", ""}, dir)
34
+
35
+	dir, err = parseRemoteURL("https://github.com/user/repo.git#mybranch:mydir/mysubdir/")
36
+	require.NoError(t, err)
37
+	assert.NotEmpty(t, dir)
38
+	assert.Equal(t, gitRepo{"https://github.com/user/repo.git", "mybranch", "mydir/mysubdir/"}, dir)
39
+
40
+	dir, err = parseRemoteURL("git@github.com:user/repo.git")
41
+	require.NoError(t, err)
42
+	assert.NotEmpty(t, dir)
43
+	assert.Equal(t, gitRepo{"git@github.com:user/repo.git", "master", ""}, dir)
44
+
45
+	dir, err = parseRemoteURL("git@github.com:user/repo.git#mybranch:mydir/mysubdir/")
46
+	require.NoError(t, err)
47
+	assert.NotEmpty(t, dir)
48
+	assert.Equal(t, gitRepo{"git@github.com:user/repo.git", "mybranch", "mydir/mysubdir/"}, dir)
49
+}
50
+
19 51
 func TestCloneArgsSmartHttp(t *testing.T) {
20 52
 	mux := http.NewServeMux()
21 53
 	server := httptest.NewServer(mux)
... ...
@@ -28,7 +60,7 @@ func TestCloneArgsSmartHttp(t *testing.T) {
28 28
 		w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q))
29 29
 	})
30 30
 
31
-	args := fetchArgs(serverURL, "master")
31
+	args := fetchArgs(serverURL.String(), "master")
32 32
 	exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"}
33 33
 	assert.Equal(t, exp, args)
34 34
 }
... ...
@@ -44,14 +76,13 @@ func TestCloneArgsDumbHttp(t *testing.T) {
44 44
 		w.Header().Set("Content-Type", "text/plain")
45 45
 	})
46 46
 
47
-	args := fetchArgs(serverURL, "master")
47
+	args := fetchArgs(serverURL.String(), "master")
48 48
 	exp := []string{"fetch", "--recurse-submodules=yes", "origin", "master"}
49 49
 	assert.Equal(t, exp, args)
50 50
 }
51 51
 
52 52
 func TestCloneArgsGit(t *testing.T) {
53
-	u, _ := url.Parse("git://github.com/docker/docker")
54
-	args := fetchArgs(u, "master")
53
+	args := fetchArgs("git://github.com/docker/docker", "master")
55 54
 	exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"}
56 55
 	assert.Equal(t, exp, args)
57 56
 }