diff options
author | Will Chandler <wchandler@gitlab.com> | 2023-04-19 20:16:16 +0300 |
---|---|---|
committer | Will Chandler <wchandler@gitlab.com> | 2023-04-19 20:16:16 +0300 |
commit | f06cc91443358f405e7a5896dd9e9eb80036efba (patch) | |
tree | da8d64d7652efd03bf03a8d61d5d45a9bf4b4f68 | |
parent | 5660b156be328c7be18df892ccb7149fea21e491 (diff) | |
parent | 311f36bf5d7893293f644e3ba82fb0b48c111a61 (diff) |
Merge branch 'pks-refs-containing-sha256' into 'master'
ref: Support SHA256 in RPCs that search for refs containing commits
See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5648
Merged-by: Will Chandler <wchandler@gitlab.com>
Approved-by: Will Chandler <wchandler@gitlab.com>
Approved-by: Quang-Minh Nguyen <qmnguyen@gitlab.com>
Co-authored-by: Patrick Steinhardt <psteinhardt@gitlab.com>
-rw-r--r-- | internal/gitaly/service/ref/refnames.go | 15 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refnames_containing.go | 39 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refnames_containing_test.go | 254 | ||||
-rw-r--r-- | internal/gitaly/service/ref/refs_test.go | 223 | ||||
-rw-r--r-- | internal/gitaly/service/ref/testhelper_test.go | 2 |
5 files changed, 293 insertions, 240 deletions
diff --git a/internal/gitaly/service/ref/refnames.go b/internal/gitaly/service/ref/refnames.go index 0ca444f58..f75f5006a 100644 --- a/internal/gitaly/service/ref/refnames.go +++ b/internal/gitaly/service/ref/refnames.go @@ -15,13 +15,16 @@ import ( // FindAllBranchNames creates a stream of ref names for all branches in the given repository func (s *server) FindAllBranchNames(in *gitalypb.FindAllBranchNamesRequest, stream gitalypb.RefService_FindAllBranchNamesServer) error { + ctx := stream.Context() + if err := service.ValidateRepository(in.GetRepository()); err != nil { return structerr.NewInvalidArgument("%w", err) } + repo := s.localrepo(in.GetRepository()) chunker := chunk.New(&findAllBranchNamesSender{stream: stream}) - if err := s.listRefNames(stream.Context(), chunker, "refs/heads", in.Repository, nil); err != nil { + if err := listRefNames(ctx, repo, chunker, "refs/heads", nil); err != nil { return structerr.NewInternal("%w", err) } @@ -44,12 +47,16 @@ func (ts *findAllBranchNamesSender) Send() error { // FindAllTagNames creates a stream of ref names for all tags in the given repository func (s *server) FindAllTagNames(in *gitalypb.FindAllTagNamesRequest, stream gitalypb.RefService_FindAllTagNamesServer) error { + ctx := stream.Context() + if err := service.ValidateRepository(in.GetRepository()); err != nil { return structerr.NewInvalidArgument("%w", err) } + repo := s.localrepo(in.GetRepository()) chunker := chunk.New(&findAllTagNamesSender{stream: stream}) - if err := s.listRefNames(stream.Context(), chunker, "refs/tags", in.Repository, nil); err != nil { + + if err := listRefNames(ctx, repo, chunker, "refs/tags", nil); err != nil { return structerr.NewInternal("%w", err) } @@ -70,7 +77,7 @@ func (ts *findAllTagNamesSender) Send() error { return ts.stream.Send(&gitalypb.FindAllTagNamesResponse{Names: ts.tagNames}) } -func (s *server) listRefNames(ctx context.Context, chunker *chunk.Chunker, prefix string, repo *gitalypb.Repository, extraArgs []string) error { +func listRefNames(ctx context.Context, repo git.RepositoryExecutor, chunker *chunk.Chunker, prefix string, extraArgs []string) error { flags := []git.Option{ git.Flag{Name: "--format=%(refname)"}, } @@ -79,7 +86,7 @@ func (s *server) listRefNames(ctx context.Context, chunker *chunk.Chunker, prefi flags = append(flags, git.Flag{Name: arg}) } - cmd, err := s.gitCmdFactory.New(ctx, repo, git.Command{ + cmd, err := repo.Exec(ctx, git.Command{ Name: "for-each-ref", Flags: flags, Args: []string{prefix}, diff --git a/internal/gitaly/service/ref/refnames_containing.go b/internal/gitaly/service/ref/refnames_containing.go index 97395c124..3eb1608df 100644 --- a/internal/gitaly/service/ref/refnames_containing.go +++ b/internal/gitaly/service/ref/refnames_containing.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/service" "gitlab.com/gitlab-org/gitaly/v15/internal/helper/chunk" "gitlab.com/gitlab-org/gitaly/v15/internal/structerr" @@ -13,19 +12,28 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" ) -// ListBranchNamesContainingCommit returns a maximum of in.GetLimit() Branch names -// which contain the SHA1 passed as argument +// ListBranchNamesContainingCommit returns a maximum of in.GetLimit() Branch names which contain the +// commit ID passed as argument. func (s *server) ListBranchNamesContainingCommit(in *gitalypb.ListBranchNamesContainingCommitRequest, stream gitalypb.RefService_ListBranchNamesContainingCommitServer) error { + ctx := stream.Context() + if err := service.ValidateRepository(in.GetRepository()); err != nil { return structerr.NewInvalidArgument("%w", err) } - if err := git.ObjectHashSHA1.ValidateHex(in.GetCommitId()); err != nil { + + repo := s.localrepo(in.GetRepository()) + objectHash, err := repo.ObjectHash(ctx) + if err != nil { + return fmt.Errorf("detecting object hash: %w", err) + } + + if err := objectHash.ValidateHex(in.GetCommitId()); err != nil { return structerr.NewInvalidArgument("%w", err) } chunker := chunk.New(&branchNamesContainingCommitSender{stream: stream}) - ctx := stream.Context() - if err := s.listRefNames(ctx, chunker, "refs/heads", in.Repository, containingArgs(in)); err != nil { + + if err := listRefNames(ctx, repo, chunker, "refs/heads", containingArgs(in)); err != nil { return structerr.NewInternal("%w", err) } @@ -59,19 +67,28 @@ func (bs *branchNamesContainingCommitSender) Send() error { return bs.stream.Send(&gitalypb.ListBranchNamesContainingCommitResponse{BranchNames: bs.branchNames}) } -// ListTagNamesContainingCommit returns a maximum of in.GetLimit() Tag names -// which contain the SHA1 passed as argument +// ListTagNamesContainingCommit returns a maximum of in.GetLimit() Tag names which contain the +// commit ID passed as argument. func (s *server) ListTagNamesContainingCommit(in *gitalypb.ListTagNamesContainingCommitRequest, stream gitalypb.RefService_ListTagNamesContainingCommitServer) error { + ctx := stream.Context() + if err := service.ValidateRepository(in.GetRepository()); err != nil { return structerr.NewInvalidArgument("%w", err) } - if err := git.ObjectHashSHA1.ValidateHex(in.GetCommitId()); err != nil { + + repo := s.localrepo(in.GetRepository()) + objectHash, err := repo.ObjectHash(ctx) + if err != nil { + return fmt.Errorf("detecting object hash: %w", err) + } + + if err := objectHash.ValidateHex(in.GetCommitId()); err != nil { return structerr.NewInvalidArgument("%w", err) } chunker := chunk.New(&tagNamesContainingCommitSender{stream: stream}) - ctx := stream.Context() - if err := s.listRefNames(ctx, chunker, "refs/tags", in.Repository, containingArgs(in)); err != nil { + + if err := listRefNames(ctx, repo, chunker, "refs/tags", containingArgs(in)); err != nil { return structerr.NewInternal("%w", err) } diff --git a/internal/gitaly/service/ref/refnames_containing_test.go b/internal/gitaly/service/ref/refnames_containing_test.go new file mode 100644 index 000000000..10c2373f3 --- /dev/null +++ b/internal/gitaly/service/ref/refnames_containing_test.go @@ -0,0 +1,254 @@ +package ref + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v15/internal/git" + "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" + "gitlab.com/gitlab-org/gitaly/v15/internal/structerr" + "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb" +) + +func TestListTagNamesContainingCommit(t *testing.T) { + t.Parallel() + + ctx := testhelper.Context(t) + cfg, client := setupRefServiceWithoutRepo(t) + + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitA := gittest.WriteCommit(t, cfg, repoPath) + commitB := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(commitA)) + commitC := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(commitB), gittest.WithBranch(git.DefaultBranch)) + + gittest.WriteTag(t, cfg, repoPath, "annotated", commitA.Revision(), gittest.WriteTagConfig{ + Message: "annotated", + }) + gittest.WriteTag(t, cfg, repoPath, "lightweight", commitB.Revision()) + + for _, tc := range []struct { + desc string + request *gitalypb.ListTagNamesContainingCommitRequest + expectedErr error + expectedTags []string + }{ + { + desc: "repository not provided", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: nil, + }, + expectedErr: structerr.NewInvalidArgument(testhelper.GitalyOrPraefect( + "empty Repository", + "repo scoped: empty Repository", + )), + }, + { + desc: "invalid commit ID", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: "invalid", + }, + expectedErr: structerr.NewInvalidArgument( + fmt.Sprintf(`invalid object ID: "invalid", expected length %v, got 7`, gittest.DefaultObjectHash.EncodedLen()), + ), + }, + { + desc: "no commit ID", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: "", + }, + expectedErr: structerr.NewInvalidArgument( + `invalid object ID: "", expected length %d, got 0`, gittest.DefaultObjectHash.EncodedLen(), + ), + }, + { + desc: "commit not contained in any tag", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: commitC.String(), + }, + expectedTags: nil, + }, + { + desc: "root commit", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: commitA.String(), + }, + expectedTags: []string{"annotated", "lightweight"}, + }, + { + desc: "root commit with limit", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: commitA.String(), + Limit: 1, + }, + expectedTags: []string{"annotated"}, + }, + { + desc: "commit with single tag", + request: &gitalypb.ListTagNamesContainingCommitRequest{ + Repository: repo, + CommitId: commitB.String(), + }, + expectedTags: []string{"lightweight"}, + }, + } { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + stream, err := client.ListTagNamesContainingCommit(ctx, tc.request) + require.NoError(t, err) + + var tagNames []string + for { + var response *gitalypb.ListTagNamesContainingCommitResponse + response, err = stream.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + err = nil + } + + break + } + + for _, tagName := range response.GetTagNames() { + tagNames = append(tagNames, string(tagName)) + } + } + + testhelper.RequireGrpcError(t, tc.expectedErr, err) + require.ElementsMatch(t, tc.expectedTags, tagNames) + }) + } +} + +func TestListBranchNamesContainingCommit(t *testing.T) { + t.Parallel() + + ctx := testhelper.Context(t) + cfg, client := setupRefServiceWithoutRepo(t) + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + rootCommitID := gittest.WriteCommit(t, cfg, repoPath) + intermediateCommitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(rootCommitID)) + headCommitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(intermediateCommitID), gittest.WithBranch(git.DefaultBranch)) + gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(intermediateCommitID), gittest.WithBranch("branch")) + + ambiguousCommitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("ambiguous"), gittest.WithParents(intermediateCommitID)) + gittest.WriteRef(t, cfg, repoPath, git.ReferenceName("refs/heads/"+ambiguousCommitID.String()), ambiguousCommitID) + + unreferencedCommitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("unreferenced")) + + for _, tc := range []struct { + desc string + request *gitalypb.ListBranchNamesContainingCommitRequest + expectedErr error + expectedBranches []string + }{ + { + desc: "repository not provided", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: nil, + }, + expectedErr: structerr.NewInvalidArgument(testhelper.GitalyOrPraefect( + "empty Repository", + "repo scoped: empty Repository", + )), + }, + { + desc: "invalid commit", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: "invalid", + }, + expectedErr: structerr.NewInvalidArgument( + fmt.Sprintf(`invalid object ID: "invalid", expected length %v, got 7`, gittest.DefaultObjectHash.EncodedLen()), + ), + }, + { + desc: "no commit ID", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: "", + }, + expectedErr: structerr.NewInvalidArgument( + fmt.Sprintf(`invalid object ID: "", expected length %v, got 0`, gittest.DefaultObjectHash.EncodedLen()), + ), + }, + { + desc: "current HEAD", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: headCommitID.String(), + }, + expectedBranches: []string{git.DefaultBranch, "branch"}, + }, + { + desc: "branch name is also commit id", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: ambiguousCommitID.String(), + }, + expectedBranches: []string{ambiguousCommitID.String()}, + }, + { + desc: "initial commit", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: rootCommitID.String(), + }, + expectedBranches: []string{ + git.DefaultBranch, + "branch", + ambiguousCommitID.String(), + }, + }, + { + desc: "commit without references", + request: &gitalypb.ListBranchNamesContainingCommitRequest{ + Repository: repo, + CommitId: unreferencedCommitID.String(), + }, + expectedBranches: nil, + }, + } { + tc := tc + + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + stream, err := client.ListBranchNamesContainingCommit(ctx, tc.request) + require.NoError(t, err) + + var branchNames []string + for { + var response *gitalypb.ListBranchNamesContainingCommitResponse + response, err = stream.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + err = nil + } + + break + } + + for _, branchName := range response.GetBranchNames() { + branchNames = append(branchNames, string(branchName)) + } + } + + testhelper.RequireGrpcError(t, tc.expectedErr, err) + require.ElementsMatch(t, tc.expectedBranches, branchNames) + }) + } +} diff --git a/internal/gitaly/service/ref/refs_test.go b/internal/gitaly/service/ref/refs_test.go index 3149654ef..c3e3d3856 100644 --- a/internal/gitaly/service/ref/refs_test.go +++ b/internal/gitaly/service/ref/refs_test.go @@ -789,226 +789,3 @@ func readFindAllBranchesResponsesFromClient(t *testing.T, c gitalypb.RefService_ return } - -func TestListTagNamesContainingCommit(t *testing.T) { - t.Parallel() - - ctx := testhelper.Context(t) - _, repoProto, _, client := setupRefService(t, ctx) - - testCases := []struct { - description string - commitID string - code codes.Code - limit uint32 - tags []string - }{ - { - description: "no commit ID", - commitID: "", - code: codes.InvalidArgument, - }, - { - description: "current master HEAD", - commitID: "e63f41fe459e62e1228fcef60d7189127aeba95a", - code: codes.OK, - tags: []string{}, - }, - { - description: "init commit", - commitID: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", - code: codes.OK, - tags: []string{"v1.0.0", "v1.1.0"}, - }, - { - description: "limited response size", - commitID: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", - code: codes.OK, - limit: 1, - tags: []string{"v1.0.0"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - request := &gitalypb.ListTagNamesContainingCommitRequest{Repository: repoProto, CommitId: tc.commitID} - - c, err := client.ListTagNamesContainingCommit(ctx, request) - require.NoError(t, err) - - var names []string - for { - r, err := c.Recv() - if err == io.EOF { - break - } else if tc.code != codes.OK { - testhelper.RequireGrpcCode(t, err, tc.code) - - return - } - require.NoError(t, err) - - for _, name := range r.GetTagNames() { - names = append(names, string(name)) - } - } - - // Test for inclusion instead of equality because new refs - // will get added to the gitlab-test repo over time. - require.Subset(t, names, tc.tags) - }) - } -} - -func TestListTagNamesContainingCommit_validate(t *testing.T) { - t.Parallel() - ctx := testhelper.Context(t) - _, repoProto, _, client := setupRefService(t, ctx) - - for _, tc := range []struct { - desc string - req *gitalypb.ListTagNamesContainingCommitRequest - expectedErr error - }{ - { - desc: "repository not provided", - req: &gitalypb.ListTagNamesContainingCommitRequest{Repository: nil}, - expectedErr: status.Error(codes.InvalidArgument, testhelper.GitalyOrPraefect( - "empty Repository", - "repo scoped: empty Repository", - )), - }, - { - desc: "bad commit provided", - req: &gitalypb.ListTagNamesContainingCommitRequest{Repository: repoProto, CommitId: "invalid"}, - expectedErr: status.Error(codes.InvalidArgument, fmt.Sprintf(`invalid object ID: "invalid", expected length %v, got 7`, gittest.DefaultObjectHash.EncodedLen())), - }, - } { - t.Run(tc.desc, func(t *testing.T) { - stream, err := client.ListTagNamesContainingCommit(ctx, tc.req) - require.NoError(t, err) - _, err = stream.Recv() - testhelper.RequireGrpcError(t, tc.expectedErr, err) - }) - } -} - -func TestListBranchNamesContainingCommit(t *testing.T) { - t.Parallel() - - ctx := testhelper.Context(t) - _, repo, _, client := setupRefService(t, ctx) - - testCases := []struct { - description string - commitID string - code codes.Code - limit uint32 - branches []string - }{ - { - description: "no commit ID", - commitID: "", - code: codes.InvalidArgument, - }, - { - description: "current master HEAD", - commitID: "e63f41fe459e62e1228fcef60d7189127aeba95a", - code: codes.OK, - branches: []string{"master"}, - }, - { - // gitlab-test contains a branch refs/heads/1942eed5cc108b19c7405106e81fa96125d0be22 - // which is in conflict with a commit with the same ID - description: "branch name is also commit id", - commitID: "1942eed5cc108b19c7405106e81fa96125d0be22", - code: codes.OK, - branches: []string{"1942eed5cc108b19c7405106e81fa96125d0be22"}, - }, - { - description: "init commit", - commitID: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", - code: codes.OK, - branches: []string{ - "deleted-image-test", - "ends-with.json", - "master", - "conflict-non-utf8", - "'test'", - "ʕ•ᴥ•ʔ", - "'test'", - "100%branch", - }, - }, - { - description: "init commit", - commitID: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", - code: codes.OK, - limit: 1, - branches: []string{"'test'"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - request := &gitalypb.ListBranchNamesContainingCommitRequest{Repository: repo, CommitId: tc.commitID} - - c, err := client.ListBranchNamesContainingCommit(ctx, request) - require.NoError(t, err) - - var names []string - for { - r, err := c.Recv() - if err == io.EOF { - break - } else if tc.code != codes.OK { - testhelper.RequireGrpcCode(t, err, tc.code) - - return - } - require.NoError(t, err) - - for _, name := range r.GetBranchNames() { - names = append(names, string(name)) - } - } - - // Test for inclusion instead of equality because new refs - // will get added to the gitlab-test repo over time. - require.Subset(t, names, tc.branches) - }) - } -} - -func TestListBranchNamesContainingCommit_validate(t *testing.T) { - t.Parallel() - ctx := testhelper.Context(t) - _, repoProto, _, client := setupRefService(t, ctx) - - for _, tc := range []struct { - desc string - req *gitalypb.ListBranchNamesContainingCommitRequest - expectedErr error - }{ - { - desc: "repository not provided", - req: &gitalypb.ListBranchNamesContainingCommitRequest{Repository: nil}, - expectedErr: status.Error(codes.InvalidArgument, testhelper.GitalyOrPraefect( - "empty Repository", - "repo scoped: empty Repository", - )), - }, - { - desc: "bad commit provided", - req: &gitalypb.ListBranchNamesContainingCommitRequest{Repository: repoProto, CommitId: "invalid"}, - expectedErr: status.Error(codes.InvalidArgument, fmt.Sprintf(`invalid object ID: "invalid", expected length %v, got 7`, gittest.DefaultObjectHash.EncodedLen())), - }, - } { - t.Run(tc.desc, func(t *testing.T) { - stream, err := client.ListBranchNamesContainingCommit(ctx, tc.req) - require.NoError(t, err) - _, err = stream.Recv() - testhelper.RequireGrpcError(t, tc.expectedErr, err) - }) - } -} diff --git a/internal/gitaly/service/ref/testhelper_test.go b/internal/gitaly/service/ref/testhelper_test.go index ed60e9561..9d657e51a 100644 --- a/internal/gitaly/service/ref/testhelper_test.go +++ b/internal/gitaly/service/ref/testhelper_test.go @@ -1,5 +1,3 @@ -//go:build !gitaly_test_sha256 - package ref import ( |