github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/builder/remotecontext/git/gitutils_test.go (about) 1 package git // import "github.com/docker/docker/builder/remotecontext/git" 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "testing" 14 15 "github.com/google/go-cmp/cmp" 16 "gotest.tools/v3/assert" 17 is "gotest.tools/v3/assert/cmp" 18 ) 19 20 func TestParseRemoteURL(t *testing.T) { 21 tests := []struct { 22 doc string 23 url string 24 expected gitRepo 25 }{ 26 { 27 doc: "git scheme uppercase, no url-fragment", 28 url: "GIT://github.com/user/repo.git", 29 expected: gitRepo{ 30 remote: "git://github.com/user/repo.git", 31 ref: "master", 32 }, 33 }, 34 { 35 doc: "git scheme, no url-fragment", 36 url: "git://github.com/user/repo.git", 37 expected: gitRepo{ 38 remote: "git://github.com/user/repo.git", 39 ref: "master", 40 }, 41 }, 42 { 43 doc: "git scheme, with url-fragment", 44 url: "git://github.com/user/repo.git#mybranch:mydir/mysubdir/", 45 expected: gitRepo{ 46 remote: "git://github.com/user/repo.git", 47 ref: "mybranch", 48 subdir: "mydir/mysubdir/", 49 }, 50 }, 51 { 52 doc: "https scheme, no url-fragment", 53 url: "https://github.com/user/repo.git", 54 expected: gitRepo{ 55 remote: "https://github.com/user/repo.git", 56 ref: "master", 57 }, 58 }, 59 { 60 doc: "https scheme, with url-fragment", 61 url: "https://github.com/user/repo.git#mybranch:mydir/mysubdir/", 62 expected: gitRepo{ 63 remote: "https://github.com/user/repo.git", 64 ref: "mybranch", 65 subdir: "mydir/mysubdir/", 66 }, 67 }, 68 { 69 doc: "git@, no url-fragment", 70 url: "git@github.com:user/repo.git", 71 expected: gitRepo{ 72 remote: "git@github.com:user/repo.git", 73 ref: "master", 74 }, 75 }, 76 { 77 doc: "git@, with url-fragment", 78 url: "git@github.com:user/repo.git#mybranch:mydir/mysubdir/", 79 expected: gitRepo{ 80 remote: "git@github.com:user/repo.git", 81 ref: "mybranch", 82 subdir: "mydir/mysubdir/", 83 }, 84 }, 85 { 86 doc: "ssh, no url-fragment", 87 url: "ssh://github.com/user/repo.git", 88 expected: gitRepo{ 89 remote: "ssh://github.com/user/repo.git", 90 ref: "master", 91 }, 92 }, 93 { 94 doc: "ssh, with url-fragment", 95 url: "ssh://github.com/user/repo.git#mybranch:mydir/mysubdir/", 96 expected: gitRepo{ 97 remote: "ssh://github.com/user/repo.git", 98 ref: "mybranch", 99 subdir: "mydir/mysubdir/", 100 }, 101 }, 102 { 103 doc: "ssh, with url-fragment and user", 104 url: "ssh://foo%40barcorp.com@github.com/user/repo.git#mybranch:mydir/mysubdir/", 105 expected: gitRepo{ 106 remote: "ssh://foo%40barcorp.com@github.com/user/repo.git", 107 ref: "mybranch", 108 subdir: "mydir/mysubdir/", 109 }, 110 }, 111 } 112 113 for _, tc := range tests { 114 tc := tc 115 t.Run(tc.doc, func(t *testing.T) { 116 repo, err := parseRemoteURL(tc.url) 117 assert.NilError(t, err) 118 assert.Check(t, is.DeepEqual(tc.expected, repo, cmp.AllowUnexported(gitRepo{}))) 119 }) 120 } 121 } 122 123 func TestCloneArgsSmartHttp(t *testing.T) { 124 mux := http.NewServeMux() 125 server := httptest.NewServer(mux) 126 serverURL, _ := url.Parse(server.URL) 127 128 serverURL.Path = "/repo.git" 129 130 mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { 131 q := r.URL.Query().Get("service") 132 w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) 133 }) 134 135 args := fetchArgs(serverURL.String(), "master") 136 exp := []string{"fetch", "--depth", "1", "origin", "--", "master"} 137 assert.Check(t, is.DeepEqual(exp, args)) 138 } 139 140 func TestCloneArgsDumbHttp(t *testing.T) { 141 mux := http.NewServeMux() 142 server := httptest.NewServer(mux) 143 serverURL, _ := url.Parse(server.URL) 144 145 serverURL.Path = "/repo.git" 146 147 mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { 148 w.Header().Set("Content-Type", "text/plain") 149 }) 150 151 args := fetchArgs(serverURL.String(), "master") 152 exp := []string{"fetch", "origin", "--", "master"} 153 assert.Check(t, is.DeepEqual(exp, args)) 154 } 155 156 func TestCloneArgsGit(t *testing.T) { 157 args := fetchArgs("git://github.com/docker/docker", "master") 158 exp := []string{"fetch", "--depth", "1", "origin", "--", "master"} 159 assert.Check(t, is.DeepEqual(exp, args)) 160 } 161 162 func gitGetConfig(name string) string { 163 b, err := git([]string{"config", "--get", name}...) 164 if err != nil { 165 // since we are interested in empty or non empty string, 166 // we can safely ignore the err here. 167 return "" 168 } 169 return strings.TrimSpace(string(b)) 170 } 171 172 func TestCheckoutGit(t *testing.T) { 173 root, err := os.MkdirTemp("", "docker-build-git-checkout") 174 assert.NilError(t, err) 175 defer os.RemoveAll(root) 176 177 autocrlf := gitGetConfig("core.autocrlf") 178 if !(autocrlf == "true" || autocrlf == "false" || 179 autocrlf == "input" || autocrlf == "") { 180 t.Logf("unknown core.autocrlf value: \"%s\"", autocrlf) 181 } 182 eol := "\n" 183 if autocrlf == "true" { 184 eol = "\r\n" 185 } 186 187 gitDir := filepath.Join(root, "repo") 188 _, err = git("init", gitDir) 189 assert.NilError(t, err) 190 191 _, err = gitWithinDir(gitDir, "config", "user.email", "test@docker.com") 192 assert.NilError(t, err) 193 194 _, err = gitWithinDir(gitDir, "config", "user.name", "Docker test") 195 assert.NilError(t, err) 196 197 err = os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0644) 198 assert.NilError(t, err) 199 200 subDir := filepath.Join(gitDir, "subdir") 201 assert.NilError(t, os.Mkdir(subDir, 0755)) 202 203 err = os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0644) 204 assert.NilError(t, err) 205 206 if runtime.GOOS != "windows" { 207 if err = os.Symlink("../subdir", filepath.Join(gitDir, "parentlink")); err != nil { 208 t.Fatal(err) 209 } 210 211 if err = os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink")); err != nil { 212 t.Fatal(err) 213 } 214 } 215 216 _, err = gitWithinDir(gitDir, "add", "-A") 217 assert.NilError(t, err) 218 219 _, err = gitWithinDir(gitDir, "commit", "-am", "First commit") 220 assert.NilError(t, err) 221 222 _, err = gitWithinDir(gitDir, "checkout", "-b", "test") 223 assert.NilError(t, err) 224 225 err = os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0644) 226 assert.NilError(t, err) 227 228 err = os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0644) 229 assert.NilError(t, err) 230 231 _, err = gitWithinDir(gitDir, "add", "-A") 232 assert.NilError(t, err) 233 234 _, err = gitWithinDir(gitDir, "commit", "-am", "Branch commit") 235 assert.NilError(t, err) 236 237 _, err = gitWithinDir(gitDir, "checkout", "master") 238 assert.NilError(t, err) 239 240 // set up submodule 241 subrepoDir := filepath.Join(root, "subrepo") 242 _, err = git("init", subrepoDir) 243 assert.NilError(t, err) 244 245 _, err = gitWithinDir(subrepoDir, "config", "user.email", "test@docker.com") 246 assert.NilError(t, err) 247 248 _, err = gitWithinDir(subrepoDir, "config", "user.name", "Docker test") 249 assert.NilError(t, err) 250 251 err = os.WriteFile(filepath.Join(subrepoDir, "subfile"), []byte("subcontents"), 0644) 252 assert.NilError(t, err) 253 254 _, err = gitWithinDir(subrepoDir, "add", "-A") 255 assert.NilError(t, err) 256 257 _, err = gitWithinDir(subrepoDir, "commit", "-am", "Subrepo initial") 258 assert.NilError(t, err) 259 260 cmd := exec.Command("git", "submodule", "add", subrepoDir, "sub") // this command doesn't work with --work-tree 261 cmd.Dir = gitDir 262 assert.NilError(t, cmd.Run()) 263 264 _, err = gitWithinDir(gitDir, "add", "-A") 265 assert.NilError(t, err) 266 267 _, err = gitWithinDir(gitDir, "commit", "-am", "With submodule") 268 assert.NilError(t, err) 269 270 type singleCase struct { 271 frag string 272 exp string 273 fail bool 274 submodule bool 275 } 276 277 cases := []singleCase{ 278 {"", "FROM scratch", false, true}, 279 {"master", "FROM scratch", false, true}, 280 {":subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false}, 281 {":nosubdir", "", true, false}, // missing directory error 282 {":Dockerfile", "", true, false}, // not a directory error 283 {"master:nosubdir", "", true, false}, 284 {"master:subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false}, 285 {"master:../subdir", "", true, false}, 286 {"test", "FROM scratch" + eol + "EXPOSE 3000", false, false}, 287 {"test:", "FROM scratch" + eol + "EXPOSE 3000", false, false}, 288 {"test:subdir", "FROM busybox" + eol + "EXPOSE 5000", false, false}, 289 } 290 291 if runtime.GOOS != "windows" { 292 // Windows GIT (2.7.1 x64) does not support parentlink/absolutelink. Sample output below 293 // git --work-tree .\repo --git-dir .\repo\.git add -A 294 // error: readlink("absolutelink"): Function not implemented 295 // error: unable to index file absolutelink 296 // fatal: adding files failed 297 cases = append(cases, singleCase{frag: "master:absolutelink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false}) 298 cases = append(cases, singleCase{frag: "master:parentlink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false}) 299 } 300 301 for _, c := range cases { 302 ref, subdir := getRefAndSubdir(c.frag) 303 r, err := cloneGitRepo(gitRepo{remote: gitDir, ref: ref, subdir: subdir}) 304 305 if c.fail { 306 assert.Check(t, is.ErrorContains(err, "")) 307 continue 308 } 309 assert.NilError(t, err) 310 defer os.RemoveAll(r) 311 if c.submodule { 312 b, err := os.ReadFile(filepath.Join(r, "sub/subfile")) 313 assert.NilError(t, err) 314 assert.Check(t, is.Equal("subcontents", string(b))) 315 } else { 316 _, err := os.Stat(filepath.Join(r, "sub/subfile")) 317 assert.Assert(t, is.ErrorContains(err, "")) 318 assert.Assert(t, os.IsNotExist(err)) 319 } 320 321 b, err := os.ReadFile(filepath.Join(r, "Dockerfile")) 322 assert.NilError(t, err) 323 assert.Check(t, is.Equal(c.exp, string(b))) 324 } 325 } 326 327 func TestValidGitTransport(t *testing.T) { 328 gitUrls := []string{ 329 "git://github.com/docker/docker", 330 "git@github.com:docker/docker.git", 331 "git@bitbucket.org:atlassianlabs/atlassian-docker.git", 332 "https://github.com/docker/docker.git", 333 "http://github.com/docker/docker.git", 334 "http://github.com/docker/docker.git#branch", 335 "http://github.com/docker/docker.git#:dir", 336 } 337 incompleteGitUrls := []string{ 338 "github.com/docker/docker", 339 } 340 341 for _, url := range gitUrls { 342 if !isGitTransport(url) { 343 t.Fatalf("%q should be detected as valid Git prefix", url) 344 } 345 } 346 347 for _, url := range incompleteGitUrls { 348 if isGitTransport(url) { 349 t.Fatalf("%q should not be detected as valid Git prefix", url) 350 } 351 } 352 } 353 354 func TestGitInvalidRef(t *testing.T) { 355 gitUrls := []string{ 356 "git://github.com/moby/moby#--foo bar", 357 "git@github.com/moby/moby#--upload-pack=sleep;:", 358 "git@g.com:a/b.git#-B", 359 "git@g.com:a/b.git#with space", 360 } 361 362 for _, url := range gitUrls { 363 _, err := Clone(url) 364 assert.Assert(t, err != nil) 365 assert.Check(t, is.Contains(strings.ToLower(err.Error()), "invalid refspec")) 366 } 367 }