`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>
| ... | ... |
@@ -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 |
} |