diff options
Diffstat (limited to 'data/datasource_git_test.go')
| -rw-r--r-- | data/datasource_git_test.go | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/data/datasource_git_test.go b/data/datasource_git_test.go new file mode 100644 index 00000000..b6078473 --- /dev/null +++ b/data/datasource_git_test.go @@ -0,0 +1,471 @@ +package data + +import ( + "context" + "encoding/base64" + "fmt" + "io/ioutil" + "net/url" + "os" + "strings" + "testing" + "time" + + "gopkg.in/src-d/go-billy.v4" + "gopkg.in/src-d/go-billy.v4/memfs" + "gopkg.in/src-d/go-billy.v4/osfs" + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/cache" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/transport/client" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "gopkg.in/src-d/go-git.v4/plumbing/transport/server" + "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" + "gopkg.in/src-d/go-git.v4/storage/filesystem" + + "golang.org/x/crypto/ssh/testdata" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestParseArgPath(t *testing.T) { + t.Parallel() + g := gitsource{} + + data := []struct { + url string + arg string + repo, path string + }{ + {"git+file:///foo//foo", + "/bar", + "", "/bar"}, + {"git+file:///foo//bar", + "/baz//qux", + "", "/baz//qux"}, + {"git+https://example.com/foo", + "/bar", + "/bar", ""}, + {"git+https://example.com/foo", + "//bar", + "", "//bar"}, + {"git+https://example.com/foo//bar", + "//baz", + "", "//baz"}, + {"git+https://example.com/foo", + "/bar//baz", + "/bar", "/baz"}, + {"git+https://example.com/foo?type=t", + "/bar//baz", + "/bar", "/baz"}, + {"git+https://example.com/foo#master", + "/bar//baz", + "/bar", "/baz"}, + {"git+https://example.com/foo", + "//bar", + "", "//bar"}, + {"git+https://example.com/foo?type=t", + "//baz", + "", "//baz"}, + {"git+https://example.com/foo?type=t#v1", + "//bar", + "", "//bar"}, + } + + for i, d := range data { + t.Run(fmt.Sprintf("%d:(%q,%q)==(%q,%q)", i, d.url, d.arg, d.repo, d.path), func(t *testing.T) { + t.Parallel() + u, _ := url.Parse(d.url) + repo, path := g.parseArgPath(u, d.arg) + assert.Equal(t, d.repo, repo) + assert.Equal(t, d.path, path) + }) + } +} + +func TestParseGitPath(t *testing.T) { + t.Parallel() + g := gitsource{} + _, _, err := g.parseGitPath(nil) + assert.ErrorContains(t, err, "") + + u := mustParseURL("http://example.com//foo") + assert.Equal(t, "//foo", u.Path) + parts := strings.SplitN(u.Path, "//", 2) + assert.Equal(t, 2, len(parts)) + assert.DeepEqual(t, []string{"", "foo"}, parts) + + data := []struct { + url string + args []string + repo, path string + }{ + {"git+https://github.com/hairyhenderson/gomplate//docs-src/content/functions/aws.yml", + nil, + "git+https://github.com/hairyhenderson/gomplate", + "/docs-src/content/functions/aws.yml"}, + {"git+ssh://github.com/hairyhenderson/gomplate.git", + nil, + "git+ssh://github.com/hairyhenderson/gomplate.git", + "/"}, + {"https://github.com", + nil, + "https://github.com", + "/"}, + {"git://example.com/foo//file.txt#someref", + nil, + "git://example.com/foo#someref", "/file.txt"}, + {"git+file:///home/foo/repo//file.txt#someref", + nil, + "git+file:///home/foo/repo#someref", "/file.txt"}, + {"git+file:///repo", + nil, + "git+file:///repo", "/"}, + {"git+file:///foo//foo", + nil, + "git+file:///foo", "/foo"}, + {"git+file:///foo//foo", + []string{"/bar"}, + "git+file:///foo", "/foo/bar"}, + {"git+file:///foo//bar", + // in this case the // is meaningless + []string{"/baz//qux"}, + "git+file:///foo", "/bar/baz/qux"}, + {"git+https://example.com/foo", + []string{"/bar"}, + "git+https://example.com/foo/bar", "/"}, + {"git+https://example.com/foo", + []string{"//bar"}, + "git+https://example.com/foo", "/bar"}, + {"git+https://example.com//foo", + []string{"/bar"}, + "git+https://example.com", "/foo/bar"}, + {"git+https://example.com/foo//bar", + []string{"//baz"}, + "git+https://example.com/foo", "/bar/baz"}, + {"git+https://example.com/foo", + []string{"/bar//baz"}, + "git+https://example.com/foo/bar", "/baz"}, + {"git+https://example.com/foo?type=t", + []string{"/bar//baz"}, + "git+https://example.com/foo/bar?type=t", "/baz"}, + {"git+https://example.com/foo#master", + []string{"/bar//baz"}, + "git+https://example.com/foo/bar#master", "/baz"}, + {"git+https://example.com/foo", + []string{"/bar//baz?type=t"}, + "git+https://example.com/foo/bar?type=t", "/baz"}, + {"git+https://example.com/foo", + []string{"/bar//baz#master"}, + "git+https://example.com/foo/bar#master", "/baz"}, + {"git+https://example.com/foo", + []string{"//bar?type=t"}, + "git+https://example.com/foo?type=t", "/bar"}, + {"git+https://example.com/foo", + []string{"//bar#master"}, + "git+https://example.com/foo#master", "/bar"}, + {"git+https://example.com/foo?type=t", + []string{"//bar#master"}, + "git+https://example.com/foo?type=t#master", "/bar"}, + {"git+https://example.com/foo?type=t#v1", + []string{"//bar?type=j#v2"}, + "git+https://example.com/foo?type=t&type=j#v2", "/bar"}, + } + + for i, d := range data { + t.Run(fmt.Sprintf("%d:(%q,%q)==(%q,%q)", i, d.url, d.args, d.repo, d.path), func(t *testing.T) { + t.Parallel() + u, _ := url.Parse(d.url) + repo, path, err := g.parseGitPath(u, d.args...) + assert.NilError(t, err) + assert.Equal(t, d.repo, repo.String()) + assert.Equal(t, d.path, path) + }) + } +} + +func TestReadGitRepo(t *testing.T) { + g := gitsource{} + fs := setupGitRepo(t) + fs, err := fs.Chroot("/repo") + assert.NilError(t, err) + + _, _, err = g.read(fs, "/bogus") + assert.ErrorContains(t, err, "can't stat /bogus") + + mtype, out, err := g.read(fs, "/foo") + assert.NilError(t, err) + assert.Equal(t, `["bar"]`, string(out)) + assert.Equal(t, jsonArrayMimetype, mtype) + + mtype, out, err = g.read(fs, "/foo/bar") + assert.NilError(t, err) + assert.Equal(t, `["hi.txt"]`, string(out)) + assert.Equal(t, jsonArrayMimetype, mtype) + + mtype, out, err = g.read(fs, "/foo/bar/hi.txt") + assert.NilError(t, err) + assert.Equal(t, `hello world`, string(out)) + assert.Equal(t, "", mtype) +} + +var testHashes = map[string]string{} + +func setupGitRepo(t *testing.T) billy.Filesystem { + fs := memfs.New() + fs.MkdirAll("/repo/.git", os.ModeDir) + repo, _ := fs.Chroot("/repo") + dot, _ := repo.Chroot("/.git") + s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) + + r, err := git.Init(s, repo) + assert.NilError(t, err) + + // config needs to be created after setting up a "normal" fs repo + // this is possibly a bug in src-d/git-go? + c, err := r.Config() + assert.NilError(t, err) + s.SetConfig(c) + assert.NilError(t, err) + + w, err := r.Worktree() + assert.NilError(t, err) + + repo.MkdirAll("/foo/bar", os.ModeDir) + f, err := repo.Create("/foo/bar/hi.txt") + assert.NilError(t, err) + _, err = f.Write([]byte("hello world")) + assert.NilError(t, err) + _, err = w.Add(f.Name()) + assert.NilError(t, err) + hash, err := w.Commit("initial commit", &git.CommitOptions{Author: &object.Signature{}}) + assert.NilError(t, err) + + ref, err := r.CreateTag("v1", hash, nil) + assert.NilError(t, err) + testHashes["v1"] = hash.String() + + branchName := plumbing.NewBranchReferenceName("mybranch") + err = w.Checkout(&git.CheckoutOptions{ + Branch: branchName, + Hash: ref.Hash(), + Create: true, + }) + assert.NilError(t, err) + + f, err = repo.Create("/secondfile.txt") + assert.NilError(t, err) + _, err = f.Write([]byte("another file\n")) + assert.NilError(t, err) + n := f.Name() + _, err = w.Add(n) + assert.NilError(t, err) + hash, err = w.Commit("second commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + }, + }) + ref = plumbing.NewHashReference(branchName, hash) + assert.NilError(t, err) + testHashes["mybranch"] = ref.Hash().String() + + // make the repo dirty + _, err = f.Write([]byte("dirty file")) + assert.NilError(t, err) + + // set up a bare repo + fs.MkdirAll("/bare.git", os.ModeDir) + fs.MkdirAll("/barewt", os.ModeDir) + repo, _ = fs.Chroot("/barewt") + dot, _ = fs.Chroot("/bare.git") + s = filesystem.NewStorage(dot, nil) + + r, err = git.Init(s, repo) + assert.NilError(t, err) + + w, err = r.Worktree() + assert.NilError(t, err) + + f, err = repo.Create("/hello.txt") + assert.NilError(t, err) + f.Write([]byte("hello world")) + w.Add(f.Name()) + _, err = w.Commit("initial commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@doe.org", + When: time.Now(), + }, + }) + assert.NilError(t, err) + + return fs +} + +func overrideFSLoader(fs billy.Filesystem) { + l := server.NewFilesystemLoader(fs) + client.InstallProtocol("file", server.NewClient(l)) +} + +func TestOpenFileRepo(t *testing.T) { + ctx := context.TODO() + repoFS := setupGitRepo(t) + g := gitsource{} + + overrideFSLoader(repoFS) + defer overrideFSLoader(osfs.New("")) + + fs, _, err := g.clone(ctx, mustParseURL("git+file:///repo"), 0) + assert.NilError(t, err) + + f, err := fs.Open("/foo/bar/hi.txt") + assert.NilError(t, err) + b, _ := ioutil.ReadAll(f) + assert.Equal(t, "hello world", string(b)) + + _, repo, err := g.clone(ctx, mustParseURL("git+file:///repo#master"), 0) + assert.NilError(t, err) + + ref, err := repo.Reference(plumbing.NewBranchReferenceName("master"), true) + assert.NilError(t, err) + assert.Equal(t, "refs/heads/master", ref.Name().String()) + + _, repo, err = g.clone(ctx, mustParseURL("git+file:///repo#refs/tags/v1"), 0) + assert.NilError(t, err) + + ref, err = repo.Head() + assert.NilError(t, err) + assert.Equal(t, testHashes["v1"], ref.Hash().String()) + + _, repo, err = g.clone(ctx, mustParseURL("git+file:///repo/#mybranch"), 0) + assert.NilError(t, err) + + ref, err = repo.Head() + assert.NilError(t, err) + assert.Equal(t, "refs/heads/mybranch", ref.Name().String()) + assert.Equal(t, testHashes["mybranch"], ref.Hash().String()) +} + +func TestOpenBareFileRepo(t *testing.T) { + ctx := context.TODO() + repoFS := setupGitRepo(t) + g := gitsource{} + + overrideFSLoader(repoFS) + defer overrideFSLoader(osfs.New("")) + + fs, _, err := g.clone(ctx, mustParseURL("git+file:///bare.git"), 0) + assert.NilError(t, err) + + f, err := fs.Open("/hello.txt") + assert.NilError(t, err) + b, _ := ioutil.ReadAll(f) + assert.Equal(t, "hello world", string(b)) +} + +func TestReadGit(t *testing.T) { + repoFS := setupGitRepo(t) + + overrideFSLoader(repoFS) + defer overrideFSLoader(osfs.New("")) + + s := &Source{ + Alias: "hi", + URL: mustParseURL("git+file:///bare.git//hello.txt"), + } + b, err := readGit(s) + assert.NilError(t, err) + assert.Equal(t, "hello world", string(b)) + + s = &Source{ + Alias: "hi", + URL: mustParseURL("git+file:///bare.git"), + } + b, err = readGit(s) + assert.NilError(t, err) + assert.Equal(t, "application/array+json", s.mediaType) + assert.Equal(t, `["hello.txt"]`, string(b)) +} + +func TestGitAuth(t *testing.T) { + g := gitsource{} + a, err := g.auth(mustParseURL("git+file:///bare.git")) + assert.NilError(t, err) + assert.Equal(t, nil, a) + + a, err = g.auth(mustParseURL("git+https://example.com/foo")) + assert.NilError(t, err) + assert.Assert(t, is.Nil(a)) + + a, err = g.auth(mustParseURL("git+https://user:swordfish@example.com/foo")) + assert.NilError(t, err) + assert.DeepEqual(t, &http.BasicAuth{Username: "user", Password: "swordfish"}, a) + + os.Setenv("GIT_HTTP_PASSWORD", "swordfish") + defer os.Unsetenv("GIT_HTTP_PASSWORD") + a, err = g.auth(mustParseURL("git+https://user@example.com/foo")) + assert.NilError(t, err) + assert.DeepEqual(t, &http.BasicAuth{Username: "user", Password: "swordfish"}, a) + os.Unsetenv("GIT_HTTP_PASSWORD") + + os.Setenv("GIT_HTTP_TOKEN", "mytoken") + defer os.Unsetenv("GIT_HTTP_TOKEN") + a, err = g.auth(mustParseURL("git+https://user@example.com/foo")) + assert.NilError(t, err) + assert.DeepEqual(t, &http.TokenAuth{Token: "mytoken"}, a) + os.Unsetenv("GIT_HTTP_TOKEN") + + if os.Getenv("SSH_AUTH_SOCK") == "" { + t.Log("no SSH_AUTH_SOCK - skipping ssh agent test") + } else { + a, err = g.auth(mustParseURL("git+ssh://git@example.com/foo")) + assert.NilError(t, err) + sa, ok := a.(*ssh.PublicKeysCallback) + assert.Equal(t, true, ok) + assert.Equal(t, "git", sa.User) + } + + key := string(testdata.PEMBytes["ed25519"]) + os.Setenv("GIT_SSH_KEY", key) + defer os.Unsetenv("GIT_SSH_KEY") + a, err = g.auth(mustParseURL("git+ssh://git@example.com/foo")) + assert.NilError(t, err) + ka, ok := a.(*ssh.PublicKeys) + assert.Equal(t, true, ok) + assert.Equal(t, "git", ka.User) + os.Unsetenv("GIT_SSH_KEY") + + key = base64.StdEncoding.EncodeToString(testdata.PEMBytes["ed25519"]) + os.Setenv("GIT_SSH_KEY", key) + defer os.Unsetenv("GIT_SSH_KEY") + a, err = g.auth(mustParseURL("git+ssh://git@example.com/foo")) + assert.NilError(t, err) + ka, ok = a.(*ssh.PublicKeys) + assert.Equal(t, true, ok) + assert.Equal(t, "git", ka.User) + os.Unsetenv("GIT_SSH_KEY") +} + +func TestRefFromURL(t *testing.T) { + t.Parallel() + g := gitsource{} + data := []struct { + url, expected string + }{ + {"git://localhost:1234/foo/bar.git//baz", ""}, + {"git+http://localhost:1234/foo/bar.git//baz", ""}, + {"git+ssh://localhost:1234/foo/bar.git//baz", ""}, + {"git+file:///foo/bar.git//baz", ""}, + {"git://localhost:1234/foo/bar.git//baz#master", "refs/heads/master"}, + {"git+http://localhost:1234/foo/bar.git//baz#mybranch", "refs/heads/mybranch"}, + {"git+ssh://localhost:1234/foo/bar.git//baz#refs/tags/foo", "refs/tags/foo"}, + {"git+file:///foo/bar.git//baz#mybranch", "refs/heads/mybranch"}, + } + + for _, d := range data { + out := g.refFromURL(mustParseURL(d.url)) + assert.Equal(t, plumbing.ReferenceName(d.expected), out) + } +} |
