diff --git a/components/engine/api/client/build.go b/components/engine/api/client/build.go index fb022e38d9..800e04ac9b 100644 --- a/components/engine/api/client/build.go +++ b/components/engine/api/client/build.go @@ -96,20 +96,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else { root := cmd.Arg(0) if urlutil.IsGitURL(root) { - remoteURL := cmd.Arg(0) - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL - } - - root, err = ioutil.TempDir("", "docker-build-git") + root, err = utils.GitClone(root) if err != nil { return err } defer os.RemoveAll(root) - - if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } } if _, err := os.Stat(root); err != nil { return err diff --git a/components/engine/builder/job.go b/components/engine/builder/job.go index acffa8b460..0ad488aae8 100644 --- a/components/engine/builder/job.go +++ b/components/engine/builder/job.go @@ -6,7 +6,6 @@ import ( "io" "io/ioutil" "os" - "os/exec" "strings" "sync" @@ -22,6 +21,7 @@ import ( "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" ) // whitelist of commands allowed for a commit/import @@ -106,19 +106,12 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { if buildConfig.RemoteURL == "" { context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { - if !urlutil.IsGitTransport(buildConfig.RemoteURL) { - buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL - } - root, err := ioutil.TempDir("", "docker-build-git") + root, err := utils.GitClone(buildConfig.RemoteURL) if err != nil { return err } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } - c, err := archive.Tar(root, archive.Uncompressed) if err != nil { return err diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index a871162049..26659c8ffa 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/docs/sources/reference/commandline/cli.md @@ -636,12 +636,13 @@ refer to any of the files in the context. For example, your build can use an [*ADD*](/reference/builder/#add) instruction to reference a file in the context. -The `URL` parameter can specify the location of a Git repository; in this -case, the repository is the context. The Git repository is recursively -cloned with its submodules. The system does a fresh `git clone -recursive` -in a temporary directory on your local host. Then, this clone is sent to -the Docker daemon as the context. Local clones give you the ability to -access private repositories using local user credentials, VPN's, and so forth. +The `URL` parameter can specify the location of a Git repository; +the repository acts as the build context. The system recursively clones the repository +and its submodules using a `git clone --depth 1 --recursive` command. +This command runs in a temporary directory on your local host. +After the command succeeds, the directory is sent to the Docker daemon as the context. +Local clones give you the ability to access private repositories using +local user credentials, VPN's, and so forth. Instead of specifying a context, you can pass a single Dockerfile in the `URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`: diff --git a/components/engine/utils/git.go b/components/engine/utils/git.go new file mode 100644 index 0000000000..18e002d184 --- /dev/null +++ b/components/engine/utils/git.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "net/http" + "os/exec" + "strings" + + "github.com/docker/docker/pkg/urlutil" +) + +func GitClone(remoteURL string) (string, error) { + if !urlutil.IsGitTransport(remoteURL) { + remoteURL = "https://" + remoteURL + } + root, err := ioutil.TempDir("", "docker-build-git") + if err != nil { + return "", err + } + + clone := cloneArgs(remoteURL, root) + + if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { + return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + + return root, nil +} + +func cloneArgs(remoteURL, root string) []string { + args := []string{"clone", "--recursive"} + shallow := true + + if strings.HasPrefix(remoteURL, "http") { + 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 + } + } + + if shallow { + args = append(args, "--depth", "1") + } + + return append(args, remoteURL, root) +} diff --git a/components/engine/utils/git_test.go b/components/engine/utils/git_test.go new file mode 100644 index 0000000000..a82841ae11 --- /dev/null +++ b/components/engine/utils/git_test.go @@ -0,0 +1,56 @@ +package utils + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" +) + +func TestCloneArgsSmartHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("service") + w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} + +func TestCloneArgsDumbHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} +func TestCloneArgsGit(t *testing.T) { + args := cloneArgs("git://github.com/docker/docker", "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +}