Browse code

Allow checking out any ref in gitutils

Also changes so that shallow fetch is performed
even when a specific ref is specified.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2017/04/11 09:11:28
Showing 3 changed files
... ...
@@ -73,17 +73,18 @@ pre-packaged tarball contexts and plain text files.
73 73
 ### Git repositories
74 74
 
75 75
 When the `URL` parameter points to the location of a Git repository, the
76
-repository acts as the build context. The system recursively clones the
77
-repository and its submodules using a `git clone --depth 1 --recursive`
78
-command. This command runs in a temporary directory on your local host. After
79
-the command succeeds, the directory is sent to the Docker daemon as the
80
-context. Local clones give you the ability to access private repositories using
81
-local user credentials, VPN's, and so forth.
76
+repository acts as the build context. The system recursively fetches the
77
+repository and its submodules. The commit history is not preserved. A
78
+repository is first pulled into a temporary directory on your local host. After
79
+the that succeeds, the directory is sent to the Docker daemon as the context.
80
+Local copy gives you the ability to access private repositories using local
81
+user credentials, VPN's, and so forth.
82 82
 
83 83
 Git URLs accept context configuration in their fragment section, separated by a
84 84
 colon `:`.  The first part represents the reference that Git will check out,
85
-this can be either a branch, a tag, or a commit SHA. The second part represents
86
-a subdirectory inside the repository that will be used as a build context.
85
+this can be either a branch, a tag, or a remote reference. The second part
86
+represents a subdirectory inside the repository that will be used as a build
87
+context.
87 88
 
88 89
 For example, run this command to use a directory called `docker` in the branch
89 90
 `container`:
... ...
@@ -100,12 +101,11 @@ Build Syntax Suffix             | Commit Used           | Build Context Used
100 100
 `myrepo.git`                    | `refs/heads/master`   | `/`
101 101
 `myrepo.git#mytag`              | `refs/tags/mytag`     | `/`
102 102
 `myrepo.git#mybranch`           | `refs/heads/mybranch` | `/`
103
-`myrepo.git#abcdef`             | `sha1 = abcdef`       | `/`
103
+`myrepo.git#pull/42/head`       | `refs/pull/42/head`   | `/`
104 104
 `myrepo.git#:myfolder`          | `refs/heads/master`   | `/myfolder`
105 105
 `myrepo.git#master:myfolder`    | `refs/heads/master`   | `/myfolder`
106 106
 `myrepo.git#mytag:myfolder`     | `refs/tags/mytag`     | `/myfolder`
107 107
 `myrepo.git#mybranch:myfolder`  | `refs/heads/mybranch` | `/myfolder`
108
-`myrepo.git#abcdef:myfolder`    | `sha1 = abcdef`       | `/myfolder`
109 108
 
110 109
 
111 110
 ### Tarball contexts
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/docker/docker/pkg/symlink"
14 14
 	"github.com/docker/docker/pkg/urlutil"
15
+	"github.com/pkg/errors"
15 16
 )
16 17
 
17 18
 // Clone clones a repository into a newly created directory which
... ...
@@ -30,21 +31,45 @@ func Clone(remoteURL string) (string, error) {
30 30
 		return "", err
31 31
 	}
32 32
 
33
-	fragment := u.Fragment
34
-	clone := cloneArgs(u, root)
33
+	if out, err := gitWithinDir(root, "init"); err != nil {
34
+		return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out)
35
+	}
36
+
37
+	ref, subdir := getRefAndSubdir(u.Fragment)
38
+	fetch := fetchArgs(u, ref)
39
+
40
+	u.Fragment = ""
41
+
42
+	// Add origin remote for compatibility with previous implementation that
43
+	// used "git clone" and also to make sure local refs are created for branches
44
+	if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil {
45
+		return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out)
46
+	}
35 47
 
36
-	if output, err := git(clone...); err != nil {
37
-		return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
48
+	if output, err := gitWithinDir(root, fetch...); err != nil {
49
+		return "", errors.Wrapf(err, "error fetching: %s", output)
38 50
 	}
39 51
 
40
-	return checkoutGit(fragment, root)
52
+	return checkoutGit(root, ref, subdir)
53
+}
54
+
55
+func getRefAndSubdir(fragment string) (ref string, subdir string) {
56
+	refAndDir := strings.SplitN(fragment, ":", 2)
57
+	ref = "master"
58
+	if len(refAndDir[0]) != 0 {
59
+		ref = refAndDir[0]
60
+	}
61
+	if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
62
+		subdir = refAndDir[1]
63
+	}
64
+	return
41 65
 }
42 66
 
43
-func cloneArgs(remoteURL *url.URL, root string) []string {
44
-	args := []string{"clone", "--recursive"}
45
-	shallow := len(remoteURL.Fragment) == 0
67
+func fetchArgs(remoteURL *url.URL, ref string) []string {
68
+	args := []string{"fetch", "--recurse-submodules=yes"}
69
+	shallow := true
46 70
 
47
-	if shallow && strings.HasPrefix(remoteURL.Scheme, "http") {
71
+	if strings.HasPrefix(remoteURL.Scheme, "http") {
48 72
 		res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
49 73
 		if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
50 74
 			shallow = false
... ...
@@ -55,26 +80,23 @@ func cloneArgs(remoteURL *url.URL, root string) []string {
55 55
 		args = append(args, "--depth", "1")
56 56
 	}
57 57
 
58
-	if remoteURL.Fragment != "" {
59
-		remoteURL.Fragment = ""
60
-	}
61
-
62
-	return append(args, remoteURL.String(), root)
58
+	return append(args, "origin", ref)
63 59
 }
64 60
 
65
-func checkoutGit(fragment, root string) (string, error) {
66
-	refAndDir := strings.SplitN(fragment, ":", 2)
67
-
68
-	if len(refAndDir[0]) != 0 {
69
-		if output, err := gitWithinDir(root, "checkout", refAndDir[0]); err != nil {
70
-			return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
61
+func checkoutGit(root, ref, subdir string) (string, error) {
62
+	// Try checking out by ref name first. This will work on branches and sets
63
+	// .git/HEAD to the current branch name
64
+	if output, err := gitWithinDir(root, "checkout", ref); err != nil {
65
+		// If checking out by branch name fails check out the last fetched ref
66
+		if _, err2 := gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil {
67
+			return "", errors.Wrapf(err, "error checking out %s: %s", ref, output)
71 68
 		}
72 69
 	}
73 70
 
74
-	if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
75
-		newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, refAndDir[1]), root)
71
+	if subdir != "" {
72
+		newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, subdir), root)
76 73
 		if err != nil {
77
-			return "", fmt.Errorf("Error setting git context, %q not within git root: %s", refAndDir[1], err)
74
+			return "", errors.Wrapf(err, "error setting git context, %q not within git root", subdir)
78 75
 		}
79 76
 
80 77
 		fi, err := os.Stat(newCtx)
... ...
@@ -82,7 +104,7 @@ func checkoutGit(fragment, root string) (string, error) {
82 82
 			return "", err
83 83
 		}
84 84
 		if !fi.IsDir() {
85
-			return "", fmt.Errorf("Error setting git context, not a directory: %s", newCtx)
85
+			return "", errors.Errorf("error setting git context, not a directory: %s", newCtx)
86 86
 		}
87 87
 		root = newCtx
88 88
 	}
... ...
@@ -20,15 +20,14 @@ func TestCloneArgsSmartHttp(t *testing.T) {
20 20
 	serverURL, _ := url.Parse(server.URL)
21 21
 
22 22
 	serverURL.Path = "/repo.git"
23
-	gitURL := serverURL.String()
24 23
 
25 24
 	mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
26 25
 		q := r.URL.Query().Get("service")
27 26
 		w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q))
28 27
 	})
29 28
 
30
-	args := cloneArgs(serverURL, "/tmp")
31
-	exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"}
29
+	args := fetchArgs(serverURL, "master")
30
+	exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"}
32 31
 	if !reflect.DeepEqual(args, exp) {
33 32
 		t.Fatalf("Expected %v, got %v", exp, args)
34 33
 	}
... ...
@@ -40,14 +39,13 @@ func TestCloneArgsDumbHttp(t *testing.T) {
40 40
 	serverURL, _ := url.Parse(server.URL)
41 41
 
42 42
 	serverURL.Path = "/repo.git"
43
-	gitURL := serverURL.String()
44 43
 
45 44
 	mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
46 45
 		w.Header().Set("Content-Type", "text/plain")
47 46
 	})
48 47
 
49
-	args := cloneArgs(serverURL, "/tmp")
50
-	exp := []string{"clone", "--recursive", gitURL, "/tmp"}
48
+	args := fetchArgs(serverURL, "master")
49
+	exp := []string{"fetch", "--recurse-submodules=yes", "origin", "master"}
51 50
 	if !reflect.DeepEqual(args, exp) {
52 51
 		t.Fatalf("Expected %v, got %v", exp, args)
53 52
 	}
... ...
@@ -55,17 +53,8 @@ func TestCloneArgsDumbHttp(t *testing.T) {
55 55
 
56 56
 func TestCloneArgsGit(t *testing.T) {
57 57
 	u, _ := url.Parse("git://github.com/docker/docker")
58
-	args := cloneArgs(u, "/tmp")
59
-	exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"}
60
-	if !reflect.DeepEqual(args, exp) {
61
-		t.Fatalf("Expected %v, got %v", exp, args)
62
-	}
63
-}
64
-
65
-func TestCloneArgsStripFragment(t *testing.T) {
66
-	u, _ := url.Parse("git://github.com/docker/docker#test")
67
-	args := cloneArgs(u, "/tmp")
68
-	exp := []string{"clone", "--recursive", "git://github.com/docker/docker", "/tmp"}
58
+	args := fetchArgs(u, "master")
59
+	exp := []string{"fetch", "--recurse-submodules=yes", "--depth", "1", "origin", "master"}
69 60
 	if !reflect.DeepEqual(args, exp) {
70 61
 		t.Fatalf("Expected %v, got %v", exp, args)
71 62
 	}
... ...
@@ -198,7 +187,8 @@ func TestCheckoutGit(t *testing.T) {
198 198
 	}
199 199
 
200 200
 	for _, c := range cases {
201
-		r, err := checkoutGit(c.frag, gitDir)
201
+		ref, subdir := getRefAndSubdir(c.frag)
202
+		r, err := checkoutGit(gitDir, ref, subdir)
202 203
 
203 204
 		fail := err != nil
204 205
 		if fail != c.fail {