diff options
Diffstat (limited to 'internal/gitaly')
-rw-r--r-- | internal/gitaly/service/ref/find_all_tags.go | 29 | ||||
-rw-r--r-- | internal/gitaly/service/ref/find_all_tags_test.go | 101 | ||||
-rw-r--r-- | internal/gitaly/service/ref/list_refs.go | 2 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refs.go | 33 | ||||
-rw-r--r-- | internal/gitaly/service/ref/remote_branches.go | 2 |
5 files changed, 159 insertions, 8 deletions
diff --git a/internal/gitaly/service/ref/find_all_tags.go b/internal/gitaly/service/ref/find_all_tags.go index e7667d349..43d36eb06 100644 --- a/internal/gitaly/service/ref/find_all_tags.go +++ b/internal/gitaly/service/ref/find_all_tags.go @@ -28,16 +28,18 @@ func (s *server) FindAllTags(in *gitalypb.FindAllTagsRequest, stream gitalypb.Re return helper.ErrInvalidArgument(err) } + opts := buildPaginationOpts(ctx, in.GetPaginationParams()) + repo := s.localrepo(in.GetRepository()) - if err := s.findAllTags(ctx, repo, sortField, stream); err != nil { + if err := s.findAllTags(ctx, repo, sortField, stream, opts); 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 { +func (s *server) findAllTags(ctx context.Context, repo *localrepo.Repo, sortField string, stream gitalypb.RefService_FindAllTagsServer, opts *paginationOpts) error { objectReader, err := s.catfileCache.ObjectReader(ctx, repo) if err != nil { return fmt.Errorf("error creating object reader: %v", err) @@ -50,9 +52,21 @@ func (s *server) findAllTags(ctx context.Context, repo *localrepo.Repo, sortFiel chunker := chunk.New(&tagSender{stream: stream}) + // If `PageToken` is not provided, then `IsPageToken` will always return `true` + // and disable pagination logic. If `PageToken` is set, then we will skip all tags + // until we reach the tag equal to `PageToken`. After that, tags will be returned + // as usual. + pastPageToken := opts.IsPageToken([]byte{}) + limit := opts.Limit + i := 0 + for catfileObjectsIter.Next() { tag := catfileObjectsIter.Result() + if i >= limit { + break + } + var result *gitalypb.Tag switch tag.ObjectInfo.Type { case "tag": @@ -111,9 +125,20 @@ func (s *server) findAllTags(ctx context.Context, repo *localrepo.Repo, sortFiel result.Name = tagName } + if !pastPageToken { + pastPageToken = opts.IsPageToken(tag.ObjectName) + continue + } + if err := chunker.Send(result); err != nil { return fmt.Errorf("sending tag: %w", err) } + + i++ + } + + if !pastPageToken { + return helper.ErrInvalidArgumentf("could not find page token") } if err := catfileObjectsIter.Err(); err != nil { diff --git a/internal/gitaly/service/ref/find_all_tags_test.go b/internal/gitaly/service/ref/find_all_tags_test.go index 160b2434d..c53b1ce78 100644 --- a/internal/gitaly/service/ref/find_all_tags_test.go +++ b/internal/gitaly/service/ref/find_all_tags_test.go @@ -503,6 +503,107 @@ func TestFindAllTags_invalidRequest(t *testing.T) { } } +func TestFindAllTags_pagination(t *testing.T) { + cfg, client := setupRefServiceWithoutRepo(t) + + ctx, cancel := testhelper.Context() + defer cancel() + + repoProto, repoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0]) + + catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + + annotatedTagID := gittest.WriteTag(t, cfg, repoPath, "annotated", "HEAD", gittest.WriteTagConfig{ + Message: "message", + }) + + for _, tc := range []struct { + desc string + paginationParams *gitalypb.PaginationParameter + sortBy *gitalypb.FindAllTagsRequest_SortBy + exp []string + expectedErr error + }{ + { + desc: "without pagination", + paginationParams: &gitalypb.PaginationParameter{Limit: 100}, + exp: []string{ + annotatedTagID.String(), + "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + "8f03acbcd11c53d9c9468078f32a2622005a4841", + }, + }, + { + desc: "with limit restrictions", + paginationParams: &gitalypb.PaginationParameter{Limit: 3}, + exp: []string{ + annotatedTagID.String(), + "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + }, + }, + { + desc: "with limit restrictions and page token", + paginationParams: &gitalypb.PaginationParameter{Limit: 3, PageToken: "refs/tags/v1.0.0"}, + exp: []string{ + "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", + "8f03acbcd11c53d9c9468078f32a2622005a4841", + }, + }, + { + desc: "with reversed sort by name, limit restrictions and page token", + paginationParams: &gitalypb.PaginationParameter{Limit: 3, PageToken: "refs/tags/v1.0.0"}, + sortBy: &gitalypb.FindAllTagsRequest_SortBy{Key: gitalypb.FindAllTagsRequest_SortBy_REFNAME, Direction: gitalypb.SortDirection_DESCENDING}, + exp: []string{ + annotatedTagID.String(), + }, + }, + { + desc: "with page token only", + paginationParams: &gitalypb.PaginationParameter{PageToken: "refs/tags/v1.1.0"}, + exp: nil, + expectedErr: helper.ErrInvalidArgumentf("could not find page token"), + }, + { + desc: "with invalid page token", + paginationParams: &gitalypb.PaginationParameter{Limit: 3, PageToken: "refs/tags/invalid"}, + exp: nil, + expectedErr: helper.ErrInvalidArgumentf("could not find page token"), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + c, err := client.FindAllTags(ctx, &gitalypb.FindAllTagsRequest{ + Repository: repoProto, + PaginationParams: tc.paginationParams, + SortBy: tc.sortBy, + }) + + if tc.expectedErr != nil { + _, err = c.Recv() + require.NotEqual(t, err, io.EOF) + testassert.GrpcEqualErr(t, tc.expectedErr, err) + } else { + 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) + } + }) + } +} + func TestFindAllTags_sorted(t *testing.T) { cfg, client := setupRefServiceWithoutRepo(t) diff --git a/internal/gitaly/service/ref/list_refs.go b/internal/gitaly/service/ref/list_refs.go index 3fe351519..f5eb03557 100644 --- a/internal/gitaly/service/ref/list_refs.go +++ b/internal/gitaly/service/ref/list_refs.go @@ -34,7 +34,7 @@ func (s *server) ListRefs(in *gitalypb.ListRefsRequest, stream gitalypb.RefServi patterns = append(patterns, string(pattern)) } - opts := paginationParamsToOpts(ctx, nil) + opts := buildFindRefsOpts(ctx, nil) opts.cmdArgs = []git.Option{ // %00 inserts the null character into the output (see for-each-ref docs) git.Flag{Name: "--format=" + strings.Join(localBranchFormatFields, "%00")}, diff --git a/internal/gitaly/service/ref/refs.go b/internal/gitaly/service/ref/refs.go index d6e2f2930..658d019b8 100644 --- a/internal/gitaly/service/ref/refs.go +++ b/internal/gitaly/service/ref/refs.go @@ -22,6 +22,20 @@ const ( tagFormat = "%(objectname) %(objecttype) %(refname:lstrip=2)" ) +type paginationOpts struct { + // Limit allows to set the maximum numbers of elements + Limit int + // IsPageToken allows control over which results are sent as part of the + // response. When IsPageToken evaluates to true for the first time, + // results will start to be sent as part of the response. This function + // will be called with an empty slice previous to sending the first line + // in order to allow sending everything right from the beginning. + IsPageToken func([]byte) bool + // When PageTokenError is true then the response will return an error when + // PageToken is not found. + PageTokenError bool +} + type findRefsOpts struct { cmdArgs []git.Option delim byte @@ -144,7 +158,7 @@ func (s *server) findLocalBranches(in *gitalypb.FindLocalBranchesRequest, stream } writer := newFindLocalBranchesWriter(stream, objectReader) - opts := paginationParamsToOpts(ctx, in.GetPaginationParams()) + opts := buildFindRefsOpts(ctx, in.GetPaginationParams()) opts.cmdArgs = []git.Option{ // %00 inserts the null character into the output (see for-each-ref docs) git.Flag{Name: "--format=" + strings.Join(localBranchFormatFields, "%00")}, @@ -195,7 +209,7 @@ func (s *server) findAllBranches(in *gitalypb.FindAllBranchesRequest, stream git return err } - opts := paginationParamsToOpts(ctx, nil) + opts := buildFindRefsOpts(ctx, nil) opts.cmdArgs = args writer := newFindAllBranchesWriter(stream, objectReader) @@ -306,8 +320,8 @@ func (s *server) validateFindTagRequest(in *gitalypb.FindTagRequest) error { return nil } -func paginationParamsToOpts(ctx context.Context, p *gitalypb.PaginationParameter) *findRefsOpts { - opts := &findRefsOpts{delim: '\n'} +func buildPaginationOpts(ctx context.Context, p *gitalypb.PaginationParameter) *paginationOpts { + opts := &paginationOpts{} opts.IsPageToken = func(_ []byte) bool { return true } opts.Limit = math.MaxInt32 @@ -338,6 +352,17 @@ func paginationParamsToOpts(ctx context.Context, p *gitalypb.PaginationParameter return opts } +func buildFindRefsOpts(ctx context.Context, p *gitalypb.PaginationParameter) *findRefsOpts { + opts := buildPaginationOpts(ctx, p) + + refsOpts := &findRefsOpts{delim: '\n'} + refsOpts.Limit = opts.Limit + refsOpts.IsPageToken = opts.IsPageToken + refsOpts.PageTokenError = opts.PageTokenError + + return refsOpts +} + // getTagSortField returns a field that needs to be used to sort the tags. // If sorting is not provided the default sorting is used: by refname. func getTagSortField(sortBy *gitalypb.FindAllTagsRequest_SortBy) (string, error) { diff --git a/internal/gitaly/service/ref/remote_branches.go b/internal/gitaly/service/ref/remote_branches.go index 6a1c1b672..57ed60999 100644 --- a/internal/gitaly/service/ref/remote_branches.go +++ b/internal/gitaly/service/ref/remote_branches.go @@ -36,7 +36,7 @@ func (s *server) findAllRemoteBranches(req *gitalypb.FindAllRemoteBranchesReques return err } - opts := paginationParamsToOpts(ctx, nil) + opts := buildFindRefsOpts(ctx, nil) opts.cmdArgs = args writer := newFindAllRemoteBranchesWriter(stream, objectReader) |