Also changes so that shallow fetch is performed
even when a specific ref is specified.
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -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 {
|