github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/remotecontext/git/gitutils.go (about) 1 package git // import "github.com/docker/docker/builder/remotecontext/git" 2 3 import ( 4 "io/ioutil" 5 "net/http" 6 "net/url" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 12 "github.com/docker/docker/pkg/symlink" 13 "github.com/docker/docker/pkg/urlutil" 14 "github.com/pkg/errors" 15 ) 16 17 type gitRepo struct { 18 remote string 19 ref string 20 subdir string 21 } 22 23 // Clone clones a repository into a newly created directory which 24 // will be under "docker-build-git" 25 func Clone(remoteURL string) (string, error) { 26 repo, err := parseRemoteURL(remoteURL) 27 28 if err != nil { 29 return "", err 30 } 31 32 return cloneGitRepo(repo) 33 } 34 35 func cloneGitRepo(repo gitRepo) (checkoutDir string, err error) { 36 fetch := fetchArgs(repo.remote, repo.ref) 37 38 root, err := ioutil.TempDir("", "docker-build-git") 39 if err != nil { 40 return "", err 41 } 42 43 defer func() { 44 if err != nil { 45 os.RemoveAll(root) 46 } 47 }() 48 49 if out, err := gitWithinDir(root, "init"); err != nil { 50 return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out) 51 } 52 53 // Add origin remote for compatibility with previous implementation that 54 // used "git clone" and also to make sure local refs are created for branches 55 if out, err := gitWithinDir(root, "remote", "add", "origin", repo.remote); err != nil { 56 return "", errors.Wrapf(err, "failed add origin repo at %s: %s", repo.remote, out) 57 } 58 59 if output, err := gitWithinDir(root, fetch...); err != nil { 60 return "", errors.Wrapf(err, "error fetching: %s", output) 61 } 62 63 checkoutDir, err = checkoutGit(root, repo.ref, repo.subdir) 64 if err != nil { 65 return "", err 66 } 67 68 cmd := exec.Command("git", "submodule", "update", "--init", "--recursive", "--depth=1") 69 cmd.Dir = root 70 output, err := cmd.CombinedOutput() 71 if err != nil { 72 return "", errors.Wrapf(err, "error initializing submodules: %s", output) 73 } 74 75 return checkoutDir, nil 76 } 77 78 func parseRemoteURL(remoteURL string) (gitRepo, error) { 79 repo := gitRepo{} 80 81 if !isGitTransport(remoteURL) { 82 remoteURL = "https://" + remoteURL 83 } 84 85 var fragment string 86 if strings.HasPrefix(remoteURL, "git@") { 87 // git@.. is not an URL, so cannot be parsed as URL 88 parts := strings.SplitN(remoteURL, "#", 2) 89 90 repo.remote = parts[0] 91 if len(parts) == 2 { 92 fragment = parts[1] 93 } 94 repo.ref, repo.subdir = getRefAndSubdir(fragment) 95 } else { 96 u, err := url.Parse(remoteURL) 97 if err != nil { 98 return repo, err 99 } 100 101 repo.ref, repo.subdir = getRefAndSubdir(u.Fragment) 102 u.Fragment = "" 103 repo.remote = u.String() 104 } 105 return repo, nil 106 } 107 108 func getRefAndSubdir(fragment string) (ref string, subdir string) { 109 refAndDir := strings.SplitN(fragment, ":", 2) 110 ref = "master" 111 if len(refAndDir[0]) != 0 { 112 ref = refAndDir[0] 113 } 114 if len(refAndDir) > 1 && len(refAndDir[1]) != 0 { 115 subdir = refAndDir[1] 116 } 117 return 118 } 119 120 func fetchArgs(remoteURL string, ref string) []string { 121 args := []string{"fetch"} 122 123 if supportsShallowClone(remoteURL) { 124 args = append(args, "--depth", "1") 125 } 126 127 return append(args, "origin", ref) 128 } 129 130 // Check if a given git URL supports a shallow git clone, 131 // i.e. it is a non-HTTP server or a smart HTTP server. 132 func supportsShallowClone(remoteURL string) bool { 133 if urlutil.IsURL(remoteURL) { 134 // Check if the HTTP server is smart 135 136 // Smart servers must correctly respond to a query for the git-upload-pack service 137 serviceURL := remoteURL + "/info/refs?service=git-upload-pack" 138 139 // Try a HEAD request and fallback to a Get request on error 140 res, err := http.Head(serviceURL) 141 if err != nil || res.StatusCode != http.StatusOK { 142 res, err = http.Get(serviceURL) 143 if err == nil { 144 res.Body.Close() 145 } 146 if err != nil || res.StatusCode != http.StatusOK { 147 // request failed 148 return false 149 } 150 } 151 152 if res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { 153 // Fallback, not a smart server 154 return false 155 } 156 return true 157 } 158 // Non-HTTP protocols always support shallow clones 159 return true 160 } 161 162 func checkoutGit(root, ref, subdir string) (string, error) { 163 // Try checking out by ref name first. This will work on branches and sets 164 // .git/HEAD to the current branch name 165 if output, err := gitWithinDir(root, "checkout", ref); err != nil { 166 // If checking out by branch name fails check out the last fetched ref 167 if _, err2 := gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil { 168 return "", errors.Wrapf(err, "error checking out %s: %s", ref, output) 169 } 170 } 171 172 if subdir != "" { 173 newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, subdir), root) 174 if err != nil { 175 return "", errors.Wrapf(err, "error setting git context, %q not within git root", subdir) 176 } 177 178 fi, err := os.Stat(newCtx) 179 if err != nil { 180 return "", err 181 } 182 if !fi.IsDir() { 183 return "", errors.Errorf("error setting git context, not a directory: %s", newCtx) 184 } 185 root = newCtx 186 } 187 188 return root, nil 189 } 190 191 func gitWithinDir(dir string, args ...string) ([]byte, error) { 192 a := []string{"--work-tree", dir, "--git-dir", filepath.Join(dir, ".git")} 193 return git(append(a, args...)...) 194 } 195 196 func git(args ...string) ([]byte, error) { 197 return exec.Command("git", args...).CombinedOutput() 198 } 199 200 // isGitTransport returns true if the provided str is a git transport by inspecting 201 // the prefix of the string for known protocols used in git. 202 func isGitTransport(str string) bool { 203 return urlutil.IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@") 204 }