github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/builder/remotecontext/git/gitutils.go (about) 1 package git 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/url" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 13 "github.com/docker/docker/pkg/symlink" 14 "github.com/docker/docker/pkg/urlutil" 15 "github.com/pkg/errors" 16 ) 17 18 // Clone clones a repository into a newly created directory which 19 // will be under "docker-build-git" 20 func Clone(remoteURL string) (string, error) { 21 if !urlutil.IsGitTransport(remoteURL) { 22 remoteURL = "https://" + remoteURL 23 } 24 root, err := ioutil.TempDir("", "docker-build-git") 25 if err != nil { 26 return "", err 27 } 28 29 u, err := url.Parse(remoteURL) 30 if err != nil { 31 return "", err 32 } 33 34 if out, err := gitWithinDir(root, "init"); err != nil { 35 return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out) 36 } 37 38 ref, subdir := getRefAndSubdir(u.Fragment) 39 fetch := fetchArgs(u, ref) 40 41 u.Fragment = "" 42 43 // Add origin remote for compatibility with previous implementation that 44 // used "git clone" and also to make sure local refs are created for branches 45 if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil { 46 return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out) 47 } 48 49 if output, err := gitWithinDir(root, fetch...); err != nil { 50 return "", errors.Wrapf(err, "error fetching: %s", output) 51 } 52 53 return checkoutGit(root, ref, subdir) 54 } 55 56 func getRefAndSubdir(fragment string) (ref string, subdir string) { 57 refAndDir := strings.SplitN(fragment, ":", 2) 58 ref = "master" 59 if len(refAndDir[0]) != 0 { 60 ref = refAndDir[0] 61 } 62 if len(refAndDir) > 1 && len(refAndDir[1]) != 0 { 63 subdir = refAndDir[1] 64 } 65 return 66 } 67 68 func fetchArgs(remoteURL *url.URL, ref string) []string { 69 args := []string{"fetch", "--recurse-submodules=yes"} 70 shallow := true 71 72 if strings.HasPrefix(remoteURL.Scheme, "http") { 73 res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) 74 if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { 75 shallow = false 76 } 77 } 78 79 if shallow { 80 args = append(args, "--depth", "1") 81 } 82 83 return append(args, "origin", ref) 84 } 85 86 func checkoutGit(root, ref, subdir string) (string, error) { 87 // Try checking out by ref name first. This will work on branches and sets 88 // .git/HEAD to the current branch name 89 if output, err := gitWithinDir(root, "checkout", ref); err != nil { 90 // If checking out by branch name fails check out the last fetched ref 91 if _, err2 := gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil { 92 return "", errors.Wrapf(err, "error checking out %s: %s", ref, output) 93 } 94 } 95 96 if subdir != "" { 97 newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, subdir), root) 98 if err != nil { 99 return "", errors.Wrapf(err, "error setting git context, %q not within git root", subdir) 100 } 101 102 fi, err := os.Stat(newCtx) 103 if err != nil { 104 return "", err 105 } 106 if !fi.IsDir() { 107 return "", errors.Errorf("error setting git context, not a directory: %s", newCtx) 108 } 109 root = newCtx 110 } 111 112 return root, nil 113 } 114 115 func gitWithinDir(dir string, args ...string) ([]byte, error) { 116 a := []string{"--work-tree", dir, "--git-dir", filepath.Join(dir, ".git")} 117 return git(append(a, args...)...) 118 } 119 120 func git(args ...string) ([]byte, error) { 121 return exec.Command("git", args...).CombinedOutput() 122 }