package xgit_test import ( "context" "fmt" "os" "path" "testing" "time" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ignite/cli/v29/ignite/pkg/randstr" "github.com/ignite/cli/v29/ignite/pkg/xgit" ) func TestInitAndCommit(t *testing.T) { tests := []struct { name string dirFunc func(*testing.T) string expectDotGitFolder bool expectedNumCommits int expectedFilesInCommit []string }{ { name: "dir is not inside an existing repo", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() err := os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) return dir }, expectDotGitFolder: true, expectedNumCommits: 1, expectedFilesInCommit: []string{"foo"}, }, { name: "dir is inside an existing repo", // In this repo, there's no existing commit but a standalone uncommitted // foo file that shouldn't be included in the xgit.InitAndCommit's commit. dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) dirInsideRepo := path.Join(dir, "bar") err = os.Mkdir(dirInsideRepo, 0o0755) require.NoError(t, err) err = os.WriteFile(path.Join(dirInsideRepo, "baz"), []byte("hello"), 0o755) require.NoError(t, err) return dirInsideRepo }, expectDotGitFolder: false, expectedNumCommits: 1, expectedFilesInCommit: []string{"bar/baz"}, }, { name: "dir is an existing repo", dirFunc: func(t *testing.T) string { t.Helper() // In this repo, there's one existing commit, and an uncommitted baz file // that must be included in the xgit.InitAndCommit's commit. dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{}) require.NoError(t, err) wt, err := repo.Worktree() require.NoError(t, err) _, err = wt.Add(".") require.NoError(t, err) _, err = wt.Commit("First commit", &git.CommitOptions{ Author: &object.Signature{}, }) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "bar"), []byte("hello"), 0o755) require.NoError(t, err) return dir }, expectDotGitFolder: true, expectedNumCommits: 2, expectedFilesInCommit: []string{"bar"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := tt.dirFunc(t) err := xgit.InitAndCommit(dir) require.NoError(t, err) _, err = os.Stat(path.Join(dir, ".git")) require.Equal(t, tt.expectDotGitFolder, !os.IsNotExist(err)) // Assert repository commits. For that we need to open the repo and // iterate over existing commits. repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{ DetectDotGit: true, }) require.NoError(t, err) logs, err := repo.Log(&git.LogOptions{}) require.NoError(t, err) var ( numCommits int lastCommit *object.Commit ) err = logs.ForEach(func(c *object.Commit) error { if numCommits == 0 { lastCommit = c } numCommits++ return nil }) require.NoError(t, err) require.Equal(t, tt.expectedNumCommits, numCommits) if assert.NotNil(t, lastCommit) { require.Equal(t, "Initialized with Ignite CLI", lastCommit.Message) require.WithinDuration(t, time.Now(), lastCommit.Committer.When, 10*time.Second) require.Equal(t, "Developer Experience team at Ignite", lastCommit.Author.Name) require.Equal(t, "hello@ignite.com", lastCommit.Author.Email) stats, err := lastCommit.Stats() require.NoError(t, err) var files []string for _, s := range stats { files = append(files, s.Name) } require.Equal(t, tt.expectedFilesInCommit, files) } }) } } func TestAreChangesCommitted(t *testing.T) { tests := []struct { name string dirFunc func(*testing.T) string expectedResult bool }{ { name: "dir is not a git repo", dirFunc: func(t *testing.T) string { t.Helper() return t.TempDir() }, expectedResult: true, }, { name: "dir is a empty git repo", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) return dir }, expectedResult: true, }, { name: "dir is a dirty empty git repo", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) return dir }, expectedResult: false, }, { name: "dir is a cleaned git repo", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{}) require.NoError(t, err) wt, err := repo.Worktree() require.NoError(t, err) _, err = wt.Add(".") require.NoError(t, err) _, err = wt.Commit("First commit", &git.CommitOptions{ Author: &object.Signature{}, }) require.NoError(t, err) return dir }, expectedResult: true, }, { name: "dir is a dirty git repo", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{}) require.NoError(t, err) wt, err := repo.Worktree() require.NoError(t, err) _, err = wt.Add(".") require.NoError(t, err) _, err = wt.Commit("First commit", &git.CommitOptions{ Author: &object.Signature{}, }) require.NoError(t, err) err = os.WriteFile(path.Join(dir, "bar"), []byte("hello"), 0o755) require.NoError(t, err) return dir }, expectedResult: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := tt.dirFunc(t) res, err := xgit.AreChangesCommitted(dir) require.NoError(t, err) assert.Equal(t, tt.expectedResult, res) }) } } func TestClone(t *testing.T) { // Create a folder with content notEmptyDir := t.TempDir() err := os.WriteFile(path.Join(notEmptyDir, ".foo"), []byte("hello"), 0o755) require.NoError(t, err) // Create a local git repo for all the test cases repoDir := t.TempDir() repo, err := git.PlainInit(repoDir, false) require.NoError(t, err) err = os.WriteFile(path.Join(repoDir, "foo"), []byte("hello"), 0o755) require.NoError(t, err) // Add a first commit w, err := repo.Worktree() require.NoError(t, err) _, err = w.Add(".") require.NoError(t, err) commit1, err := w.Commit("commit1", &git.CommitOptions{ Author: &object.Signature{ Name: "bob", Email: "bob@example.com", When: time.Now(), }, }) // Add a branch on commit1 require.NoError(t, err) err = w.Checkout(&git.CheckoutOptions{ Branch: plumbing.NewBranchReferenceName("my-branch"), Create: true, }) require.NoError(t, err) // Back to master err = w.Checkout(&git.CheckoutOptions{Branch: plumbing.NewBranchReferenceName("master")}) require.NoError(t, err) // Add a tag on commit1 _, err = repo.CreateTag("v1", commit1, &git.CreateTagOptions{ Tagger: &object.Signature{Name: "me"}, Message: "v1", }) require.NoError(t, err) // Add a second commit err = os.WriteFile(path.Join(repoDir, "bar"), []byte("hello"), 0o755) require.NoError(t, err) _, err = w.Add(".") require.NoError(t, err) commit2, err := w.Commit("commit2", &git.CommitOptions{ Author: &object.Signature{ Name: "bob", Email: "bob@example.com", When: time.Now(), }, }) require.NoError(t, err) tests := []struct { name string dir string urlRef string expectedError string expectedRef plumbing.Hash }{ { name: "fail: repo doesn't exist", dir: t.TempDir(), urlRef: "/tmp/not/exists", expectedError: "repository not found", }, { name: "fail: target dir isn't empty", dir: notEmptyDir, urlRef: repoDir, expectedError: fmt.Sprintf(`clone: target directory "%s" is not empty`, notEmptyDir), }, { name: "ok: target dir doesn't exists", dir: "/tmp/not/exists/" + randstr.Runes(6), urlRef: repoDir, expectedRef: commit2, }, { name: "ok: no ref", dir: t.TempDir(), urlRef: repoDir, expectedRef: commit2, }, { name: "ok: empty ref", dir: t.TempDir(), urlRef: repoDir + "@", expectedRef: commit2, }, { name: "ok: with tag ref", dir: t.TempDir(), urlRef: repoDir + "@v1", expectedRef: commit1, }, { name: "ok: with branch ref", dir: t.TempDir(), urlRef: repoDir + "@my-branch", expectedRef: commit1, }, { name: "ok: with commit1 hash ref", dir: t.TempDir(), urlRef: repoDir + "@" + commit1.String(), expectedRef: commit1, }, { name: "ok: with commit2 hash ref", dir: t.TempDir(), urlRef: repoDir + "@" + commit2.String(), expectedRef: commit2, }, { name: "fail: ref not found", dir: t.TempDir(), urlRef: repoDir + "@what", expectedError: "reference not found", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ( files, _ = os.ReadDir(tt.dir) dirWasEmpty = len(files) == 0 ) err := xgit.Clone(context.Background(), tt.urlRef, tt.dir) if tt.expectedError != "" { require.EqualError(t, err, tt.expectedError) if dirWasEmpty { // If it was empty, ensure target dir is still clean files, _ := os.ReadDir(tt.dir) require.Empty(t, files, "target dir should be empty in case of error") } return } require.NoError(t, err) _, err = os.Stat(tt.dir) require.False(t, os.IsNotExist(err), "dir %s should exist", tt.dir) repo, err := git.PlainOpen(tt.dir) require.NoError(t, err) h, err := repo.Head() require.NoError(t, err) require.Equal(t, tt.expectedRef, h.Hash()) }) } } func TestIsRepository(t *testing.T) { tests := []struct { name string dirFunc func(*testing.T) string shouldFail bool expected bool }{ { name: "path is a repository", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() _, err := git.PlainInit(dir, false) require.NoError(t, err) return dir }, expected: true, }, { name: "path is not a repository", dirFunc: func(t *testing.T) string { t.Helper() return t.TempDir() }, expected: false, }, { name: "repository error", dirFunc: func(t *testing.T) string { t.Helper() dir := t.TempDir() err := os.Chmod(dir, 0) require.NoError(t, err) return dir }, shouldFail: true, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Act exists, err := xgit.IsRepository(tt.dirFunc(t)) // Assert require.Equal(t, tt.expected, exists) if tt.shouldFail { require.Error(t, err) } else { require.NoError(t, err) } }) } }