diff options
-rw-r--r-- | internal/gitaly/service/operations/tags.go | 23 | ||||
-rw-r--r-- | internal/gitaly/service/operations/tags_test.go | 70 |
2 files changed, 87 insertions, 6 deletions
diff --git a/internal/gitaly/service/operations/tags.go b/internal/gitaly/service/operations/tags.go index eb6d99817..91791043b 100644 --- a/internal/gitaly/service/operations/tags.go +++ b/internal/gitaly/service/operations/tags.go @@ -33,16 +33,27 @@ func validateUserDeleteTagRequest(in *gitalypb.UserDeleteTagRequest) error { //nolint:revive // This is unintentionally missing documentation. func (s *Server) UserDeleteTag(ctx context.Context, req *gitalypb.UserDeleteTagRequest) (*gitalypb.UserDeleteTagResponse, error) { - if err := validateUserDeleteTagRequest(req); err != nil { + var err error + + if err = validateUserDeleteTagRequest(req); err != nil { return nil, helper.ErrInvalidArgument(err) } referenceName := git.ReferenceName(fmt.Sprintf("refs/tags/%s", req.TagName)) - revision, err := s.localrepo(req.GetRepository()).ResolveRevision(ctx, referenceName.Revision()) - if err != nil { - if errors.Is(err, git.ErrReferenceNotFound) { - return nil, helper.ErrFailedPreconditionf("tag not found: %s", req.TagName) + + var revision git.ObjectID + if expectedOldOID := req.GetExpectedOldOid(); expectedOldOID != "" { + revision, err = s.localrepo(req.GetRepository()).ResolveRevision(ctx, git.Revision(expectedOldOID)) + if err != nil { + return nil, helper.ErrFailedPreconditionf("resolve object ID: %s: %w", expectedOldOID, err) + } + } else { + revision, err = s.localrepo(req.GetRepository()).ResolveRevision(ctx, referenceName.Revision()) + if err != nil { + if errors.Is(err, git.ErrReferenceNotFound) { + return nil, helper.ErrFailedPreconditionf("tag not found: %s", req.TagName) + } + return nil, helper.ErrInternalf("resolving revision %q: %w", referenceName, err) } - return nil, helper.ErrInternalf("resolving revision %q: %w", referenceName, err) } if err := s.updateReferenceWithHooks(ctx, req.Repository, req.User, nil, referenceName, git.ObjectHashSHA1.ZeroOID, revision); err != nil { diff --git a/internal/gitaly/service/operations/tags_test.go b/internal/gitaly/service/operations/tags_test.go index 9ecf3c82b..4c192350c 100644 --- a/internal/gitaly/service/operations/tags_test.go +++ b/internal/gitaly/service/operations/tags_test.go @@ -51,6 +51,76 @@ func TestUserDeleteTag_successful(t *testing.T) { require.NotContains(t, string(tags), tagNameInput, "tag name still exists in tags list") } +func TestUserDeleteTag_successfulWithExpectedOldOID(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) + + ctx, cfg, repo, repoPath, client := setupOperationsService(t, ctx) + + tagNameInput := "to-be-deleted-soon-tag" + + gittest.Exec(t, cfg, "-C", repoPath, "tag", tagNameInput) + + request := &gitalypb.UserDeleteTagRequest{ + Repository: repo, + TagName: []byte(tagNameInput), + User: gittest.TestUser, + ExpectedOldOid: text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", tagNameInput)), + } + + response, err := client.UserDeleteTag(ctx, request) + require.NoError(t, err) + require.Empty(t, response.PreReceiveError) + + tags := gittest.Exec(t, cfg, "-C", repoPath, "tag") + require.NotContains(t, string(tags), tagNameInput, "tag name still exists in tags list") +} + +func TestUserDeleteTag_failureWithExpectedOldOID(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) + + ctx, cfg, repo, repoPath, client := setupOperationsService(t, ctx) + + tagNameInput := "to-be-deleted-soon-tag" + + gittest.Exec(t, cfg, "-C", repoPath, "tag", tagNameInput) + + testCases := []struct { + desc string + request *gitalypb.UserDeleteTagRequest + expectedErr error + }{ + { + desc: "invalid expectedOldOID", + request: &gitalypb.UserDeleteTagRequest{ + Repository: repo, + TagName: []byte(tagNameInput), + User: gittest.TestUser, + ExpectedOldOid: "foobar", + }, + expectedErr: helper.ErrFailedPreconditionf(`resolve object ID: foobar: reference not found`), + }, + { + desc: "incorrect expectedOldOID", + request: &gitalypb.UserDeleteTagRequest{ + Repository: repo, + TagName: []byte(tagNameInput), + User: gittest.TestUser, + ExpectedOldOid: text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", tagNameInput+"~1")), + }, + expectedErr: helper.ErrFailedPreconditionf(`Could not update refs/tags/to-be-deleted-soon-tag. Please refresh and try again.`), + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + _, err := client.UserDeleteTag(ctx, tc.request) + testhelper.RequireGrpcError(t, tc.expectedErr, err) + }) + } +} + func TestUserDeleteTag_hooks(t *testing.T) { t.Parallel() ctx := testhelper.Context(t) |