Merge pull request #12502 from calavera/shallow_git_history

Shallow clone using git to build images.
This commit is contained in:
Alexander Morozov 2015-04-24 10:16:01 -07:00
commit 790c63a5ef
5 changed files with 113 additions and 25 deletions

View file

@ -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

View file

@ -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

View file

@ -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`:

47
utils/git.go Normal file
View file

@ -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)
}

56
utils/git_test.go Normal file
View file

@ -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)
}
}