package data import ( "context" "encoding/base64" "fmt" "io" "net/url" "os" "strings" "testing" "time" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport/client" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/server" "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/go-git/go-git/v5/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 { d := d 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", "", "git+https://github.com/hairyhenderson/gomplate", "/docs-src/content/functions/aws.yml"}, {"git+ssh://github.com/hairyhenderson/gomplate.git", "", "git+ssh://github.com/hairyhenderson/gomplate.git", "/"}, {"https://github.com", "", "https://github.com", "/"}, {"git://example.com/foo//file.txt#someref", "", "git://example.com/foo#someref", "/file.txt"}, {"git+file:///home/foo/repo//file.txt#someref", "", "git+file:///home/foo/repo#someref", "/file.txt"}, {"git+file:///repo", "", "git+file:///repo", "/"}, {"git+file:///foo//foo", "", "git+file:///foo", "/foo"}, {"git+file:///foo//foo", "/bar", "git+file:///foo", "/foo/bar"}, {"git+file:///foo//bar", // in this case the // is meaningless "/baz//qux", "git+file:///foo", "/bar/baz/qux"}, {"git+https://example.com/foo", "/bar", "git+https://example.com/foo/bar", "/"}, {"git+https://example.com/foo", "//bar", "git+https://example.com/foo", "/bar"}, {"git+https://example.com//foo", "/bar", "git+https://example.com", "/foo/bar"}, {"git+https://example.com/foo//bar", "//baz", "git+https://example.com/foo", "/bar/baz"}, {"git+https://example.com/foo", "/bar//baz", "git+https://example.com/foo/bar", "/baz"}, {"git+https://example.com/foo?type=t", "/bar//baz", "git+https://example.com/foo/bar?type=t", "/baz"}, {"git+https://example.com/foo#master", "/bar//baz", "git+https://example.com/foo/bar#master", "/baz"}, {"git+https://example.com/foo", "/bar//baz?type=t", "git+https://example.com/foo/bar?type=t", "/baz"}, {"git+https://example.com/foo", "/bar//baz#master", "git+https://example.com/foo/bar#master", "/baz"}, {"git+https://example.com/foo", "//bar?type=t", "git+https://example.com/foo?type=t", "/bar"}, {"git+https://example.com/foo", "//bar#master", "git+https://example.com/foo#master", "/bar"}, {"git+https://example.com/foo?type=t", "//bar#master", "git+https://example.com/foo?type=t#master", "/bar"}, {"git+https://example.com/foo?type=t#v1", "//bar?type=j#v2", "git+https://example.com/foo?type=t&type=j#v2", "/bar"}, } for i, d := range data { d := d 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) args := []string{d.args} if d.args == "" { args = nil } repo, path, err := g.parseGitPath(u, 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) // default to main h := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main")) err = s.SetReference(h) assert.NilError(t, err) // config needs to be created after setting up a "normal" fs repo // this is possibly a bug in git-go? c, err := r.Config() assert.NilError(t, err) c.Init.DefaultBranch = "main" 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.Background() repoFS := setupGitRepo(t) g := gitsource{} overrideFSLoader(repoFS) defer overrideFSLoader(osfs.New("")) fsys, _, err := g.clone(ctx, mustParseURL("git+file:///repo"), 0) assert.NilError(t, err) f, err := fsys.Open("/foo/bar/hi.txt") assert.NilError(t, err) b, _ := io.ReadAll(f) assert.Equal(t, "hello world", string(b)) _, repo, err := g.clone(ctx, mustParseURL("git+file:///repo#main"), 0) assert.NilError(t, err) ref, err := repo.Reference(plumbing.NewBranchReferenceName("main"), true) assert.NilError(t, err) assert.Equal(t, "refs/heads/main", 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.Background() repoFS := setupGitRepo(t) g := gitsource{} overrideFSLoader(repoFS) defer overrideFSLoader(osfs.New("")) fsys, _, err := g.clone(ctx, mustParseURL("git+file:///bare.git"), 0) assert.NilError(t, err) f, err := fsys.Open("/hello.txt") assert.NilError(t, err) b, _ := io.ReadAll(f) assert.Equal(t, "hello world", string(b)) } func TestReadGit(t *testing.T) { ctx := context.Background() repoFS := setupGitRepo(t) overrideFSLoader(repoFS) defer overrideFSLoader(osfs.New("")) s := &Source{ Alias: "hi", URL: mustParseURL("git+file:///bare.git//hello.txt"), } b, err := readGit(ctx, 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(ctx, 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) } }