diff --git a/components/engine/builder/remotecontext/git/gitutils.go b/components/engine/builder/remotecontext/git/gitutils.go index fd8250dbc3..b94d462cd5 100644 --- a/components/engine/builder/remotecontext/git/gitutils.go +++ b/components/engine/builder/remotecontext/git/gitutils.go @@ -15,18 +15,24 @@ import ( "github.com/pkg/errors" ) +type gitRepo struct { + remote string + ref string + subdir string +} + // Clone clones a repository into a newly created directory which // will be under "docker-build-git" func Clone(remoteURL string) (string, error) { - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL - } - root, err := ioutil.TempDir("", "docker-build-git") + repo, err := parseRemoteURL(remoteURL) + if err != nil { return "", err } - u, err := url.Parse(remoteURL) + fetch := fetchArgs(repo.remote, repo.ref) + + root, err := ioutil.TempDir("", "docker-build-git") if err != nil { return "", err } @@ -35,22 +41,47 @@ func Clone(remoteURL string) (string, error) { return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out) } - ref, subdir := getRefAndSubdir(u.Fragment) - fetch := fetchArgs(u, ref) - - u.Fragment = "" - // Add origin remote for compatibility with previous implementation that // used "git clone" and also to make sure local refs are created for branches - if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil { - return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out) + if out, err := gitWithinDir(root, "remote", "add", "origin", repo.remote); err != nil { + return "", errors.Wrapf(err, "failed add origin repo at %s: %s", repo.remote, out) } if output, err := gitWithinDir(root, fetch...); err != nil { return "", errors.Wrapf(err, "error fetching: %s", output) } - return checkoutGit(root, ref, subdir) + return checkoutGit(root, repo.ref, repo.subdir) +} + +func parseRemoteURL(remoteURL string) (gitRepo, error) { + repo := gitRepo{} + + if !isGitTransport(remoteURL) { + remoteURL = "https://" + remoteURL + } + + var fragment string + if strings.HasPrefix(remoteURL, "git@") { + // git@.. is not an URL, so cannot be parsed as URL + parts := strings.SplitN(remoteURL, "#", 2) + + repo.remote = parts[0] + if len(parts) == 2 { + fragment = parts[1] + } + repo.ref, repo.subdir = getRefAndSubdir(fragment) + } else { + u, err := url.Parse(remoteURL) + if err != nil { + return repo, err + } + + repo.ref, repo.subdir = getRefAndSubdir(u.Fragment) + u.Fragment = "" + repo.remote = u.String() + } + return repo, nil } func getRefAndSubdir(fragment string) (ref string, subdir string) { @@ -65,11 +96,11 @@ func getRefAndSubdir(fragment string) (ref string, subdir string) { return } -func fetchArgs(remoteURL *url.URL, ref string) []string { +func fetchArgs(remoteURL string, ref string) []string { args := []string{"fetch", "--recurse-submodules=yes"} shallow := true - if strings.HasPrefix(remoteURL.Scheme, "http") { + if urlutil.IsURL(remoteURL) { res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { shallow = false @@ -120,3 +151,9 @@ func gitWithinDir(dir string, args ...string) ([]byte, error) { func git(args ...string) ([]byte, error) { return exec.Command("git", args...).CombinedOutput() } + +// isGitTransport returns true if the provided str is a git transport by inspecting +// the prefix of the string for known protocols used in git. +func isGitTransport(str string) bool { + return urlutil.IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@") +} diff --git a/components/engine/builder/remotecontext/git/gitutils_test.go b/components/engine/builder/remotecontext/git/gitutils_test.go index eb771f8c98..c638a498f2 100644 --- a/components/engine/builder/remotecontext/git/gitutils_test.go +++ b/components/engine/builder/remotecontext/git/gitutils_test.go @@ -16,6 +16,38 @@ import ( "github.com/stretchr/testify/require" ) +func TestParseRemoteURL(t *testing.T) { + dir, err := parseRemoteURL("git://github.com/user/repo.git") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"git://github.com/user/repo.git", "master", ""}, dir) + + dir, err = parseRemoteURL("git://github.com/user/repo.git#mybranch:mydir/mysubdir/") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"git://github.com/user/repo.git", "mybranch", "mydir/mysubdir/"}, dir) + + dir, err = parseRemoteURL("https://github.com/user/repo.git") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"https://github.com/user/repo.git", "master", ""}, dir) + + dir, err = parseRemoteURL("https://github.com/user/repo.git#mybranch:mydir/mysubdir/") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"https://github.com/user/repo.git", "mybranch", "mydir/mysubdir/"}, dir) + + dir, err = parseRemoteURL("git@github.com:user/repo.git") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"git@github.com:user/repo.git", "master", ""}, dir) + + dir, err = parseRemoteURL("git@github.com:user/repo.git#mybranch:mydir/mysubdir/") + require.NoError(t, err) + assert.NotEmpty(t, dir) + assert.Equal(t, gitRepo{"git@github.com:user/repo.git", "mybranch", "mydir/mysubdir/"}, dir) +} + func TestCloneArgsSmartHttp(t *testing.T) { mux := http.NewServeMux() server := httptest.NewServer(mux) @@ -28,7 +60,7 @@ func TestCloneArgsSmartHttp(t *testing.T) { w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) }) - args := fetchArgs(serverURL, "master") + args := fetchArgs(serverURL.String(), "master") exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"} assert.Equal(t, exp, args) } @@ -44,14 +76,13 @@ func TestCloneArgsDumbHttp(t *testing.T) { w.Header().Set("Content-Type", "text/plain") }) - args := fetchArgs(serverURL, "master") + args := fetchArgs(serverURL.String(), "master") exp := []string{"fetch", "--recurse-submodules=yes", "origin", "master"} assert.Equal(t, exp, args) } func TestCloneArgsGit(t *testing.T) { - u, _ := url.Parse("git://github.com/docker/docker") - args := fetchArgs(u, "master") + args := fetchArgs("git://github.com/docker/docker", "master") exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"} assert.Equal(t, exp, args) } @@ -178,3 +209,30 @@ func TestCheckoutGit(t *testing.T) { assert.Equal(t, c.exp, string(b)) } } + +func TestValidGitTransport(t *testing.T) { + gitUrls := []string{ + "git://github.com/docker/docker", + "git@github.com:docker/docker.git", + "git@bitbucket.org:atlassianlabs/atlassian-docker.git", + "https://github.com/docker/docker.git", + "http://github.com/docker/docker.git", + "http://github.com/docker/docker.git#branch", + "http://github.com/docker/docker.git#:dir", + } + incompleteGitUrls := []string{ + "github.com/docker/docker", + } + + for _, url := range gitUrls { + if !isGitTransport(url) { + t.Fatalf("%q should be detected as valid Git prefix", url) + } + } + + for _, url := range incompleteGitUrls { + if isGitTransport(url) { + t.Fatalf("%q should not be detected as valid Git prefix", url) + } + } +} diff --git a/components/engine/pkg/urlutil/urlutil.go b/components/engine/pkg/urlutil/urlutil.go index 44152873b1..cfcd582036 100644 --- a/components/engine/pkg/urlutil/urlutil.go +++ b/components/engine/pkg/urlutil/urlutil.go @@ -29,12 +29,6 @@ func IsGitURL(str string) bool { return checkURL(str, "git") } -// IsGitTransport returns true if the provided str is a git transport by inspecting -// the prefix of the string for known protocols used in git. -func IsGitTransport(str string) bool { - return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@") -} - // IsTransportURL returns true if the provided str is a transport (tcp, tcp+tls, udp, unix) URL. func IsTransportURL(str string) bool { return checkURL(str, "transport") diff --git a/components/engine/pkg/urlutil/urlutil_test.go b/components/engine/pkg/urlutil/urlutil_test.go index d84145a160..e7579f554f 100644 --- a/components/engine/pkg/urlutil/urlutil_test.go +++ b/components/engine/pkg/urlutil/urlutil_test.go @@ -27,20 +27,6 @@ var ( } ) -func TestValidGitTransport(t *testing.T) { - for _, url := range gitUrls { - if !IsGitTransport(url) { - t.Fatalf("%q should be detected as valid Git prefix", url) - } - } - - for _, url := range incompleteGitUrls { - if IsGitTransport(url) { - t.Fatalf("%q should not be detected as valid Git prefix", url) - } - } -} - func TestIsGIT(t *testing.T) { for _, url := range gitUrls { if !IsGitURL(url) {