diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-09-13 10:30:30 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-09-13 10:30:30 +0300 |
commit | 9608e75580e805da5258ad3c02df3ad1cde91176 (patch) | |
tree | df209a176aa9f87a0f3fd3b9dc8049c78ef59b87 | |
parent | fa6eb13ccb4f2d5a36d3c010eeb197eb2bfdc825 (diff) | |
parent | 158715f9c7afd0934b1b930f87081d8ca1540b8d (diff) |
Merge branch 'pks-find-all-tags' into 'master'
Miscellaneous tag-related refactorings
See merge request gitlab-org/gitaly!3846
-rw-r--r-- | internal/git/catfile/tag_test.go | 4 | ||||
-rw-r--r-- | internal/git/gittest/tag.go | 53 | ||||
-rw-r--r-- | internal/git/objectpool/fetch_test.go | 4 | ||||
-rw-r--r-- | internal/gitaly/service/ref/find_all_tags.go | 217 | ||||
-rw-r--r-- | internal/gitaly/service/ref/find_all_tags_test.go | 617 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refs.go | 205 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refs_test.go | 642 | ||||
-rw-r--r-- | internal/gitaly/service/ref/tag_messages_test.go | 10 | ||||
-rw-r--r-- | internal/gitaly/service/ref/tag_signatures_test.go | 28 | ||||
-rw-r--r-- | internal/gitaly/service/remote/update_remote_mirror_test.go | 16 | ||||
-rw-r--r-- | internal/gitaly/service/repository/fetch_remote_test.go | 2 |
11 files changed, 923 insertions, 875 deletions
diff --git a/internal/git/catfile/tag_test.go b/internal/git/catfile/tag_test.go index bebaad25e..0275a4f28 100644 --- a/internal/git/catfile/tag_test.go +++ b/internal/git/catfile/tag_test.go @@ -27,7 +27,7 @@ func TestGetTag(t *testing.T) { testCases := []struct { tagName string - rev string + rev git.Revision message string trim bool }{ @@ -53,7 +53,7 @@ func TestGetTag(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.tagName, func(t *testing.T) { - tagID := gittest.CreateTag(t, cfg, testRepoPath, testCase.tagName, testCase.rev, &gittest.CreateTagOpts{Message: testCase.message}) + tagID := gittest.WriteTag(t, cfg, testRepoPath, testCase.tagName, testCase.rev, gittest.WriteTagConfig{Message: testCase.message}) tag, err := GetTag(ctx, c, git.Revision(tagID), testCase.tagName, testCase.trim, true) require.NoError(t, err) diff --git a/internal/git/gittest/tag.go b/internal/git/gittest/tag.go index 309dad6fe..ecda217dc 100644 --- a/internal/git/gittest/tag.go +++ b/internal/git/gittest/tag.go @@ -5,34 +5,43 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v14/internal/helper/text" ) -// CreateTagOpts holds extra options for CreateTag. -type CreateTagOpts struct { +// WriteTagConfig holds extra options for WriteTag. +type WriteTagConfig struct { + // Message is the message of an annotated tag. If left empty, then a lightweight tag will + // be created. Message string - Force bool + // Force indicates whether existing tags with the same name shall be overwritten. + Force bool } -// CreateTag creates a new tag. -func CreateTag(t testing.TB, cfg config.Cfg, repoPath, tagName, targetID string, opts *CreateTagOpts) string { - var message string - force := false +// WriteTag writes a new tag into the repository. This function either returns the tag ID in case +// an annotated tag was created, or otherwise the target object ID when a lightweight tag was +// created. Takes either no WriteTagConfig, in which case the default values will be used, or +// exactly one. +func WriteTag( + t testing.TB, + cfg config.Cfg, + repoPath string, + tagName string, + targetRevision git.Revision, + optionalConfig ...WriteTagConfig, +) git.ObjectID { + require.Less(t, len(optionalConfig), 2, "only a single config may be passed") - if opts != nil { - if opts.Message != "" { - message = opts.Message - } - force = opts.Force + var config WriteTagConfig + if len(optionalConfig) == 1 { + config = optionalConfig[0] } committerName := "Scrooge McDuck" committerEmail := "scrooge@mcduck.com" - // message can be very large, passing it directly in args would blow things up! - stdin := bytes.NewBufferString(message) - args := []string{ "-C", repoPath, "-c", fmt.Sprintf("user.name=%s", committerName), @@ -40,17 +49,23 @@ func CreateTag(t testing.TB, cfg config.Cfg, repoPath, tagName, targetID string, "tag", } - if force { + if config.Force { args = append(args, "-f") } - if message != "" { + // The message can be very large, passing it directly in args would blow things up. + stdin := bytes.NewBufferString(config.Message) + if config.Message != "" { args = append(args, "-F", "-") } - args = append(args, tagName, targetID) + args = append(args, tagName, targetRevision.String()) ExecStream(t, cfg, stdin, args...) tagID := Exec(t, cfg, "-C", repoPath, "show-ref", "-s", tagName) - return text.ChompBytes(tagID) + + objectID, err := git.NewObjectIDFromHex(text.ChompBytes(tagID)) + require.NoError(t, err) + + return objectID } diff --git a/internal/git/objectpool/fetch_test.go b/internal/git/objectpool/fetch_test.go index 491c4f8ab..d0dbb2fb7 100644 --- a/internal/git/objectpool/fetch_test.go +++ b/internal/git/objectpool/fetch_test.go @@ -51,7 +51,7 @@ func TestFetchFromOriginDangling(t *testing.T) { // A tag with random hex characters in its name should be unique. newTagName := "tag-" + nonce - newTag := gittest.CreateTag(t, pool.cfg, pool.FullPath(), newTagName, existingCommit, &gittest.CreateTagOpts{ + newTag := gittest.WriteTag(t, pool.cfg, pool.FullPath(), newTagName, existingCommit, gittest.WriteTagConfig{ Message: "msg", }) @@ -77,7 +77,7 @@ func TestFetchFromOriginDangling(t *testing.T) { refsAfter := gittest.Exec(t, pool.cfg, "-C", pool.FullPath(), "for-each-ref", "--format=%(refname) %(objectname)") refsAfterLines := strings.Split(string(refsAfter), "\n") - for _, id := range []string{newBlob.String(), newTree.String(), newCommit.String(), newTag} { + for _, id := range []git.ObjectID{newBlob, newTree, newCommit, newTag} { require.Contains(t, refsAfterLines, fmt.Sprintf("refs/dangling/%s %s", id, id)) } } diff --git a/internal/gitaly/service/ref/find_all_tags.go b/internal/gitaly/service/ref/find_all_tags.go new file mode 100644 index 000000000..057414302 --- /dev/null +++ b/internal/gitaly/service/ref/find_all_tags.go @@ -0,0 +1,217 @@ +package ref + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + + "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" + "gitlab.com/gitlab-org/gitaly/v14/internal/git/gitpipe" + "gitlab.com/gitlab-org/gitaly/v14/internal/git/localrepo" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper/chunk" + "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" + "google.golang.org/protobuf/proto" +) + +func (s *server) FindAllTags(in *gitalypb.FindAllTagsRequest, stream gitalypb.RefService_FindAllTagsServer) error { + ctx := stream.Context() + + if err := s.validateFindAllTagsRequest(in); err != nil { + return helper.ErrInvalidArgument(err) + } + + sortField, err := getTagSortField(in.GetSortBy()) + if err != nil { + return helper.ErrInvalidArgument(err) + } + + repo := s.localrepo(in.GetRepository()) + + if err := s.findAllTags(ctx, repo, sortField, stream); err != nil { + return helper.ErrInternal(err) + } + + return nil +} + +func (s *server) findAllTags(ctx context.Context, repo *localrepo.Repo, sortField string, stream gitalypb.RefService_FindAllTagsServer) error { + c, err := s.catfileCache.BatchProcess(ctx, repo) + if err != nil { + return fmt.Errorf("error creating catfile: %v", err) + } + + forEachRefIter := gitpipe.ForEachRef(ctx, repo, []string{"refs/tags/"}, sortField) + forEachRefIter = gitpipe.RevisionTransform(ctx, forEachRefIter, + func(r gitpipe.RevisionResult) []gitpipe.RevisionResult { + // We transform the pipeline to include each tag-reference twice: once for + // the "normal" object, and once we opportunistically peel the object to a + // non-tag object. This is required such that we can efficiently parse the + // tagged object. + return []gitpipe.RevisionResult{ + r, + {OID: r.OID + "^{}"}, + } + }, + ) + + catfileInfoIter := gitpipe.CatfileInfo(ctx, c, forEachRefIter) + + // In the previous pipeline step, we request information about both the object and the + // peeled object in case the object is a tag. Given that we now know about object types, we + // can filter out the second request in case the object is not a tag: peeling a non-tag + // object to a non-tag object is always going to end up with the same object anyway. And + // requesting the same object twice is moot. + type state int + const ( + // stateTag indicates that the next object is going to be a tag. + stateTag = state(iota) + // statePeeledTag indicates that the next object is going to be the peeled object of + // the preceding tag. + statePeeledTag + // stateSkip indicates that the next object shall be skipped because it is the + // peeled version of a non-tag object, which is the same object anyway. + stateSkip + ) + + currentState := stateTag + catfileInfoIter = gitpipe.CatfileInfoFilter(ctx, catfileInfoIter, + func(r gitpipe.CatfileInfoResult) bool { + switch currentState { + case stateTag: + // If we've got a tag, then we want to also see its peeled object. + // Otherwise, we can skip over the peeled object. + currentState = statePeeledTag + if r.ObjectInfo.Type != "tag" { + currentState = stateSkip + } + return true + case statePeeledTag: + currentState = stateTag + return true + case stateSkip: + currentState = stateTag + return false + } + + // We could try to gracefully handle this, but I don't see much of a point + // given that we can see above that it's never going to be anything else but + // a known state. + panic("invalid state") + }, + ) + + catfileObjectsIter := gitpipe.CatfileObject(ctx, c, catfileInfoIter) + + chunker := chunk.New(&tagSender{stream: stream}) + + for catfileObjectsIter.Next() { + tag := catfileObjectsIter.Result() + + var result *gitalypb.Tag + switch tag.ObjectInfo.Type { + case "tag": + var err error + result, err = catfile.ParseTag(tag.ObjectReader, tag.ObjectInfo.Oid) + if err != nil { + return fmt.Errorf("parsing annotated tag: %w", err) + } + + // For each tag, we expect both the tag itself as well as its + // potentially-peeled tagged object. + if !catfileObjectsIter.Next() { + return errors.New("expected peeled tag") + } + + peeledTag := catfileObjectsIter.Result() + + // We only need to parse the tagged object in case we have an annotated tag + // which refers to a commit object. Otherwise, we discard the object's + // contents. + if peeledTag.ObjectInfo.Type == "commit" { + result.TargetCommit, err = catfile.ParseCommit(peeledTag.ObjectReader, peeledTag.ObjectInfo.Oid) + if err != nil { + return fmt.Errorf("parsing tagged commit: %w", err) + } + } else { + if _, err := io.Copy(ioutil.Discard, peeledTag.ObjectReader); err != nil { + return fmt.Errorf("discarding tagged object contents: %w", err) + } + } + case "commit": + commit, err := catfile.ParseCommit(tag.ObjectReader, tag.ObjectInfo.Oid) + if err != nil { + return fmt.Errorf("parsing tagged commit: %w", err) + } + + result = &gitalypb.Tag{ + Id: tag.ObjectInfo.Oid.String(), + TargetCommit: commit, + } + default: + if _, err := io.Copy(ioutil.Discard, tag.ObjectReader); err != nil { + return fmt.Errorf("discarding tag object contents: %w", err) + } + + result = &gitalypb.Tag{ + Id: tag.ObjectInfo.Oid.String(), + } + } + + // In case we can deduce the tag name from the object name (which should typically + // be the case), we always want to return the tag name. While annotated tags do have + // their name encoded in the object itself, we instead want to default to the name + // of the reference such that we can discern multiple refs pointing to the same tag. + if tagName := bytes.TrimPrefix(tag.ObjectName, []byte("refs/tags/")); len(tagName) > 0 { + result.Name = tagName + } + + if err := chunker.Send(result); err != nil { + return fmt.Errorf("sending tag: %w", err) + } + } + + if err := catfileObjectsIter.Err(); err != nil { + return fmt.Errorf("iterating over tags: %w", err) + } + + if err := chunker.Flush(); err != nil { + return fmt.Errorf("flushing chunker: %w", err) + } + + return nil +} + +func (s *server) validateFindAllTagsRequest(request *gitalypb.FindAllTagsRequest) error { + if request.GetRepository() == nil { + return errors.New("empty Repository") + } + + if _, err := s.locator.GetRepoPath(request.GetRepository()); err != nil { + return fmt.Errorf("invalid git directory: %v", err) + } + + return nil +} + +type tagSender struct { + tags []*gitalypb.Tag + stream gitalypb.RefService_FindAllTagsServer +} + +func (t *tagSender) Reset() { + t.tags = t.tags[:0] +} + +func (t *tagSender) Append(m proto.Message) { + t.tags = append(t.tags, m.(*gitalypb.Tag)) +} + +func (t *tagSender) Send() error { + return t.stream.Send(&gitalypb.FindAllTagsResponse{ + Tags: t.tags, + }) +} diff --git a/internal/gitaly/service/ref/find_all_tags_test.go b/internal/gitaly/service/ref/find_all_tags_test.go new file mode 100644 index 000000000..628edc948 --- /dev/null +++ b/internal/gitaly/service/ref/find_all_tags_test.go @@ -0,0 +1,617 @@ +package ref + +import ( + "bytes" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/internal/git" + "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" + "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" + "gitlab.com/gitlab-org/gitaly/v14/internal/git/localrepo" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper" + "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testassert" + "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestFindAllTags_successful(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) + repo := localrepo.NewTestRepo(t, cfg, repoProto) + + ctx, cancel := testhelper.Context() + defer cancel() + + // reconstruct the v1.1.2 tag from patches to test truncated tag message + // with partial PGP block + truncatedPGPTagMsg := testhelper.MustReadFile(t, "testdata/truncated_pgp_msg.patch") + + truncatedPGPTagID := string(gittest.ExecStream(t, cfg, bytes.NewBuffer(truncatedPGPTagMsg), "-C", repoPath, "mktag")) + truncatedPGPTagID = strings.TrimSpace(truncatedPGPTagID) // remove trailing newline + gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/tags/pgp-long-tag-message", truncatedPGPTagID) + + blobID := git.ObjectID("faaf198af3a36dbf41961466703cc1d47c61d051") + commitID := git.ObjectID("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") + + gitCommit := testhelper.GitLabTestCommit(commitID.String()) + + bigCommitID := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithBranch("local-big-commits"), + gittest.WithMessage("An empty commit with REALLY BIG message\n\n"+strings.Repeat("a", helper.MaxCommitOrTagMessageSize+1)), + gittest.WithParents("60ecb67744cb56576c30214ff52294f8ce2def98"), + ) + bigCommit, err := repo.ReadCommit(ctx, git.Revision(bigCommitID)) + require.NoError(t, err) + + annotatedTagID := gittest.WriteTag(t, cfg, repoPath, "v1.2.0", blobID.Revision(), gittest.WriteTagConfig{Message: "Blob tag"}) + + gittest.WriteTag(t, cfg, repoPath, "v1.3.0", commitID.Revision()) + gittest.WriteTag(t, cfg, repoPath, "v1.4.0", blobID.Revision()) + + // To test recursive resolving to a commit + gittest.WriteTag(t, cfg, repoPath, "v1.5.0", "v1.3.0") + + // A tag to commit with a big message + gittest.WriteTag(t, cfg, repoPath, "v1.6.0", bigCommitID.Revision()) + + // A tag with a big message + bigMessage := strings.Repeat("a", 11*1024) + bigMessageTag1ID := gittest.WriteTag(t, cfg, repoPath, "v1.7.0", commitID.Revision(), gittest.WriteTagConfig{Message: bigMessage}) + + // A tag with a commit id as its name + commitTagID := gittest.WriteTag(t, cfg, repoPath, commitID.String(), commitID.Revision(), gittest.WriteTagConfig{Message: "commit tag with a commit sha as the name"}) + + // a tag of a tag + tagOfTagID := gittest.WriteTag(t, cfg, repoPath, "tag-of-tag", commitTagID.Revision(), gittest.WriteTagConfig{Message: "tag of a tag"}) + + rpcRequest := &gitalypb.FindAllTagsRequest{Repository: repoProto} + + c, err := client.FindAllTags(ctx, rpcRequest) + require.NoError(t, err) + + var receivedTags []*gitalypb.Tag + for { + r, err := c.Recv() + if err == io.EOF { + break + } + require.NoError(t, err) + receivedTags = append(receivedTags, r.GetTags()...) + } + + expectedTags := []*gitalypb.Tag{ + { + Name: []byte(commitID), + Id: commitTagID.String(), + TargetCommit: gitCommit, + Message: []byte("commit tag with a commit sha as the name"), + MessageSize: 40, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + }, + { + Name: []byte("tag-of-tag"), + Id: tagOfTagID.String(), + TargetCommit: gitCommit, + Message: []byte("tag of a tag"), + MessageSize: 12, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + }, + { + Name: []byte("v1.0.0"), + Id: "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + TargetCommit: gitCommit, + Message: []byte("Release"), + MessageSize: 7, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamppb.Timestamp{Seconds: 1393491299}, + Timezone: []byte("+0200"), + }, + }, + { + Name: []byte("v1.1.0"), + Id: "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + TargetCommit: testhelper.GitLabTestCommit("5937ac0a7beb003549fc5fd26fc247adbce4a52e"), + Message: []byte("Version 1.1.0"), + MessageSize: 13, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamppb.Timestamp{Seconds: 1393505709}, + Timezone: []byte("+0200"), + }, + }, + { + Name: []byte("v1.1.1"), + Id: "8f03acbcd11c53d9c9468078f32a2622005a4841", + TargetCommit: testhelper.GitLabTestCommit("189a6c924013fc3fe40d6f1ec1dc20214183bc97"), + Message: []byte("x509 signed tag\n-----BEGIN SIGNED MESSAGE-----\nMIISfwYJKoZIhvcNAQcCoIIScDCCEmwCAQExDTALBglghkgBZQMEAgEwCwYJKoZI\nhvcNAQcBoIIP8zCCB3QwggVcoAMCAQICBBXXLOIwDQYJKoZIhvcNAQELBQAwgbYx\nCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVu\nMRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwU\nU2llbWVucyBUcnVzdCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBD\nQSBNZWRpdW0gU3RyZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjAeFw0xNzAyMDMw\nNjU4MzNaFw0yMDAyMDMwNjU4MzNaMFsxETAPBgNVBAUTCFowMDBOV0RIMQ4wDAYD\nVQQqDAVSb2dlcjEOMAwGA1UEBAwFTWVpZXIxEDAOBgNVBAoMB1NpZW1lbnMxFDAS\nBgNVBAMMC01laWVyIFJvZ2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAuBNea/68ZCnHYQjpm/k3ZBG0wBpEKSwG6lk9CEQlSxsqVLQHAoAKBIlJm1in\nYVLcK/Sq1yhYJ/qWcY/M53DhK2rpPuhtrWJUdOUy8EBWO20F4bd4Fw9pO7jt8bme\nu33TSrK772vKjuppzB6SeG13Cs08H+BIeD106G27h7ufsO00pvsxoSDL+uc4slnr\npBL+2TAL7nSFnB9QHWmRIK27SPqJE+lESdb0pse11x1wjvqKy2Q7EjL9fpqJdHzX\nNLKHXd2r024TOORTa05DFTNR+kQEKKV96XfpYdtSBomXNQ44cisiPBJjFtYvfnFE\nwgrHa8fogn/b0C+A+HAoICN12wIDAQABo4IC4jCCAt4wHQYDVR0OBBYEFCF+gkUp\nXQ6xGc0kRWXuDFxzA14zMEMGA1UdEQQ8MDqgIwYKKwYBBAGCNxQCA6AVDBNyLm1l\naWVyQHNpZW1lbnMuY29tgRNyLm1laWVyQHNpZW1lbnMuY29tMA4GA1UdDwEB/wQE\nAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwgcoGA1UdHwSBwjCB\nvzCBvKCBuaCBtoYmaHR0cDovL2NoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5j\ncmyGQWxkYXA6Ly9jbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jZXJ0\naWZpY2F0ZVJldm9jYXRpb25MaXN0hklsZGFwOi8vY2wuc2llbWVucy5jb20vQ049\nWlpaWlpaQTYsbz1UcnVzdGNlbnRlcj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0\nMEUGA1UdIAQ+MDwwOgYNKwYBBAGhaQcCAgMBAzApMCcGCCsGAQUFBwIBFhtodHRw\nOi8vd3d3LnNpZW1lbnMuY29tL3BraS8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW\ngBT4FV1HDGx3e3LEAheRaKK292oJRDCCAQQGCCsGAQUFBwEBBIH3MIH0MDIGCCsG\nAQUFBzAChiZodHRwOi8vYWguc2llbWVucy5jb20vcGtpP1paWlpaWkE2LmNydDBB\nBggrBgEFBQcwAoY1bGRhcDovL2FsLnNpZW1lbnMubmV0L0NOPVpaWlpaWkE2LEw9\nUEtJP2NBQ2VydGlmaWNhdGUwSQYIKwYBBQUHMAKGPWxkYXA6Ly9hbC5zaWVtZW5z\nLmNvbS9DTj1aWlpaWlpBNixvPVRydXN0Y2VudGVyP2NBQ2VydGlmaWNhdGUwMAYI\nKwYBBQUHMAGGJGh0dHA6Ly9vY3NwLnBraS1zZXJ2aWNlcy5zaWVtZW5zLmNvbTAN\nBgkqhkiG9w0BAQsFAAOCAgEAXPVcX6vaEcszJqg5IemF9aFTlwTrX5ITNIpzcqG+\nkD5haOf2mZYLjl+MKtLC1XfmIsGCUZNb8bjP6QHQEI+2d6x/ZOqPq7Kd7PwVu6x6\nxZrkDjUyhUbUntT5+RBy++l3Wf6Cq6Kx+K8ambHBP/bu90/p2U8KfFAG3Kr2gI2q\nfZrnNMOxmJfZ3/sXxssgLkhbZ7hRa+MpLfQ6uFsSiat3vlawBBvTyHnoZ/7oRc8y\nqi6QzWcd76CPpMElYWibl+hJzKbBZUWvc71AzHR6i1QeZ6wubYz7vr+FF5Y7tnxB\nVz6omPC9XAg0F+Dla6Zlz3Awj5imCzVXa+9SjtnsidmJdLcKzTAKyDewewoxYOOJ\nj3cJU7VSjJPl+2fVmDBaQwcNcUcu/TPAKApkegqO7tRF9IPhjhW8QkRnkqMetO3D\nOXmAFVIsEI0Hvb2cdb7B6jSpjGUuhaFm9TCKhQtCk2p8JCDTuaENLm1x34rrJKbT\n2vzyYN0CZtSkUdgD4yQxK9VWXGEzexRisWb4AnZjD2NAquLPpXmw8N0UwFD7MSpC\ndpaX7FktdvZmMXsnGiAdtLSbBgLVWOD1gmJFDjrhNbI8NOaOaNk4jrfGqNh5lhGU\n4DnBT2U6Cie1anLmFH/oZooAEXR2o3Nu+1mNDJChnJp0ovs08aa3zZvBdcloOvfU\nqdowggh3MIIGX6ADAgECAgQtyi/nMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYDVQQG\nEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UE\nCgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaQTExHTAbBgNVBAsMFFNpZW1lbnMg\nVHJ1c3QgQ2VudGVyMSIwIAYDVQQDDBlTaWVtZW5zIFJvb3QgQ0EgVjMuMCAyMDE2\nMB4XDTE2MDcyMDEzNDYxMFoXDTIyMDcyMDEzNDYxMFowgbYxCzAJBgNVBAYTAkRF\nMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQKDAdT\naWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVz\ndCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3Ry\nZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAL9UfK+JAZEqVMVvECdYF9IK4KSw34AqyNl3rYP5x03dtmKaNu+2\n0fQqNESA1NGzw3s6LmrKLh1cR991nB2cvKOXu7AvEGpSuxzIcOROd4NpvRx+Ej1p\nJIPeqf+ScmVK7lMSO8QL/QzjHOpGV3is9sG+ZIxOW9U1ESooy4Hal6ZNs4DNItsz\npiCKqm6G3et4r2WqCy2RRuSqvnmMza7Y8BZsLy0ZVo5teObQ37E/FxqSrbDI8nxn\nB7nVUve5ZjrqoIGSkEOtyo11003dVO1vmWB9A0WQGDqE/q3w178hGhKfxzRaqzyi\nSoADUYS2sD/CglGTUxVq6u0pGLLsCFjItcCWqW+T9fPYfJ2CEd5b3hvqdCn+pXjZ\n/gdX1XAcdUF5lRnGWifaYpT9n4s4adzX8q6oHSJxTppuAwLRKH6eXALbGQ1I9lGQ\nDSOipD/09xkEsPw6HOepmf2U3YxZK1VU2sHqugFJboeLcHMzp6E1n2ctlNG1GKE9\nFDHmdyFzDi0Nnxtf/GgVjnHF68hByEE1MYdJ4nJLuxoT9hyjYdRW9MpeNNxxZnmz\nW3zh7QxIqP0ZfIz6XVhzrI9uZiqwwojDiM5tEOUkQ7XyW6grNXe75yt6mTj89LlB\nH5fOW2RNmCy/jzBXDjgyskgK7kuCvUYTuRv8ITXbBY5axFA+CpxZqokpAgMBAAGj\nggKmMIICojCCAQUGCCsGAQUFBwEBBIH4MIH1MEEGCCsGAQUFBzAChjVsZGFwOi8v\nYWwuc2llbWVucy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/Y0FDZXJ0aWZpY2F0ZTAy\nBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBMS5j\ncnQwSgYIKwYBBQUHMAKGPmxkYXA6Ly9hbC5zaWVtZW5zLmNvbS91aWQ9WlpaWlpa\nQTEsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRw\nOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAUcG2g\nUOyp0CxnnRkV/v0EczXD4tQwEgYDVR0TAQH/BAgwBgEB/wIBADBABgNVHSAEOTA3\nMDUGCCsGAQQBoWkHMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuc2llbWVucy5j\nb20vcGtpLzCBxwYDVR0fBIG/MIG8MIG5oIG2oIGzhj9sZGFwOi8vY2wuc2llbWVu\ncy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/YXV0aG9yaXR5UmV2b2NhdGlvbkxpc3SG\nJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTEuY3JshkhsZGFwOi8v\nY2wuc2llbWVucy5jb20vdWlkPVpaWlpaWkExLG89VHJ1c3RjZW50ZXI/YXV0aG9y\naXR5UmV2b2NhdGlvbkxpc3QwJwYDVR0lBCAwHgYIKwYBBQUHAwIGCCsGAQUFBwME\nBggrBgEFBQcDCTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPgVXUcMbHd7csQC\nF5Foorb3aglEMA0GCSqGSIb3DQEBCwUAA4ICAQBw+sqMp3SS7DVKcILEmXbdRAg3\nlLO1r457KY+YgCT9uX4VG5EdRKcGfWXK6VHGCi4Dos5eXFV34Mq/p8nu1sqMuoGP\nYjHn604eWDprhGy6GrTYdxzcE/GGHkpkuE3Ir/45UcmZlOU41SJ9SNjuIVrSHMOf\nccSY42BCspR/Q1Z/ykmIqQecdT3/Kkx02GzzSN2+HlW6cEO4GBW5RMqsvd2n0h2d\nfe2zcqOgkLtx7u2JCR/U77zfyxG3qXtcymoz0wgSHcsKIl+GUjITLkHfS9Op8V7C\nGr/dX437sIg5pVHmEAWadjkIzqdHux+EF94Z6kaHywohc1xG0KvPYPX7iSNjkvhz\n4NY53DHmxl4YEMLffZnaS/dqyhe1GTpcpyN8WiR4KuPfxrkVDOsuzWFtMSvNdlOV\ngdI0MXcLMP+EOeANZWX6lGgJ3vWyemo58nzgshKd24MY3w3i6masUkxJH2KvI7UH\n/1Db3SC8oOUjInvSRej6M3ZhYWgugm6gbpUgFoDw/o9Cg6Qm71hY0JtcaPC13rzm\nN8a2Br0+Fa5e2VhwLmAxyfe1JKzqPwuHT0S5u05SQghL5VdzqfA8FCL/j4XC9yI6\ncsZTAQi73xFQYVjZt3+aoSz84lOlTmVo/jgvGMY/JzH9I4mETGgAJRNj34Z/0meh\nM+pKWCojNH/dgyJSwDGCAlIwggJOAgEBMIG/MIG2MQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2llbWVu\nczERMA8GA1UEBRMIWlpaWlpaQTYxHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3QgQ2Vu\ndGVyMT8wPQYDVQQDDDZTaWVtZW5zIElzc3VpbmcgQ0EgTWVkaXVtIFN0cmVuZ3Ro\nIEF1dGhlbnRpY2F0aW9uIDIwMTYCBBXXLOIwCwYJYIZIAWUDBAIBoGkwHAYJKoZI\nhvcNAQkFMQ8XDTE5MTEyMDE0NTYyMFowLwYJKoZIhvcNAQkEMSIEIJDnZUpcVLzC\nOdtpkH8gtxwLPIDE0NmAmFC9uM8q2z+OMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0B\nBwEwCwYJKoZIhvcNAQEBBIIBAH/Pqv2xp3a0jSPkwU1K3eGA/1lfoNJMUny4d/PS\nLVWlkgrmedXdLmuBzAGEaaZOJS0lEpNd01pR/reHs7xxZ+RZ0olTs2ufM0CijQSx\nOL9HDl2O3OoD77NWx4tl3Wy1yJCeV3XH/cEI7AkKHCmKY9QMoMYWh16ORBtr+YcS\nYK+gONOjpjgcgTJgZ3HSFgQ50xiD4WT1kFBHsuYsLqaOSbTfTN6Ayyg4edjrPQqa\nVcVf1OQcIrfWA3yMQrnEZfOYfN/D4EPjTfxBV+VCi/F2bdZmMbJ7jNk1FbewSwWO\nSDH1i0K32NyFbnh0BSos7njq7ELqKlYBsoB/sZfaH2vKy5U=\n-----END SIGNED MESSAGE-----"), + MessageSize: 6494, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Roger Meier"), + Email: []byte("r.meier@siemens.com"), + Date: ×tamppb.Timestamp{Seconds: 1574261780}, + Timezone: []byte("+0100"), + }, + SignatureType: gitalypb.SignatureType_X509, + }, + { + Name: []byte("pgp-long-tag-message"), + Id: truncatedPGPTagID, + TargetCommit: gitCommit, // 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + Message: truncatedPGPTagMsg[146:10386], // first 10240 bytes of tag message + MessageSize: 11148, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1393491261}, + Timezone: []byte("+0100"), + }, + SignatureType: gitalypb.SignatureType_PGP, + }, + { + Name: []byte("v1.2.0"), + Id: annotatedTagID.String(), + Message: []byte("Blob tag"), + MessageSize: 8, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + }, + { + Name: []byte("v1.3.0"), + Id: commitID.String(), + TargetCommit: gitCommit, + }, + { + Name: []byte("v1.4.0"), + Id: blobID.String(), + }, + { + Name: []byte("v1.5.0"), + Id: commitID.String(), + TargetCommit: gitCommit, + }, + { + Name: []byte("v1.6.0"), + Id: bigCommitID.String(), + TargetCommit: bigCommit, + }, + { + Name: []byte("v1.7.0"), + Id: bigMessageTag1ID.String(), + Message: []byte(bigMessage[:helper.MaxCommitOrTagMessageSize]), + MessageSize: int64(len(bigMessage)), + TargetCommit: gitCommit, + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + }, + } + + require.Len(t, receivedTags, len(expectedTags)) + require.ElementsMatch(t, expectedTags, receivedTags) +} + +func TestFindAllTags_simpleNestedTags(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + ctx, cancel := testhelper.Context() + defer cancel() + + repoProto, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) + + commitID := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithParents(), + ) + + tagID := gittest.WriteTag(t, cfg, repoPath, "my/nested/tag", commitID.Revision()) + + stream, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{Repository: repoProto}) + require.NoError(t, err) + + response, err := stream.Recv() + require.NoError(t, err) + testassert.ProtoEqual(t, &gitalypb.FindAllTagsResponse{ + Tags: []*gitalypb.Tag{ + { + Name: []byte("my/nested/tag"), + Id: tagID.String(), + TargetCommit: &gitalypb.GitCommit{ + Id: commitID.String(), + Body: []byte("message"), + BodySize: 7, + Subject: []byte("message"), + TreeId: git.EmptyTreeOID.String(), + Author: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + Committer: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + }, + }, + }, + }, response) + + response, err = stream.Recv() + require.Equal(t, io.EOF, err) + require.Nil(t, response) +} + +func TestFindAllTags_duplicateAnnotatedTags(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + repoProto, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) + repo := localrepo.NewTestRepo(t, cfg, repoProto) + + ctx, cancel := testhelper.Context() + defer cancel() + + commitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents()) + date := time.Unix(12345, 0) + + tagID, err := repo.WriteTag(ctx, commitID, "commit", []byte("annotated"), []byte("message"), + gittest.TestUser, date) + require.NoError(t, err) + + require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated", tagID, git.ZeroOID)) + require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated-dup", tagID, git.ZeroOID)) + require.NoError(t, repo.UpdateRef(ctx, "refs/tags/lightweight-1", commitID, git.ZeroOID)) + require.NoError(t, repo.UpdateRef(ctx, "refs/tags/lightweight-2", commitID, git.ZeroOID)) + + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{Repository: repoProto}) + require.NoError(t, err) + + var receivedTags []*gitalypb.Tag + for { + r, err := c.Recv() + if err == io.EOF { + break + } + require.NoError(t, err) + receivedTags = append(receivedTags, r.GetTags()...) + } + + commitAuthor := &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + } + commit := &gitalypb.GitCommit{ + Id: commitID.String(), + TreeId: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + Body: []byte("message"), + BodySize: 7, + Subject: []byte("message"), + Author: commitAuthor, + Committer: commitAuthor, + } + + testassert.ProtoEqual(t, []*gitalypb.Tag{ + { + Name: []byte("annotated"), + Id: tagID.String(), + Message: []byte("message"), + MessageSize: 7, + Tagger: &gitalypb.CommitAuthor{ + Name: gittest.TestUser.Name, + Email: gittest.TestUser.Email, + Date: timestamppb.New(date), + Timezone: []byte("+0000"), + }, + TargetCommit: commit, + }, + { + Name: []byte("annotated-dup"), + Id: tagID.String(), + Message: []byte("message"), + MessageSize: 7, + Tagger: &gitalypb.CommitAuthor{ + Name: gittest.TestUser.Name, + Email: gittest.TestUser.Email, + Date: timestamppb.New(date), + Timezone: []byte("+0000"), + }, + TargetCommit: commit, + }, + {Name: []byte("lightweight-1"), Id: commitID.String(), TargetCommit: commit}, + {Name: []byte("lightweight-2"), Id: commitID.String(), TargetCommit: commit}, + }, receivedTags) +} + +func TestFindAllTags_nestedTags(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + ctx, cancel := testhelper.Context() + defer cancel() + + repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) + repo := localrepo.NewTestRepo(t, cfg, repoProto) + + blobID := git.ObjectID("faaf198af3a36dbf41961466703cc1d47c61d051") + commitID := git.ObjectID("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") + + testCases := []struct { + description string + depth int + originalOid git.ObjectID + }{ + { + description: "nested 1 deep, points to a commit", + depth: 1, + originalOid: commitID, + }, + { + description: "nested 4 deep, points to a commit", + depth: 4, + originalOid: commitID, + }, + { + description: "nested 3 deep, points to a blob", + depth: 3, + originalOid: blobID, + }, + { + description: "nested 20 deep, points to a commit", + depth: 20, + originalOid: commitID, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + tags := bytes.NewReader(gittest.Exec(t, cfg, "-C", repoPath, "tag")) + testhelper.MustRunCommand(t, tags, "xargs", cfg.Git.BinPath, "-C", repoPath, "tag", "-d") + + catfileCache := catfile.NewCache(cfg) + batch, err := catfileCache.BatchProcess(ctx, repo) + require.NoError(t, err) + + info, err := batch.Info(ctx, git.Revision(tc.originalOid)) + require.NoError(t, err) + + expectedTags := make(map[string]*gitalypb.Tag) + tagID := tc.originalOid + + for depth := 0; depth < tc.depth; depth++ { + tagName := fmt.Sprintf("tag-depth-%d", depth) + tagMessage := fmt.Sprintf("a commit %d deep", depth) + tagID = gittest.WriteTag(t, cfg, repoPath, tagName, tagID.Revision(), gittest.WriteTagConfig{Message: tagMessage}) + + expectedTag := &gitalypb.Tag{ + Name: []byte(tagName), + Id: tagID.String(), + Message: []byte(tagMessage), + MessageSize: int64(len([]byte(tagMessage))), + Tagger: &gitalypb.CommitAuthor{ + Name: []byte("Scrooge McDuck"), + Email: []byte("scrooge@mcduck.com"), + Date: ×tamppb.Timestamp{Seconds: 1572776879}, + Timezone: []byte("+0100"), + }, + } + + if info.Type == "commit" { + commit, err := catfile.GetCommit(ctx, batch, git.Revision(tc.originalOid)) + require.NoError(t, err) + expectedTag.TargetCommit = commit + } + + expectedTags[string(expectedTag.Name)] = expectedTag + } + + rpcRequest := &gitalypb.FindAllTagsRequest{Repository: repoProto} + + c, err := client.FindAllTags(ctx, rpcRequest) + require.NoError(t, err) + + var receivedTags []*gitalypb.Tag + for { + r, err := c.Recv() + if err == io.EOF { + break + } + require.NoError(t, err) + receivedTags = append(receivedTags, r.GetTags()...) + } + + require.Len(t, receivedTags, len(expectedTags)) + for _, receivedTag := range receivedTags { + assert.Equal(t, expectedTags[string(receivedTag.Name)], receivedTag) + } + }) + } +} + +func TestFindAllTags_invalidRequest(t *testing.T) { + _, client := setupRefServiceWithoutRepo(t) + + ctx, cancel := testhelper.Context() + defer cancel() + + testCases := []struct { + desc string + request *gitalypb.FindAllTagsRequest + }{ + { + desc: "empty request", + request: &gitalypb.FindAllTagsRequest{}, + }, + { + desc: "invalid repo", + request: &gitalypb.FindAllTagsRequest{ + Repository: &gitalypb.Repository{ + StorageName: "fake", + RelativePath: "repo", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + c, err := client.FindAllTags(ctx, tc.request) + require.NoError(t, err) + + var recvError error + for recvError == nil { + _, recvError = c.Recv() + } + + testhelper.RequireGrpcError(t, recvError, codes.InvalidArgument) + }) + } +} + +func TestFindAllTags_sorted(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + ctx, cancel := testhelper.Context() + defer cancel() + + repoProto, _ := gittest.CloneRepo(t, cfg, cfg.Storages[0]) + + repo := localrepo.New(git.NewExecCommandFactory(cfg), catfile.NewCache(cfg), repoProto, cfg) + headCommit, err := repo.ReadCommit(ctx, "HEAD") + require.NoError(t, err) + annotatedTagID, err := repo.WriteTag(ctx, git.ObjectID(headCommit.Id), "commit", []byte("annotated"), []byte("message"), gittest.TestUser, time.Now()) + require.NoError(t, err) + require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated", annotatedTagID, git.ZeroOID)) + + require.NoError(t, repo.ExecAndWait(ctx, git.SubCmd{ + Name: "tag", + Args: []string{"not-annotated", headCommit.Id}, + }, git.WithDisabledHooks())) + + for _, tc := range []struct { + desc string + sortBy *gitalypb.FindAllTagsRequest_SortBy + exp []string + }{ + { + desc: "by name", + sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME}, + exp: []string{ + annotatedTagID.String(), + headCommit.Id, + "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + "8f03acbcd11c53d9c9468078f32a2622005a4841", + }, + }, + { + desc: "by updated in ascending order", + sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_CREATORDATE, Direction: gitalypb.SortDirection_ASCENDING}, + exp: []string{ + "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + headCommit.Id, + "8f03acbcd11c53d9c9468078f32a2622005a4841", + annotatedTagID.String(), + }, + }, + { + desc: "by updated in descending order", + sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_CREATORDATE, Direction: gitalypb.SortDirection_DESCENDING}, + exp: []string{ + annotatedTagID.String(), + "8f03acbcd11c53d9c9468078f32a2622005a4841", + headCommit.Id, + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ + Repository: repoProto, + SortBy: tc.sortBy, + }) + require.NoError(t, err) + + var tags []string + for { + r, err := c.Recv() + if err == io.EOF { + break + } + require.NoError(t, err) + for _, tag := range r.GetTags() { + tags = append(tags, tag.Id) + } + } + + require.Equal(t, tc.exp, tags) + }) + } + + t.Run("by unsupported key", func(t *testing.T) { + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ + Repository: repoProto, + SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_Key(-1)}, + }) + require.NoError(t, err) + r, err := c.Recv() + testassert.GrpcEqualErr(t, status.Error(codes.InvalidArgument, "unsupported sorting key: -1"), err) + require.Nil(t, r) + }) + + t.Run("by unsupported direction", func(t *testing.T) { + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ + Repository: repoProto, + SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME, Direction: gitalypb.SortDirection(-1)}, + }) + require.NoError(t, err) + r, err := c.Recv() + testassert.GrpcEqualErr(t, status.Error(codes.InvalidArgument, "unsupported sorting direction: -1"), err) + require.Nil(t, r) + }) + + t.Run("no tags", func(t *testing.T) { + repoProto, _ := gittest.InitRepo(t, cfg, cfg.Storages[0]) + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ + Repository: repoProto, + SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME}, + }) + require.NoError(t, err) + + r, err := c.Recv() + require.Equal(t, io.EOF, err) + require.Nil(t, r) + }) +} diff --git a/internal/gitaly/service/ref/refs.go b/internal/gitaly/service/ref/refs.go index 7e149b674..c9fd8d56a 100644 --- a/internal/gitaly/service/ref/refs.go +++ b/internal/gitaly/service/ref/refs.go @@ -6,21 +6,15 @@ import ( "context" "errors" "fmt" - "io" - "io/ioutil" "math" "strings" "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" - "gitlab.com/gitlab-org/gitaly/v14/internal/git/gitpipe" - "gitlab.com/gitlab-org/gitaly/v14/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v14/internal/helper" - "gitlab.com/gitlab-org/gitaly/v14/internal/helper/chunk" "gitlab.com/gitlab-org/gitaly/v14/internal/helper/lines" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" - "google.golang.org/protobuf/proto" ) const ( @@ -62,205 +56,6 @@ func (s *server) findRefs(ctx context.Context, writer lines.Sender, repo git.Rep return cmd.Wait() } -type tagSender struct { - tags []*gitalypb.Tag - stream gitalypb.RefService_FindAllTagsServer -} - -func (t *tagSender) Reset() { - t.tags = t.tags[:0] -} - -func (t *tagSender) Append(m proto.Message) { - t.tags = append(t.tags, m.(*gitalypb.Tag)) -} - -func (t *tagSender) Send() error { - return t.stream.Send(&gitalypb.FindAllTagsResponse{ - Tags: t.tags, - }) -} - -func (s *server) FindAllTags(in *gitalypb.FindAllTagsRequest, stream gitalypb.RefService_FindAllTagsServer) error { - ctx := stream.Context() - - if err := s.validateFindAllTagsRequest(in); err != nil { - return helper.ErrInvalidArgument(err) - } - - sortField, err := getTagSortField(in.GetSortBy()) - if err != nil { - return helper.ErrInvalidArgument(err) - } - - repo := s.localrepo(in.GetRepository()) - - if err := s.findAllTags(ctx, repo, sortField, stream); err != nil { - return helper.ErrInternal(err) - } - - return nil -} - -func (s *server) findAllTags(ctx context.Context, repo *localrepo.Repo, sortField string, stream gitalypb.RefService_FindAllTagsServer) error { - c, err := s.catfileCache.BatchProcess(ctx, repo) - if err != nil { - return fmt.Errorf("error creating catfile: %v", err) - } - - forEachRefIter := gitpipe.ForEachRef(ctx, repo, []string{"refs/tags/"}, sortField) - forEachRefIter = gitpipe.RevisionTransform(ctx, forEachRefIter, - func(r gitpipe.RevisionResult) []gitpipe.RevisionResult { - // We transform the pipeline to include each tag-reference twice: once for - // the "normal" object, and once we opportunistically peel the object to a - // non-tag object. This is required such that we can efficiently parse the - // tagged object. - return []gitpipe.RevisionResult{ - r, - {OID: r.OID + "^{}"}, - } - }, - ) - - catfileInfoIter := gitpipe.CatfileInfo(ctx, c, forEachRefIter) - - // In the previous pipeline step, we request information about both the object and the - // peeled object in case the object is a tag. Given that we now know about object types, we - // can filter out the second request in case the object is not a tag: peeling a non-tag - // object to a non-tag object is always going to end up with the same object anyway. And - // requesting the same object twice is moot. - type state int - const ( - // stateTag indicates that the next object is going to be a tag. - stateTag = state(iota) - // statePeeledTag indicates that the next object is going to be the peeled object of - // the preceding tag. - statePeeledTag - // stateSkip indicates that the next object shall be skipped because it is the - // peeled version of a non-tag object, which is the same object anyway. - stateSkip - ) - - currentState := stateTag - catfileInfoIter = gitpipe.CatfileInfoFilter(ctx, catfileInfoIter, - func(r gitpipe.CatfileInfoResult) bool { - switch currentState { - case stateTag: - // If we've got a tag, then we want to also see its peeled object. - // Otherwise, we can skip over the peeled object. - currentState = statePeeledTag - if r.ObjectInfo.Type != "tag" { - currentState = stateSkip - } - return true - case statePeeledTag: - currentState = stateTag - return true - case stateSkip: - currentState = stateTag - return false - } - - // We could try to gracefully handle this, but I don't see much of a point - // given that we can see above that it's never going to be anything else but - // a known state. - panic("invalid state") - }, - ) - - catfileObjectsIter := gitpipe.CatfileObject(ctx, c, catfileInfoIter) - - chunker := chunk.New(&tagSender{stream: stream}) - - for catfileObjectsIter.Next() { - tag := catfileObjectsIter.Result() - - var result *gitalypb.Tag - switch tag.ObjectInfo.Type { - case "tag": - var err error - result, err = catfile.ParseTag(tag.ObjectReader, tag.ObjectInfo.Oid) - if err != nil { - return fmt.Errorf("parsing annotated tag: %w", err) - } - - // For each tag, we expect both the tag itself as well as its - // potentially-peeled tagged object. - if !catfileObjectsIter.Next() { - return errors.New("expected peeled tag") - } - - peeledTag := catfileObjectsIter.Result() - - // We only need to parse the tagged object in case we have an annotated tag - // which refers to a commit object. Otherwise, we discard the object's - // contents. - if peeledTag.ObjectInfo.Type == "commit" { - result.TargetCommit, err = catfile.ParseCommit(peeledTag.ObjectReader, peeledTag.ObjectInfo.Oid) - if err != nil { - return fmt.Errorf("parsing tagged commit: %w", err) - } - } else { - if _, err := io.Copy(ioutil.Discard, peeledTag.ObjectReader); err != nil { - return fmt.Errorf("discarding tagged object contents: %w", err) - } - } - case "commit": - commit, err := catfile.ParseCommit(tag.ObjectReader, tag.ObjectInfo.Oid) - if err != nil { - return fmt.Errorf("parsing tagged commit: %w", err) - } - - result = &gitalypb.Tag{ - Id: tag.ObjectInfo.Oid.String(), - TargetCommit: commit, - } - default: - if _, err := io.Copy(ioutil.Discard, tag.ObjectReader); err != nil { - return fmt.Errorf("discarding tag object contents: %w", err) - } - - result = &gitalypb.Tag{ - Id: tag.ObjectInfo.Oid.String(), - } - } - - // In case we can deduce the tag name from the object name (which should typically - // be the case), we always want to return the tag name. While annotated tags do have - // their name encoded in the object itself, we instead want to default to the name - // of the reference such that we can discern multiple refs pointing to the same tag. - if tagName := bytes.TrimPrefix(tag.ObjectName, []byte("refs/tags/")); len(tagName) > 0 { - result.Name = tagName - } - - if err := chunker.Send(result); err != nil { - return fmt.Errorf("sending tag: %w", err) - } - } - - if err := catfileObjectsIter.Err(); err != nil { - return fmt.Errorf("iterating over tags: %w", err) - } - - if err := chunker.Flush(); err != nil { - return fmt.Errorf("flushing chunker: %w", err) - } - - return nil -} - -func (s *server) validateFindAllTagsRequest(request *gitalypb.FindAllTagsRequest) error { - if request.GetRepository() == nil { - return errors.New("empty Repository") - } - - if _, err := s.locator.GetRepoPath(request.GetRepository()); err != nil { - return fmt.Errorf("invalid git directory: %v", err) - } - - return nil -} - // FindBranchNames returns all branch names. // // Deprecated: Use localrepo.Repo.GetBranches instead. diff --git a/internal/gitaly/service/ref/refs_test.go b/internal/gitaly/service/ref/refs_test.go index cbfb7383c..d7560ec4c 100644 --- a/internal/gitaly/service/ref/refs_test.go +++ b/internal/gitaly/service/ref/refs_test.go @@ -7,9 +7,7 @@ import ( "io" "strings" "testing" - "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/catfile" @@ -22,7 +20,6 @@ import ( "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -313,599 +310,6 @@ func TestInvalidRepoFindDefaultBranchNameRequest(t *testing.T) { } } -func TestSuccessfulFindAllTagsRequest(t *testing.T) { - cfg, client := setupRefServiceWithoutRepo(t) - - repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - repo := localrepo.NewTestRepo(t, cfg, repoProto) - - ctx, cancel := testhelper.Context() - defer cancel() - - // reconstruct the v1.1.2 tag from patches to test truncated tag message - // with partial PGP block - truncatedPGPTagMsg := testhelper.MustReadFile(t, "testdata/truncated_pgp_msg.patch") - - truncatedPGPTagID := string(gittest.ExecStream(t, cfg, bytes.NewBuffer(truncatedPGPTagMsg), "-C", repoPath, "mktag")) - truncatedPGPTagID = strings.TrimSpace(truncatedPGPTagID) // remove trailing newline - gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/tags/pgp-long-tag-message", truncatedPGPTagID) - - blobID := "faaf198af3a36dbf41961466703cc1d47c61d051" - commitID := "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" - - gitCommit := testhelper.GitLabTestCommit(commitID) - - bigCommitID := gittest.WriteCommit(t, cfg, repoPath, - gittest.WithBranch("local-big-commits"), - gittest.WithMessage("An empty commit with REALLY BIG message\n\n"+strings.Repeat("a", helper.MaxCommitOrTagMessageSize+1)), - gittest.WithParents("60ecb67744cb56576c30214ff52294f8ce2def98"), - ) - bigCommit, err := repo.ReadCommit(ctx, git.Revision(bigCommitID)) - require.NoError(t, err) - - annotatedTagID := gittest.CreateTag(t, cfg, repoPath, "v1.2.0", blobID, &gittest.CreateTagOpts{Message: "Blob tag"}) - - gittest.CreateTag(t, cfg, repoPath, "v1.3.0", commitID, nil) - gittest.CreateTag(t, cfg, repoPath, "v1.4.0", blobID, nil) - - // To test recursive resolving to a commit - gittest.CreateTag(t, cfg, repoPath, "v1.5.0", "v1.3.0", nil) - - // A tag to commit with a big message - gittest.CreateTag(t, cfg, repoPath, "v1.6.0", bigCommitID.String(), nil) - - // A tag with a big message - bigMessage := strings.Repeat("a", 11*1024) - bigMessageTag1ID := gittest.CreateTag(t, cfg, repoPath, "v1.7.0", commitID, &gittest.CreateTagOpts{Message: bigMessage}) - - // A tag with a commit id as its name - commitTagID := gittest.CreateTag(t, cfg, repoPath, commitID, commitID, &gittest.CreateTagOpts{Message: "commit tag with a commit sha as the name"}) - - // a tag of a tag - tagOfTagID := gittest.CreateTag(t, cfg, repoPath, "tag-of-tag", commitTagID, &gittest.CreateTagOpts{Message: "tag of a tag"}) - - rpcRequest := &gitalypb.FindAllTagsRequest{Repository: repoProto} - - c, err := client.FindAllTags(ctx, rpcRequest) - require.NoError(t, err) - - var receivedTags []*gitalypb.Tag - for { - r, err := c.Recv() - if err == io.EOF { - break - } - require.NoError(t, err) - receivedTags = append(receivedTags, r.GetTags()...) - } - - expectedTags := []*gitalypb.Tag{ - { - Name: []byte(commitID), - Id: commitTagID, - TargetCommit: gitCommit, - Message: []byte("commit tag with a commit sha as the name"), - MessageSize: 40, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - }, - { - Name: []byte("tag-of-tag"), - Id: tagOfTagID, - TargetCommit: gitCommit, - Message: []byte("tag of a tag"), - MessageSize: 12, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - }, - { - Name: []byte("v1.0.0"), - Id: "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", - TargetCommit: gitCommit, - Message: []byte("Release"), - MessageSize: 7, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Dmitriy Zaporozhets"), - Email: []byte("dmitriy.zaporozhets@gmail.com"), - Date: ×tamppb.Timestamp{Seconds: 1393491299}, - Timezone: []byte("+0200"), - }, - }, - { - Name: []byte("v1.1.0"), - Id: "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", - TargetCommit: testhelper.GitLabTestCommit("5937ac0a7beb003549fc5fd26fc247adbce4a52e"), - Message: []byte("Version 1.1.0"), - MessageSize: 13, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Dmitriy Zaporozhets"), - Email: []byte("dmitriy.zaporozhets@gmail.com"), - Date: ×tamppb.Timestamp{Seconds: 1393505709}, - Timezone: []byte("+0200"), - }, - }, - { - Name: []byte("v1.1.1"), - Id: "8f03acbcd11c53d9c9468078f32a2622005a4841", - TargetCommit: testhelper.GitLabTestCommit("189a6c924013fc3fe40d6f1ec1dc20214183bc97"), - Message: []byte("x509 signed tag\n-----BEGIN SIGNED MESSAGE-----\nMIISfwYJKoZIhvcNAQcCoIIScDCCEmwCAQExDTALBglghkgBZQMEAgEwCwYJKoZI\nhvcNAQcBoIIP8zCCB3QwggVcoAMCAQICBBXXLOIwDQYJKoZIhvcNAQELBQAwgbYx\nCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVu\nMRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwU\nU2llbWVucyBUcnVzdCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBD\nQSBNZWRpdW0gU3RyZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjAeFw0xNzAyMDMw\nNjU4MzNaFw0yMDAyMDMwNjU4MzNaMFsxETAPBgNVBAUTCFowMDBOV0RIMQ4wDAYD\nVQQqDAVSb2dlcjEOMAwGA1UEBAwFTWVpZXIxEDAOBgNVBAoMB1NpZW1lbnMxFDAS\nBgNVBAMMC01laWVyIFJvZ2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAuBNea/68ZCnHYQjpm/k3ZBG0wBpEKSwG6lk9CEQlSxsqVLQHAoAKBIlJm1in\nYVLcK/Sq1yhYJ/qWcY/M53DhK2rpPuhtrWJUdOUy8EBWO20F4bd4Fw9pO7jt8bme\nu33TSrK772vKjuppzB6SeG13Cs08H+BIeD106G27h7ufsO00pvsxoSDL+uc4slnr\npBL+2TAL7nSFnB9QHWmRIK27SPqJE+lESdb0pse11x1wjvqKy2Q7EjL9fpqJdHzX\nNLKHXd2r024TOORTa05DFTNR+kQEKKV96XfpYdtSBomXNQ44cisiPBJjFtYvfnFE\nwgrHa8fogn/b0C+A+HAoICN12wIDAQABo4IC4jCCAt4wHQYDVR0OBBYEFCF+gkUp\nXQ6xGc0kRWXuDFxzA14zMEMGA1UdEQQ8MDqgIwYKKwYBBAGCNxQCA6AVDBNyLm1l\naWVyQHNpZW1lbnMuY29tgRNyLm1laWVyQHNpZW1lbnMuY29tMA4GA1UdDwEB/wQE\nAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwgcoGA1UdHwSBwjCB\nvzCBvKCBuaCBtoYmaHR0cDovL2NoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5j\ncmyGQWxkYXA6Ly9jbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jZXJ0\naWZpY2F0ZVJldm9jYXRpb25MaXN0hklsZGFwOi8vY2wuc2llbWVucy5jb20vQ049\nWlpaWlpaQTYsbz1UcnVzdGNlbnRlcj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0\nMEUGA1UdIAQ+MDwwOgYNKwYBBAGhaQcCAgMBAzApMCcGCCsGAQUFBwIBFhtodHRw\nOi8vd3d3LnNpZW1lbnMuY29tL3BraS8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW\ngBT4FV1HDGx3e3LEAheRaKK292oJRDCCAQQGCCsGAQUFBwEBBIH3MIH0MDIGCCsG\nAQUFBzAChiZodHRwOi8vYWguc2llbWVucy5jb20vcGtpP1paWlpaWkE2LmNydDBB\nBggrBgEFBQcwAoY1bGRhcDovL2FsLnNpZW1lbnMubmV0L0NOPVpaWlpaWkE2LEw9\nUEtJP2NBQ2VydGlmaWNhdGUwSQYIKwYBBQUHMAKGPWxkYXA6Ly9hbC5zaWVtZW5z\nLmNvbS9DTj1aWlpaWlpBNixvPVRydXN0Y2VudGVyP2NBQ2VydGlmaWNhdGUwMAYI\nKwYBBQUHMAGGJGh0dHA6Ly9vY3NwLnBraS1zZXJ2aWNlcy5zaWVtZW5zLmNvbTAN\nBgkqhkiG9w0BAQsFAAOCAgEAXPVcX6vaEcszJqg5IemF9aFTlwTrX5ITNIpzcqG+\nkD5haOf2mZYLjl+MKtLC1XfmIsGCUZNb8bjP6QHQEI+2d6x/ZOqPq7Kd7PwVu6x6\nxZrkDjUyhUbUntT5+RBy++l3Wf6Cq6Kx+K8ambHBP/bu90/p2U8KfFAG3Kr2gI2q\nfZrnNMOxmJfZ3/sXxssgLkhbZ7hRa+MpLfQ6uFsSiat3vlawBBvTyHnoZ/7oRc8y\nqi6QzWcd76CPpMElYWibl+hJzKbBZUWvc71AzHR6i1QeZ6wubYz7vr+FF5Y7tnxB\nVz6omPC9XAg0F+Dla6Zlz3Awj5imCzVXa+9SjtnsidmJdLcKzTAKyDewewoxYOOJ\nj3cJU7VSjJPl+2fVmDBaQwcNcUcu/TPAKApkegqO7tRF9IPhjhW8QkRnkqMetO3D\nOXmAFVIsEI0Hvb2cdb7B6jSpjGUuhaFm9TCKhQtCk2p8JCDTuaENLm1x34rrJKbT\n2vzyYN0CZtSkUdgD4yQxK9VWXGEzexRisWb4AnZjD2NAquLPpXmw8N0UwFD7MSpC\ndpaX7FktdvZmMXsnGiAdtLSbBgLVWOD1gmJFDjrhNbI8NOaOaNk4jrfGqNh5lhGU\n4DnBT2U6Cie1anLmFH/oZooAEXR2o3Nu+1mNDJChnJp0ovs08aa3zZvBdcloOvfU\nqdowggh3MIIGX6ADAgECAgQtyi/nMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYDVQQG\nEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UE\nCgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaQTExHTAbBgNVBAsMFFNpZW1lbnMg\nVHJ1c3QgQ2VudGVyMSIwIAYDVQQDDBlTaWVtZW5zIFJvb3QgQ0EgVjMuMCAyMDE2\nMB4XDTE2MDcyMDEzNDYxMFoXDTIyMDcyMDEzNDYxMFowgbYxCzAJBgNVBAYTAkRF\nMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQKDAdT\naWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVz\ndCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3Ry\nZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAL9UfK+JAZEqVMVvECdYF9IK4KSw34AqyNl3rYP5x03dtmKaNu+2\n0fQqNESA1NGzw3s6LmrKLh1cR991nB2cvKOXu7AvEGpSuxzIcOROd4NpvRx+Ej1p\nJIPeqf+ScmVK7lMSO8QL/QzjHOpGV3is9sG+ZIxOW9U1ESooy4Hal6ZNs4DNItsz\npiCKqm6G3et4r2WqCy2RRuSqvnmMza7Y8BZsLy0ZVo5teObQ37E/FxqSrbDI8nxn\nB7nVUve5ZjrqoIGSkEOtyo11003dVO1vmWB9A0WQGDqE/q3w178hGhKfxzRaqzyi\nSoADUYS2sD/CglGTUxVq6u0pGLLsCFjItcCWqW+T9fPYfJ2CEd5b3hvqdCn+pXjZ\n/gdX1XAcdUF5lRnGWifaYpT9n4s4adzX8q6oHSJxTppuAwLRKH6eXALbGQ1I9lGQ\nDSOipD/09xkEsPw6HOepmf2U3YxZK1VU2sHqugFJboeLcHMzp6E1n2ctlNG1GKE9\nFDHmdyFzDi0Nnxtf/GgVjnHF68hByEE1MYdJ4nJLuxoT9hyjYdRW9MpeNNxxZnmz\nW3zh7QxIqP0ZfIz6XVhzrI9uZiqwwojDiM5tEOUkQ7XyW6grNXe75yt6mTj89LlB\nH5fOW2RNmCy/jzBXDjgyskgK7kuCvUYTuRv8ITXbBY5axFA+CpxZqokpAgMBAAGj\nggKmMIICojCCAQUGCCsGAQUFBwEBBIH4MIH1MEEGCCsGAQUFBzAChjVsZGFwOi8v\nYWwuc2llbWVucy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/Y0FDZXJ0aWZpY2F0ZTAy\nBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBMS5j\ncnQwSgYIKwYBBQUHMAKGPmxkYXA6Ly9hbC5zaWVtZW5zLmNvbS91aWQ9WlpaWlpa\nQTEsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRw\nOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAUcG2g\nUOyp0CxnnRkV/v0EczXD4tQwEgYDVR0TAQH/BAgwBgEB/wIBADBABgNVHSAEOTA3\nMDUGCCsGAQQBoWkHMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuc2llbWVucy5j\nb20vcGtpLzCBxwYDVR0fBIG/MIG8MIG5oIG2oIGzhj9sZGFwOi8vY2wuc2llbWVu\ncy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/YXV0aG9yaXR5UmV2b2NhdGlvbkxpc3SG\nJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTEuY3JshkhsZGFwOi8v\nY2wuc2llbWVucy5jb20vdWlkPVpaWlpaWkExLG89VHJ1c3RjZW50ZXI/YXV0aG9y\naXR5UmV2b2NhdGlvbkxpc3QwJwYDVR0lBCAwHgYIKwYBBQUHAwIGCCsGAQUFBwME\nBggrBgEFBQcDCTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPgVXUcMbHd7csQC\nF5Foorb3aglEMA0GCSqGSIb3DQEBCwUAA4ICAQBw+sqMp3SS7DVKcILEmXbdRAg3\nlLO1r457KY+YgCT9uX4VG5EdRKcGfWXK6VHGCi4Dos5eXFV34Mq/p8nu1sqMuoGP\nYjHn604eWDprhGy6GrTYdxzcE/GGHkpkuE3Ir/45UcmZlOU41SJ9SNjuIVrSHMOf\nccSY42BCspR/Q1Z/ykmIqQecdT3/Kkx02GzzSN2+HlW6cEO4GBW5RMqsvd2n0h2d\nfe2zcqOgkLtx7u2JCR/U77zfyxG3qXtcymoz0wgSHcsKIl+GUjITLkHfS9Op8V7C\nGr/dX437sIg5pVHmEAWadjkIzqdHux+EF94Z6kaHywohc1xG0KvPYPX7iSNjkvhz\n4NY53DHmxl4YEMLffZnaS/dqyhe1GTpcpyN8WiR4KuPfxrkVDOsuzWFtMSvNdlOV\ngdI0MXcLMP+EOeANZWX6lGgJ3vWyemo58nzgshKd24MY3w3i6masUkxJH2KvI7UH\n/1Db3SC8oOUjInvSRej6M3ZhYWgugm6gbpUgFoDw/o9Cg6Qm71hY0JtcaPC13rzm\nN8a2Br0+Fa5e2VhwLmAxyfe1JKzqPwuHT0S5u05SQghL5VdzqfA8FCL/j4XC9yI6\ncsZTAQi73xFQYVjZt3+aoSz84lOlTmVo/jgvGMY/JzH9I4mETGgAJRNj34Z/0meh\nM+pKWCojNH/dgyJSwDGCAlIwggJOAgEBMIG/MIG2MQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2llbWVu\nczERMA8GA1UEBRMIWlpaWlpaQTYxHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3QgQ2Vu\ndGVyMT8wPQYDVQQDDDZTaWVtZW5zIElzc3VpbmcgQ0EgTWVkaXVtIFN0cmVuZ3Ro\nIEF1dGhlbnRpY2F0aW9uIDIwMTYCBBXXLOIwCwYJYIZIAWUDBAIBoGkwHAYJKoZI\nhvcNAQkFMQ8XDTE5MTEyMDE0NTYyMFowLwYJKoZIhvcNAQkEMSIEIJDnZUpcVLzC\nOdtpkH8gtxwLPIDE0NmAmFC9uM8q2z+OMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0B\nBwEwCwYJKoZIhvcNAQEBBIIBAH/Pqv2xp3a0jSPkwU1K3eGA/1lfoNJMUny4d/PS\nLVWlkgrmedXdLmuBzAGEaaZOJS0lEpNd01pR/reHs7xxZ+RZ0olTs2ufM0CijQSx\nOL9HDl2O3OoD77NWx4tl3Wy1yJCeV3XH/cEI7AkKHCmKY9QMoMYWh16ORBtr+YcS\nYK+gONOjpjgcgTJgZ3HSFgQ50xiD4WT1kFBHsuYsLqaOSbTfTN6Ayyg4edjrPQqa\nVcVf1OQcIrfWA3yMQrnEZfOYfN/D4EPjTfxBV+VCi/F2bdZmMbJ7jNk1FbewSwWO\nSDH1i0K32NyFbnh0BSos7njq7ELqKlYBsoB/sZfaH2vKy5U=\n-----END SIGNED MESSAGE-----"), - MessageSize: 6494, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Roger Meier"), - Email: []byte("r.meier@siemens.com"), - Date: ×tamppb.Timestamp{Seconds: 1574261780}, - Timezone: []byte("+0100"), - }, - SignatureType: gitalypb.SignatureType_X509, - }, - { - Name: []byte("pgp-long-tag-message"), - Id: truncatedPGPTagID, - TargetCommit: gitCommit, // 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - Message: truncatedPGPTagMsg[146:10386], // first 10240 bytes of tag message - MessageSize: 11148, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1393491261}, - Timezone: []byte("+0100"), - }, - SignatureType: gitalypb.SignatureType_PGP, - }, - { - Name: []byte("v1.2.0"), - Id: annotatedTagID, - Message: []byte("Blob tag"), - MessageSize: 8, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - }, - { - Name: []byte("v1.3.0"), - Id: commitID, - TargetCommit: gitCommit, - }, - { - Name: []byte("v1.4.0"), - Id: blobID, - }, - { - Name: []byte("v1.5.0"), - Id: commitID, - TargetCommit: gitCommit, - }, - { - Name: []byte("v1.6.0"), - Id: bigCommitID.String(), - TargetCommit: bigCommit, - }, - { - Name: []byte("v1.7.0"), - Id: bigMessageTag1ID, - Message: []byte(bigMessage[:helper.MaxCommitOrTagMessageSize]), - MessageSize: int64(len(bigMessage)), - TargetCommit: gitCommit, - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - }, - } - - require.Len(t, receivedTags, len(expectedTags)) - require.ElementsMatch(t, expectedTags, receivedTags) -} - -func TestFindAllTagsNestedTags(t *testing.T) { - cfg, client := setupRefServiceWithoutRepo(t) - - ctx, cancel := testhelper.Context() - defer cancel() - - repoProto, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) - - commitID := gittest.WriteCommit(t, cfg, repoPath, - gittest.WithParents(), - ) - - tagID := gittest.CreateTag(t, cfg, repoPath, "my/nested/tag", commitID.String(), nil) - - stream, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{Repository: repoProto}) - require.NoError(t, err) - - response, err := stream.Recv() - require.NoError(t, err) - testassert.ProtoEqual(t, &gitalypb.FindAllTagsResponse{ - Tags: []*gitalypb.Tag{ - { - Name: []byte("my/nested/tag"), - Id: tagID, - TargetCommit: &gitalypb.GitCommit{ - Id: commitID.String(), - Body: []byte("message"), - BodySize: 7, - Subject: []byte("message"), - TreeId: git.EmptyTreeOID.String(), - Author: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - Committer: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - }, - }, - }, - }, response) - - response, err = stream.Recv() - require.Equal(t, io.EOF, err) - require.Nil(t, response) -} - -func TestFindAllTags_duplicateAnnotatedTags(t *testing.T) { - cfg, client := setupRefServiceWithoutRepo(t) - - repoProto, repoPath := gittest.InitRepo(t, cfg, cfg.Storages[0]) - repo := localrepo.NewTestRepo(t, cfg, repoProto) - - ctx, cancel := testhelper.Context() - defer cancel() - - commitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents()) - date := time.Unix(12345, 0) - - tagID, err := repo.WriteTag(ctx, commitID, "commit", []byte("annotated"), []byte("message"), - gittest.TestUser, date) - require.NoError(t, err) - - require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated", tagID, git.ZeroOID)) - require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated-dup", tagID, git.ZeroOID)) - require.NoError(t, repo.UpdateRef(ctx, "refs/tags/lightweight-1", commitID, git.ZeroOID)) - require.NoError(t, repo.UpdateRef(ctx, "refs/tags/lightweight-2", commitID, git.ZeroOID)) - - c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{Repository: repoProto}) - require.NoError(t, err) - - var receivedTags []*gitalypb.Tag - for { - r, err := c.Recv() - if err == io.EOF { - break - } - require.NoError(t, err) - receivedTags = append(receivedTags, r.GetTags()...) - } - - commitAuthor := &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - } - commit := &gitalypb.GitCommit{ - Id: commitID.String(), - TreeId: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", - Body: []byte("message"), - BodySize: 7, - Subject: []byte("message"), - Author: commitAuthor, - Committer: commitAuthor, - } - - testassert.ProtoEqual(t, []*gitalypb.Tag{ - { - Name: []byte("annotated"), - Id: tagID.String(), - Message: []byte("message"), - MessageSize: 7, - Tagger: &gitalypb.CommitAuthor{ - Name: gittest.TestUser.Name, - Email: gittest.TestUser.Email, - Date: timestamppb.New(date), - Timezone: []byte("+0000"), - }, - TargetCommit: commit, - }, - { - Name: []byte("annotated-dup"), - Id: tagID.String(), - Message: []byte("message"), - MessageSize: 7, - Tagger: &gitalypb.CommitAuthor{ - Name: gittest.TestUser.Name, - Email: gittest.TestUser.Email, - Date: timestamppb.New(date), - Timezone: []byte("+0000"), - }, - TargetCommit: commit, - }, - {Name: []byte("lightweight-1"), Id: commitID.String(), TargetCommit: commit}, - {Name: []byte("lightweight-2"), Id: commitID.String(), TargetCommit: commit}, - }, receivedTags) -} - -func TestFindAllTagNestedTags(t *testing.T) { - cfg, client := setupRefServiceWithoutRepo(t) - - ctx, cancel := testhelper.Context() - defer cancel() - - repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - repo := localrepo.NewTestRepo(t, cfg, repoProto) - - blobID := "faaf198af3a36dbf41961466703cc1d47c61d051" - commitID := "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" - - testCases := []struct { - description string - depth int - originalOid string - }{ - { - description: "nested 1 deep, points to a commit", - depth: 1, - originalOid: commitID, - }, - { - description: "nested 4 deep, points to a commit", - depth: 4, - originalOid: commitID, - }, - { - description: "nested 3 deep, points to a blob", - depth: 3, - originalOid: blobID, - }, - { - description: "nested 20 deep, points to a commit", - depth: 20, - originalOid: commitID, - }, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - tags := bytes.NewReader(gittest.Exec(t, cfg, "-C", repoPath, "tag")) - testhelper.MustRunCommand(t, tags, "xargs", cfg.Git.BinPath, "-C", repoPath, "tag", "-d") - - catfileCache := catfile.NewCache(cfg) - batch, err := catfileCache.BatchProcess(ctx, repo) - require.NoError(t, err) - - info, err := batch.Info(ctx, git.Revision(tc.originalOid)) - require.NoError(t, err) - - expectedTags := make(map[string]*gitalypb.Tag) - tagID := tc.originalOid - - for depth := 0; depth < tc.depth; depth++ { - tagName := fmt.Sprintf("tag-depth-%d", depth) - tagMessage := fmt.Sprintf("a commit %d deep", depth) - tagID = gittest.CreateTag(t, cfg, repoPath, tagName, tagID, &gittest.CreateTagOpts{Message: tagMessage}) - - expectedTag := &gitalypb.Tag{ - Name: []byte(tagName), - Id: tagID, - Message: []byte(tagMessage), - MessageSize: int64(len([]byte(tagMessage))), - Tagger: &gitalypb.CommitAuthor{ - Name: []byte("Scrooge McDuck"), - Email: []byte("scrooge@mcduck.com"), - Date: ×tamppb.Timestamp{Seconds: 1572776879}, - Timezone: []byte("+0100"), - }, - } - - if info.Type == "commit" { - commit, err := catfile.GetCommit(ctx, batch, git.Revision(tc.originalOid)) - require.NoError(t, err) - expectedTag.TargetCommit = commit - } - - expectedTags[string(expectedTag.Name)] = expectedTag - } - - rpcRequest := &gitalypb.FindAllTagsRequest{Repository: repoProto} - - c, err := client.FindAllTags(ctx, rpcRequest) - require.NoError(t, err) - - var receivedTags []*gitalypb.Tag - for { - r, err := c.Recv() - if err == io.EOF { - break - } - require.NoError(t, err) - receivedTags = append(receivedTags, r.GetTags()...) - } - - require.Len(t, receivedTags, len(expectedTags)) - for _, receivedTag := range receivedTags { - assert.Equal(t, expectedTags[string(receivedTag.Name)], receivedTag) - } - }) - } -} - -func TestInvalidFindAllTagsRequest(t *testing.T) { - _, client := setupRefServiceWithoutRepo(t) - - ctx, cancel := testhelper.Context() - defer cancel() - - testCases := []struct { - desc string - request *gitalypb.FindAllTagsRequest - }{ - { - desc: "empty request", - request: &gitalypb.FindAllTagsRequest{}, - }, - { - desc: "invalid repo", - request: &gitalypb.FindAllTagsRequest{ - Repository: &gitalypb.Repository{ - StorageName: "fake", - RelativePath: "repo", - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - c, err := client.FindAllTags(ctx, tc.request) - require.NoError(t, err) - - var recvError error - for recvError == nil { - _, recvError = c.Recv() - } - - testhelper.RequireGrpcError(t, recvError, codes.InvalidArgument) - }) - } -} - -func TestFindAllTagsSorted(t *testing.T) { - cfg, client := setupRefServiceWithoutRepo(t) - - ctx, cancel := testhelper.Context() - defer cancel() - - repoProto, _ := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - - repo := localrepo.New(git.NewExecCommandFactory(cfg), catfile.NewCache(cfg), repoProto, cfg) - headCommit, err := repo.ReadCommit(ctx, "HEAD") - require.NoError(t, err) - annotatedTagID, err := repo.WriteTag(ctx, git.ObjectID(headCommit.Id), "commit", []byte("annotated"), []byte("message"), gittest.TestUser, time.Now()) - require.NoError(t, err) - require.NoError(t, repo.UpdateRef(ctx, "refs/tags/annotated", annotatedTagID, git.ZeroOID)) - - require.NoError(t, repo.ExecAndWait(ctx, git.SubCmd{ - Name: "tag", - Args: []string{"not-annotated", headCommit.Id}, - }, git.WithDisabledHooks())) - - for _, tc := range []struct { - desc string - sortBy *gitalypb.FindAllTagsRequest_SortBy - exp []string - }{ - { - desc: "by name", - sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME}, - exp: []string{ - annotatedTagID.String(), - headCommit.Id, - "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", - "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", - "8f03acbcd11c53d9c9468078f32a2622005a4841", - }, - }, - { - desc: "by updated in ascending order", - sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_CREATORDATE, Direction: gitalypb.SortDirection_ASCENDING}, - exp: []string{ - "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", - "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", - headCommit.Id, - "8f03acbcd11c53d9c9468078f32a2622005a4841", - annotatedTagID.String(), - }, - }, - { - desc: "by updated in descending order", - sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_CREATORDATE, Direction: gitalypb.SortDirection_DESCENDING}, - exp: []string{ - annotatedTagID.String(), - "8f03acbcd11c53d9c9468078f32a2622005a4841", - headCommit.Id, - "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", - "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", - }, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ - Repository: repoProto, - SortBy: tc.sortBy, - }) - require.NoError(t, err) - - var tags []string - for { - r, err := c.Recv() - if err == io.EOF { - break - } - require.NoError(t, err) - for _, tag := range r.GetTags() { - tags = append(tags, tag.Id) - } - } - - require.Equal(t, tc.exp, tags) - }) - } - - t.Run("by unsupported key", func(t *testing.T) { - c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ - Repository: repoProto, - SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_Key(-1)}, - }) - require.NoError(t, err) - r, err := c.Recv() - testassert.GrpcEqualErr(t, status.Error(codes.InvalidArgument, "unsupported sorting key: -1"), err) - require.Nil(t, r) - }) - - t.Run("by unsupported direction", func(t *testing.T) { - c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ - Repository: repoProto, - SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME, Direction: gitalypb.SortDirection(-1)}, - }) - require.NoError(t, err) - r, err := c.Recv() - testassert.GrpcEqualErr(t, status.Error(codes.InvalidArgument, "unsupported sorting direction: -1"), err) - require.Nil(t, r) - }) - - t.Run("no tags", func(t *testing.T) { - repoProto, _ := gittest.InitRepo(t, cfg, cfg.Storages[0]) - c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ - Repository: repoProto, - SortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME}, - }) - require.NoError(t, err) - - r, err := c.Recv() - require.Equal(t, io.EOF, err) - require.Nil(t, r) - }) -} - func TestSuccessfulFindLocalBranches(t *testing.T) { _, repo, _, client := setupRefService(t) @@ -1471,10 +875,10 @@ func TestSuccessfulFindTagRequest(t *testing.T) { repo := localrepo.NewTestRepo(t, cfg, repoProto) - blobID := "faaf198af3a36dbf41961466703cc1d47c61d051" - commitID := "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" + blobID := git.ObjectID("faaf198af3a36dbf41961466703cc1d47c61d051") + commitID := git.ObjectID("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") - gitCommit := testhelper.GitLabTestCommit(commitID) + gitCommit := testhelper.GitLabTestCommit(commitID.String()) ctx, cancel := testhelper.Context() defer cancel() @@ -1487,31 +891,31 @@ func TestSuccessfulFindTagRequest(t *testing.T) { bigCommit, err := repo.ReadCommit(ctx, git.Revision(bigCommitID)) require.NoError(t, err) - annotatedTagID := gittest.CreateTag(t, cfg, repoPath, "v1.2.0", blobID, &gittest.CreateTagOpts{Message: "Blob tag"}) + annotatedTagID := gittest.WriteTag(t, cfg, repoPath, "v1.2.0", blobID.Revision(), gittest.WriteTagConfig{Message: "Blob tag"}) - gittest.CreateTag(t, cfg, repoPath, "v1.3.0", commitID, nil) - gittest.CreateTag(t, cfg, repoPath, "v1.4.0", blobID, nil) + gittest.WriteTag(t, cfg, repoPath, "v1.3.0", commitID.Revision()) + gittest.WriteTag(t, cfg, repoPath, "v1.4.0", blobID.Revision()) // To test recursive resolving to a commit - gittest.CreateTag(t, cfg, repoPath, "v1.5.0", "v1.3.0", nil) + gittest.WriteTag(t, cfg, repoPath, "v1.5.0", "v1.3.0") // A tag to commit with a big message - gittest.CreateTag(t, cfg, repoPath, "v1.6.0", bigCommitID.String(), nil) + gittest.WriteTag(t, cfg, repoPath, "v1.6.0", bigCommitID.Revision()) // A tag with a big message bigMessage := strings.Repeat("a", 11*1024) - bigMessageTag1ID := gittest.CreateTag(t, cfg, repoPath, "v1.7.0", commitID, &gittest.CreateTagOpts{Message: bigMessage}) + bigMessageTag1ID := gittest.WriteTag(t, cfg, repoPath, "v1.7.0", commitID.Revision(), gittest.WriteTagConfig{Message: bigMessage}) // A tag with a commit id as its name - commitTagID := gittest.CreateTag(t, cfg, repoPath, commitID, commitID, &gittest.CreateTagOpts{Message: "commit tag with a commit sha as the name"}) + commitTagID := gittest.WriteTag(t, cfg, repoPath, commitID.String(), commitID.Revision(), gittest.WriteTagConfig{Message: "commit tag with a commit sha as the name"}) // a tag of a tag - tagOfTagID := gittest.CreateTag(t, cfg, repoPath, "tag-of-tag", commitTagID, &gittest.CreateTagOpts{Message: "tag of a tag"}) + tagOfTagID := gittest.WriteTag(t, cfg, repoPath, "tag-of-tag", commitTagID.Revision(), gittest.WriteTagConfig{Message: "tag of a tag"}) expectedTags := []*gitalypb.Tag{ { Name: []byte(commitID), - Id: commitTagID, + Id: commitTagID.String(), TargetCommit: gitCommit, Message: []byte("commit tag with a commit sha as the name"), MessageSize: 40, @@ -1524,7 +928,7 @@ func TestSuccessfulFindTagRequest(t *testing.T) { }, { Name: []byte("tag-of-tag"), - Id: tagOfTagID, + Id: tagOfTagID.String(), TargetCommit: gitCommit, Message: []byte("tag of a tag"), MessageSize: 12, @@ -1578,7 +982,7 @@ func TestSuccessfulFindTagRequest(t *testing.T) { }, { Name: []byte("v1.2.0"), - Id: annotatedTagID, + Id: annotatedTagID.String(), Message: []byte("Blob tag"), MessageSize: 8, Tagger: &gitalypb.CommitAuthor{ @@ -1590,16 +994,16 @@ func TestSuccessfulFindTagRequest(t *testing.T) { }, { Name: []byte("v1.3.0"), - Id: commitID, + Id: commitID.String(), TargetCommit: gitCommit, }, { Name: []byte("v1.4.0"), - Id: blobID, + Id: blobID.String(), }, { Name: []byte("v1.5.0"), - Id: commitID, + Id: commitID.String(), TargetCommit: gitCommit, }, { @@ -1609,7 +1013,7 @@ func TestSuccessfulFindTagRequest(t *testing.T) { }, { Name: []byte("v1.7.0"), - Id: bigMessageTag1ID, + Id: bigMessageTag1ID.String(), Message: []byte(bigMessage[:helper.MaxCommitOrTagMessageSize]), MessageSize: int64(len(bigMessage)), TargetCommit: gitCommit, @@ -1638,8 +1042,8 @@ func TestFindTagNestedTag(t *testing.T) { repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) repo := localrepo.NewTestRepo(t, cfg, repoProto) - blobID := "faaf198af3a36dbf41961466703cc1d47c61d051" - commitID := "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" + blobID := git.ObjectID("faaf198af3a36dbf41961466703cc1d47c61d051") + commitID := git.ObjectID("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") ctx, cancel := testhelper.Context() defer cancel() @@ -1647,7 +1051,7 @@ func TestFindTagNestedTag(t *testing.T) { testCases := []struct { description string depth int - originalOid string + originalOid git.ObjectID }{ { description: "nested 1 deep, points to a commit", @@ -1689,11 +1093,11 @@ func TestFindTagNestedTag(t *testing.T) { for depth := 0; depth < tc.depth; depth++ { tagName = fmt.Sprintf("tag-depth-%d", depth) tagMessage = fmt.Sprintf("a commit %d deep", depth) - tagID = gittest.CreateTag(t, cfg, repoPath, tagName, tagID, &gittest.CreateTagOpts{Message: tagMessage}) + tagID = gittest.WriteTag(t, cfg, repoPath, tagName, tagID.Revision(), gittest.WriteTagConfig{Message: tagMessage}) } expectedTag := &gitalypb.Tag{ Name: []byte(tagName), - Id: tagID, + Id: tagID.String(), Message: []byte(tagMessage), MessageSize: int64(len([]byte(tagMessage))), Tagger: &gitalypb.CommitAuthor{ diff --git a/internal/gitaly/service/ref/tag_messages_test.go b/internal/gitaly/service/ref/tag_messages_test.go index a6b11df99..6eeeda895 100644 --- a/internal/gitaly/service/ref/tag_messages_test.go +++ b/internal/gitaly/service/ref/tag_messages_test.go @@ -23,21 +23,21 @@ func TestSuccessfulGetTagMessagesRequest(t *testing.T) { message1 := strings.Repeat("a", helper.MaxCommitOrTagMessageSize*2) message2 := strings.Repeat("b", helper.MaxCommitOrTagMessageSize) - tag1ID := gittest.CreateTag(t, cfg, repoPath, "big-tag-1", "master", &gittest.CreateTagOpts{Message: message1}) - tag2ID := gittest.CreateTag(t, cfg, repoPath, "big-tag-2", "master~", &gittest.CreateTagOpts{Message: message2}) + tag1ID := gittest.WriteTag(t, cfg, repoPath, "big-tag-1", "master", gittest.WriteTagConfig{Message: message1}) + tag2ID := gittest.WriteTag(t, cfg, repoPath, "big-tag-2", "master~", gittest.WriteTagConfig{Message: message2}) request := &gitalypb.GetTagMessagesRequest{ Repository: repo, - TagIds: []string{tag1ID, tag2ID}, + TagIds: []string{tag1ID.String(), tag2ID.String()}, } expectedMessages := []*gitalypb.GetTagMessagesResponse{ { - TagId: tag1ID, + TagId: tag1ID.String(), Message: []byte(message1 + "\n"), }, { - TagId: tag2ID, + TagId: tag2ID.String(), Message: []byte(message2 + "\n"), }, } diff --git a/internal/gitaly/service/ref/tag_signatures_test.go b/internal/gitaly/service/ref/tag_signatures_test.go index 6eee14723..08ce72d88 100644 --- a/internal/gitaly/service/ref/tag_signatures_test.go +++ b/internal/gitaly/service/ref/tag_signatures_test.go @@ -24,16 +24,16 @@ func TestGetTagSignatures(t *testing.T) { message1 := strings.Repeat("a", helper.MaxCommitOrTagMessageSize) + "\n" signature1 := string(testhelper.MustReadFile(t, "testdata/tag-1e292f8fedd741b75372e19097c76d327140c312-signature")) - tag1ID := gittest.CreateTag(t, cfg, repoPath, "big-tag-1", "master", &gittest.CreateTagOpts{Message: message1 + signature1}) + tag1ID := gittest.WriteTag(t, cfg, repoPath, "big-tag-1", "master", gittest.WriteTagConfig{Message: message1 + signature1}) content1 := "object 1e292f8fedd741b75372e19097c76d327140c312\ntype commit\ntag big-tag-1\ntagger Scrooge McDuck <scrooge@mcduck.com> 1572776879 +0100\n\n" + message1 message2 := strings.Repeat("b", helper.MaxCommitOrTagMessageSize) + "\n" signature2 := string(testhelper.MustReadFile(t, "testdata/tag-7975be0116940bf2ad4321f79d02a55c5f7779aa-signature")) - tag2ID := gittest.CreateTag(t, cfg, repoPath, "big-tag-2", "master~", &gittest.CreateTagOpts{Message: message2 + signature2}) + tag2ID := gittest.WriteTag(t, cfg, repoPath, "big-tag-2", "master~", gittest.WriteTagConfig{Message: message2 + signature2}) content2 := "object 7975be0116940bf2ad4321f79d02a55c5f7779aa\ntype commit\ntag big-tag-2\ntagger Scrooge McDuck <scrooge@mcduck.com> 1572776879 +0100\n\n" + message2 message3 := "tag message\n" - tag3ID := gittest.CreateTag(t, cfg, repoPath, "tag-3", "master~~", &gittest.CreateTagOpts{Message: message3}) + tag3ID := gittest.WriteTag(t, cfg, repoPath, "tag-3", "master~~", gittest.WriteTagConfig{Message: message3}) content3 := "object 60ecb67744cb56576c30214ff52294f8ce2def98\ntype commit\ntag tag-3\ntagger Scrooge McDuck <scrooge@mcduck.com> 1572776879 +0100\n\n" + message3 for _, tc := range []struct { @@ -78,11 +78,11 @@ func TestGetTagSignatures(t *testing.T) { { desc: "single tag signature", revisions: []string{ - tag1ID, + tag1ID.String(), }, expectedSignatures: []*gitalypb.GetTagSignaturesResponse_TagSignature{ { - TagId: tag1ID, + TagId: tag1ID.String(), Signature: []byte(signature1), Content: []byte(content1), }, @@ -91,11 +91,11 @@ func TestGetTagSignatures(t *testing.T) { { desc: "single tag signature by short SHA", revisions: []string{ - tag1ID[:7], + tag1ID.String()[:7], }, expectedSignatures: []*gitalypb.GetTagSignaturesResponse_TagSignature{ { - TagId: tag1ID, + TagId: tag1ID.String(), Signature: []byte(signature1), Content: []byte(content1), }, @@ -108,7 +108,7 @@ func TestGetTagSignatures(t *testing.T) { }, expectedSignatures: []*gitalypb.GetTagSignaturesResponse_TagSignature{ { - TagId: tag1ID, + TagId: tag1ID.String(), Signature: []byte(signature1), Content: []byte(content1), }, @@ -117,17 +117,17 @@ func TestGetTagSignatures(t *testing.T) { { desc: "multiple tag signatures", revisions: []string{ - tag1ID, - tag2ID, + tag1ID.String(), + tag2ID.String(), }, expectedSignatures: []*gitalypb.GetTagSignaturesResponse_TagSignature{ { - TagId: tag1ID, + TagId: tag1ID.String(), Signature: []byte(signature1), Content: []byte(content1), }, { - TagId: tag2ID, + TagId: tag2ID.String(), Signature: []byte(signature2), Content: []byte(content2), }, @@ -136,11 +136,11 @@ func TestGetTagSignatures(t *testing.T) { { desc: "tag without signature", revisions: []string{ - tag3ID, + tag3ID.String(), }, expectedSignatures: []*gitalypb.GetTagSignaturesResponse_TagSignature{ { - TagId: tag3ID, + TagId: tag3ID.String(), Signature: []byte(""), Content: []byte(content3), }, diff --git a/internal/gitaly/service/remote/update_remote_mirror_test.go b/internal/gitaly/service/remote/update_remote_mirror_test.go index c817ed69a..e56171609 100644 --- a/internal/gitaly/service/remote/update_remote_mirror_test.go +++ b/internal/gitaly/service/remote/update_remote_mirror_test.go @@ -641,9 +641,9 @@ func TestSuccessfulUpdateRemoteMirrorRequest(t *testing.T) { testRepo, testRepoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) _, mirrorPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - gittest.CreateTag(t, cfg, mirrorPath, "v0.0.1", "master", nil) // I needed another tag for the tests - gittest.CreateTag(t, cfg, testRepoPath, "new-tag", "60ecb67744cb56576c30214ff52294f8ce2def98", nil) - gittest.CreateTag(t, cfg, testRepoPath, "v1.0.0", "0b4bc9a49b562e85de7cc9e834518ea6828729b9", &gittest.CreateTagOpts{ + gittest.WriteTag(t, cfg, mirrorPath, "v0.0.1", "master") // I needed another tag for the tests + gittest.WriteTag(t, cfg, testRepoPath, "new-tag", "60ecb67744cb56576c30214ff52294f8ce2def98") + gittest.WriteTag(t, cfg, testRepoPath, "v1.0.0", "0b4bc9a49b562e85de7cc9e834518ea6828729b9", gittest.WriteTagConfig{ Message: "Overriding tag", Force: true, }) @@ -758,9 +758,9 @@ func TestSuccessfulUpdateRemoteMirrorRequestWithWildcards(t *testing.T) { {"tag", "--delete", "v1.1.0"}, // v1.1.0 is ambiguous, maps to a branch and a tag in gitlab-test repository } - gittest.CreateTag(t, cfg, testRepoPath, "new-tag", "60ecb67744cb56576c30214ff52294f8ce2def98", nil) // Add tag - gittest.CreateTag(t, cfg, testRepoPath, "v1.0.0", "0b4bc9a49b562e85de7cc9e834518ea6828729b9", - &gittest.CreateTagOpts{Message: "Overriding tag", Force: true}) // Update tag + gittest.WriteTag(t, cfg, testRepoPath, "new-tag", "60ecb67744cb56576c30214ff52294f8ce2def98") // Add tag + gittest.WriteTag(t, cfg, testRepoPath, "v1.0.0", "0b4bc9a49b562e85de7cc9e834518ea6828729b9", + gittest.WriteTagConfig{Message: "Overriding tag", Force: true}) // Update tag for _, args := range setupCommands { gitArgs := []string{"-C", testRepoPath} @@ -770,7 +770,7 @@ func TestSuccessfulUpdateRemoteMirrorRequestWithWildcards(t *testing.T) { // Workaround for https://gitlab.com/gitlab-org/gitaly/issues/1439 // Create a tag on the remote to ensure it gets deleted later - gittest.CreateTag(t, cfg, mirrorPath, "v1.2.0", "master", nil) + gittest.WriteTag(t, cfg, mirrorPath, "v1.2.0", "master") newTagOid := string(gittest.Exec(t, cfg, "-C", testRepoPath, "rev-parse", "v1.0.0")) newTagOid = strings.TrimSpace(newTagOid) @@ -874,7 +874,7 @@ func TestSuccessfulUpdateRemoteMirrorRequestWithKeepDivergentRefs(t *testing.T) testRepo, testRepoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) _, mirrorPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) - gittest.CreateTag(t, cfg, mirrorPath, "v2.0.0", "master", nil) + gittest.WriteTag(t, cfg, mirrorPath, "v2.0.0", "master") setupCommands := [][]string{ // Preconditions diff --git a/internal/gitaly/service/repository/fetch_remote_test.go b/internal/gitaly/service/repository/fetch_remote_test.go index 05618d95d..5fac9e4a5 100644 --- a/internal/gitaly/service/repository/fetch_remote_test.go +++ b/internal/gitaly/service/repository/fetch_remote_test.go @@ -57,7 +57,7 @@ func TestFetchRemoteSuccess(t *testing.T) { cloneRepo := copyRepo(t, cfg, repo, repoPath) // Ensure there's a new tag to fetch - gittest.CreateTag(t, cfg, repoPath, "testtag", "master", nil) + gittest.WriteTag(t, cfg, repoPath, "testtag", "master") req := &gitalypb.FetchRemoteRequest{Repository: cloneRepo, RemoteParams: &gitalypb.Remote{ Url: repoPath, |