package ref import ( "fmt" "strings" "testing" "time" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v16/internal/helper" "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text" "gitlab.com/gitlab-org/gitaly/v16/internal/structerr" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" "google.golang.org/protobuf/types/known/timestamppb" ) func TestFindTag(t *testing.T) { t.Parallel() ctx := testhelper.Context(t) cfg, client := setupRefService(t) type setupData struct { request *gitalypb.FindTagRequest expectedErr error expectedTag *gitalypb.Tag } for _, tc := range []struct { desc string setup func(t *testing.T) setupData }{ { desc: "empty request", setup: func(t *testing.T) setupData { return setupData{ request: &gitalypb.FindTagRequest{}, expectedErr: structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), } }, }, { desc: "invalid repo", setup: func(t *testing.T) setupData { return setupData{ request: &gitalypb.FindTagRequest{ Repository: &gitalypb.Repository{ StorageName: "fake", RelativePath: "repo", }, }, expectedErr: testhelper.ToInterceptedMetadata(structerr.NewInvalidArgument( "%w", storage.NewStorageNotFoundError("fake"), )), } }, }, { desc: "empty tag name", setup: func(t *testing.T) setupData { repo, _ := gittest.CreateRepository(t, ctx, cfg) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, }, expectedErr: structerr.NewInvalidArgument("tag name is empty"), } }, }, { desc: "nonexistent tag", setup: func(t *testing.T) setupData { repo, _ := gittest.CreateRepository(t, ctx, cfg) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("does-not-exist"), }, expectedErr: structerr.NewNotFound("tag does not exist").WithDetail( &gitalypb.FindTagError{ Error: &gitalypb.FindTagError_TagNotFound{ TagNotFound: &gitalypb.ReferenceNotFoundError{ ReferenceName: []byte("refs/tags/does-not-exist"), }, }, }, ), } }, }, { desc: "lightweight tag", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) tagID := gittest.WriteTag(t, cfg, repoPath, commitID.String(), commitID.Revision()) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte(commitID), }, expectedTag: &gitalypb.Tag{ Name: []byte(commitID), Id: tagID.String(), TargetCommit: commit, }, } }, }, { desc: "annotated tag", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) tagID := gittest.WriteTag(t, cfg, repoPath, commitID.String(), commitID.Revision(), gittest.WriteTagConfig{ Message: "annotated", }) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte(commitID), }, expectedTag: &gitalypb.Tag{ Name: []byte(commitID), Id: tagID.String(), TargetCommit: commit, Message: []byte("annotated"), MessageSize: 9, Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "tag pointing to blob", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) blobID := gittest.WriteBlob(t, cfg, repoPath, []byte("tagged data")) tagID := gittest.WriteTag(t, cfg, repoPath, "tagged-blob", blobID.Revision(), gittest.WriteTagConfig{ Message: "annotated", }) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("tagged-blob"), }, expectedTag: &gitalypb.Tag{ Name: []byte("tagged-blob"), Id: tagID.String(), Message: []byte("annotated"), MessageSize: 9, Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "deeply nested tag", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) currentID := commitID for i := 0; i < 20; i++ { tagName := fmt.Sprintf("tag-%d", i) currentID = gittest.WriteTag(t, cfg, repoPath, tagName, currentID.Revision(), gittest.WriteTagConfig{ Message: tagName, }) } return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("tag-19"), }, expectedTag: &gitalypb.Tag{ Name: []byte("tag-19"), Id: currentID.String(), TargetCommit: commit, Message: []byte("tag-19"), MessageSize: 6, Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "tag with commit ID as name", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) tagID := gittest.WriteTag(t, cfg, repoPath, commitID.String(), commitID.Revision(), gittest.WriteTagConfig{ Message: "message\n", }) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte(commitID), }, expectedTag: &gitalypb.Tag{ Name: []byte(commitID), Id: tagID.String(), TargetCommit: commit, Message: []byte("message"), MessageSize: 7, Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "tag of tag", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) parentTagID := gittest.WriteTag(t, cfg, repoPath, "parent-tag", commitID.Revision(), gittest.WriteTagConfig{ Message: "parent message\n", }) childTagID := gittest.WriteTag(t, cfg, repoPath, "child-tag", parentTagID.Revision(), gittest.WriteTagConfig{ Message: "child message\n", }) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("child-tag"), }, expectedTag: &gitalypb.Tag{ Name: []byte("child-tag"), Id: childTagID.String(), TargetCommit: commit, Message: []byte("child message"), MessageSize: 13, Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "tag of commit with huge commit message", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitMessage := "An empty commit with REALLY BIG message\n\n" + strings.Repeat("a", helper.MaxCommitOrTagMessageSize+1) commitID, commit := writeCommit(t, ctx, cfg, repo, gittest.WithMessage(commitMessage), ) tagID := gittest.WriteTag(t, cfg, repoPath, "tag", commitID.Revision()) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("tag"), }, expectedTag: &gitalypb.Tag{ Name: []byte("tag"), Id: tagID.String(), TargetCommit: commit, }, } }, }, { desc: "tag with huge tag message", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) tagMessage := strings.Repeat("a", 11*1024) tagID := gittest.WriteTag(t, cfg, repoPath, "tag", commitID.Revision(), gittest.WriteTagConfig{ Message: tagMessage, }) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("tag"), }, expectedTag: &gitalypb.Tag{ Name: []byte("tag"), Id: tagID.String(), TargetCommit: commit, Message: []byte(tagMessage[:helper.MaxCommitOrTagMessageSize]), MessageSize: int64(len(tagMessage)), Tagger: gittest.DefaultCommitAuthor, }, } }, }, { desc: "tag with signature", setup: func(t *testing.T) setupData { repo, repoPath := gittest.CreateRepository(t, ctx, cfg) commitID, commit := writeCommit(t, ctx, cfg, repo) output := gittest.ExecOpts(t, cfg, gittest.ExecConfig{ Stdin: strings.NewReader(fmt.Sprintf( `object %[1]s type commit tag signed-tag tagger Some Author 100000000 +0100 gpgsig -----BEGIN PGP SIGNATURE----- this is a pseude PGP signature -----END PGP SIGNATURE----- signed tag message `, commitID)), }, "-C", repoPath, "hash-object", "-t", "tag", "--stdin", "-w") tagID, err := gittest.DefaultObjectHash.FromHex(text.ChompBytes(output)) require.NoError(t, err) gittest.WriteRef(t, cfg, repoPath, "refs/tags/signed-tag", tagID) return setupData{ request: &gitalypb.FindTagRequest{ Repository: repo, TagName: []byte("signed-tag"), }, expectedTag: &gitalypb.Tag{ Name: []byte("signed-tag"), Id: tagID.String(), TargetCommit: commit, Message: []byte("signed tag message"), MessageSize: 18, Tagger: &gitalypb.CommitAuthor{ Name: []byte("Some Author"), Email: []byte("some.author@example.com"), Timezone: []byte("+0100"), Date: timestamppb.New(time.Unix(100000000, 0)), }, }, } }, }, } { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() setup := tc.setup(t) response, err := client.FindTag(ctx, setup.request) testhelper.RequireGrpcError(t, setup.expectedErr, err) testhelper.ProtoEqual(t, setup.expectedTag, response.GetTag()) }) } }