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