diff options
author | Toon Claes <toon@gitlab.com> | 2021-01-11 12:05:16 +0300 |
---|---|---|
committer | Toon Claes <toon@gitlab.com> | 2021-01-11 12:05:16 +0300 |
commit | 04496625daa52aaa82fe4140d898258e7e6fda22 (patch) | |
tree | 862b76cfa0fbab04c6d5f5c84c6a1b321ac973e5 | |
parent | 42f8c9df6f29d8983c0f976b352129ec91499667 (diff) | |
parent | 649c7a79344c8b86cabd40a650c1f3279d1edf27 (diff) |
Merge branch 'avar/port-user-tag-create-to-go' into 'master'
Port UserCreateTag to Go
See merge request gitlab-org/gitaly!2911
-rw-r--r-- | changelogs/unreleased/avar-port-user-tag-create-to-go.yml | 5 | ||||
-rw-r--r-- | internal/git/reference.go | 37 | ||||
-rw-r--r-- | internal/git/reference_test.go | 53 | ||||
-rw-r--r-- | internal/git/repository.go | 92 | ||||
-rw-r--r-- | internal/git/repository_test.go | 89 | ||||
-rw-r--r-- | internal/git/subcommand.go | 6 | ||||
-rw-r--r-- | internal/gitaly/server/auth_test.go | 3 | ||||
-rw-r--r-- | internal/gitaly/service/operations/tags.go | 210 | ||||
-rw-r--r-- | internal/gitaly/service/operations/tags_test.go | 53 | ||||
-rw-r--r-- | internal/metadata/featureflag/feature_flags.go | 3 |
10 files changed, 530 insertions, 21 deletions
diff --git a/changelogs/unreleased/avar-port-user-tag-create-to-go.yml b/changelogs/unreleased/avar-port-user-tag-create-to-go.yml new file mode 100644 index 000000000..443332205 --- /dev/null +++ b/changelogs/unreleased/avar-port-user-tag-create-to-go.yml @@ -0,0 +1,5 @@ +--- +title: Port UserCreateTag to Go +merge_request: 2911 +author: +type: changed diff --git a/internal/git/reference.go b/internal/git/reference.go index 7cb279632..de61d9669 100644 --- a/internal/git/reference.go +++ b/internal/git/reference.go @@ -1,5 +1,10 @@ package git +import ( + "bytes" + "context" +) + // Reference represents a Git reference. type Reference struct { // Name is the name of the reference @@ -29,3 +34,35 @@ func NewSymbolicReference(name, target string) Reference { IsSymbolic: true, } } + +// CheckRefFormatError is used by CheckRefFormat() below +type CheckRefFormatError struct{} + +func (e CheckRefFormatError) Error() string { + return "" +} + +// CheckRefFormat checks whether a fully-qualified refname is well +// well-formed using git-check-ref-format +func CheckRefFormat(ctx context.Context, refName string) (bool, error) { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + cmd, err := SafeCmdWithoutRepo(ctx, nil, + SubCmd{ + Name: "check-ref-format", + Args: []string{refName}, + }, + WithStdout(stdout), + WithStderr(stderr), + ) + if err != nil { + return false, err + } + + if err := cmd.Wait(); err != nil { + return false, CheckRefFormatError{} + } + + return true, nil +} diff --git a/internal/git/reference_test.go b/internal/git/reference_test.go new file mode 100644 index 000000000..cd209fc10 --- /dev/null +++ b/internal/git/reference_test.go @@ -0,0 +1,53 @@ +package git + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/internal/testhelper" +) + +func TestCheckRefFormat(t *testing.T) { + ctx, cancel := testhelper.Context() + defer cancel() + + for _, tc := range []struct { + desc string + tagName string + ok bool + err error + }{ + // Just trivial tests here, most of this is tested in + // internal/gitaly/service/operations/tags_test.go + { + desc: "unqualified name", + tagName: "my-name", + ok: false, + err: CheckRefFormatError{}, + }, + { + desc: "fully-qualified name", + tagName: "refs/heads/my-name", + ok: true, + err: nil, + }, + { + desc: "basic tag", + tagName: "refs/tags/my-tag", + ok: true, + err: nil, + }, + { + desc: "invalid tag", + tagName: "refs/tags/my tag", + ok: false, + err: CheckRefFormatError{}, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + ok, err := CheckRefFormat(ctx, tc.tagName) + require.Equal(t, tc.err, err) + require.Equal(t, tc.ok, ok) + }) + } +} diff --git a/internal/git/repository.go b/internal/git/repository.go index 5d15c9858..558831e09 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "os/exec" "strings" + "time" "gitlab.com/gitlab-org/gitaly/internal/command" "gitlab.com/gitlab-org/gitaly/internal/git/repository" @@ -151,6 +152,97 @@ func (repo *LocalRepository) WriteBlob(ctx context.Context, path string, content return text.ChompBytes(stdout.Bytes()), nil } +// FormatTagError is used by FormatTag() below +type FormatTagError struct { + expectedLines int + actualLines int +} + +func (e FormatTagError) Error() string { + return fmt.Sprintf("should have %d tag header lines, got %d", e.expectedLines, e.actualLines) +} + +// FormatTag is used by WriteTag (or for testing) to make the tag +// signature to feed to git-mktag, i.e. the plain-text mktag +// format. This does not create an object, just crafts input for "git +// mktag" to consume. +// +// We are not being paranoid about exhaustive input validation here +// because we're just about to run git's own "fsck" check on this. +// +// However, if someone injected parameters with extra newlines they +// could cause subsequent values to be ignore via a crafted +// message. This someone could also locally craft a tag locally and +// "git push" it. But allowing e.g. someone to provide their own +// timestamp here would at best be annoying, and at worst run up +// against some other assumption (e.g. that some hook check isn't as +// strict on locally generated data). +func FormatTag(objectID, objectType string, tagName, userName, userEmail, tagBody []byte) (string, error) { + unixEpoch := time.Now().Unix() + tagHeaderFormat := "object %s\n" + + "type %s\n" + + "tag %s\n" + + "tagger %s <%s> %d +0000\n" + tagBuf := fmt.Sprintf(tagHeaderFormat, objectID, objectType, tagName, userName, userEmail, unixEpoch) + + maxHeaderLines := 4 + actualHeaderLines := strings.Count(tagBuf, "\n") + if actualHeaderLines != maxHeaderLines { + return "", FormatTagError{expectedLines: maxHeaderLines, actualLines: actualHeaderLines} + } + + tagBuf += "\n" + tagBuf += string(tagBody) + + return tagBuf, nil +} + +// MktagError is used by WriteTag() below +type MktagError struct { + tagName []byte + stderr string +} + +func (e MktagError) Error() string { + // TODO: Upper-case error message purely for transitory backwards compatibility + return fmt.Sprintf("Could not update refs/tags/%s. Please refresh and try again.", e.tagName) +} + +// WriteTag writes a tag to the repository's object database with +// git-mktag and returns its object ID. +// +// It's important that this be git-mktag and not git-hash-object due +// to its fsck sanity checking semantics. +func (repo *LocalRepository) WriteTag(ctx context.Context, objectID, objectType string, tagName, userName, userEmail, tagBody []byte) (string, error) { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + tagBuf, err := FormatTag(objectID, objectType, tagName, userName, userEmail, tagBody) + if err != nil { + return "", err + } + + content := strings.NewReader(tagBuf) + + cmd, err := repo.command(ctx, nil, + SubCmd{ + Name: "mktag", + }, + WithStdin(content), + WithStdout(stdout), + WithStderr(stderr), + ) + if err != nil { + return "", err + } + + if err := cmd.Wait(); err != nil { + return "", MktagError{tagName: tagName, stderr: stderr.String()} + } + + return text.ChompBytes(stdout.Bytes()), nil +} + // ReadObject reads an object from the repository's object database. InvalidObjectError // is returned if the oid does not refer to a valid object. func (repo *LocalRepository) ReadObject(ctx context.Context, oid string) ([]byte, error) { diff --git a/internal/git/repository_test.go b/internal/git/repository_test.go index ec7701936..f68878eb6 100644 --- a/internal/git/repository_test.go +++ b/internal/git/repository_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/internal/helper/text" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" ) @@ -299,6 +300,94 @@ lf text } } +func TestLocalRepository_FormatTag(t *testing.T) { + for _, tc := range []struct { + desc string + objectID string + objectType string + tagName []byte + userName []byte + userEmail []byte + tagBody []byte + err error + }{ + // Just trivial tests here, most of this is tested in + // internal/gitaly/service/operations/tags_test.go + { + desc: "basic signature", + objectID: "0000000000000000000000000000000000000000", + objectType: "commit", + tagName: []byte("my-tag"), + userName: []byte("root"), + userEmail: []byte("root@localhost"), + tagBody: []byte(""), + }, + { + desc: "basic signature", + objectID: "0000000000000000000000000000000000000000", + objectType: "commit", + tagName: []byte("my-tag\ninjection"), + userName: []byte("root"), + userEmail: []byte("root@localhost"), + tagBody: []byte(""), + err: FormatTagError{expectedLines: 4, actualLines: 5}, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + signature, err := FormatTag(tc.objectID, tc.objectType, tc.tagName, tc.userName, tc.userEmail, tc.tagBody) + if err != nil { + require.Equal(t, tc.err, err) + require.Equal(t, "", signature) + } else { + require.NoError(t, err) + require.Contains(t, signature, "object ") + require.Contains(t, signature, "tag ") + require.Contains(t, signature, "tagger ") + } + }) + } +} + +func TestLocalRepository_WriteTag(t *testing.T) { + ctx, cancel := testhelper.Context() + defer cancel() + + pbRepo, repoPath, clean := testhelper.NewTestRepo(t) + defer clean() + + repo := NewRepository(pbRepo) + + for _, tc := range []struct { + desc string + objectID string + objectType string + tagName []byte + userName []byte + userEmail []byte + tagBody []byte + }{ + // Just trivial tests here, most of this is tested in + // internal/gitaly/service/operations/tags_test.go + { + desc: "basic signature", + objectID: "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + objectType: "commit", + tagName: []byte("my-tag"), + userName: []byte("root"), + userEmail: []byte("root@localhost"), + tagBody: []byte(""), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + tagObjID, err := repo.WriteTag(ctx, tc.objectID, tc.objectType, tc.tagName, tc.userName, tc.userEmail, tc.tagBody) + require.NoError(t, err) + + repoTagObjID := testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "rev-parse", tagObjID) + require.Equal(t, text.ChompBytes(repoTagObjID), tagObjID) + }) + } +} + func TestLocalRepository_ReadObject(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() diff --git a/internal/git/subcommand.go b/internal/git/subcommand.go index 657b15815..27b96e05c 100644 --- a/internal/git/subcommand.go +++ b/internal/git/subcommand.go @@ -34,6 +34,9 @@ var gitCommands = map[string]gitCommand{ "cat-file": gitCommand{ flags: scReadOnly, }, + "check-ref-format": gitCommand{ + flags: scReadOnly | scNoRefUpdates | scNoEndOfOptions, + }, "checkout": gitCommand{ flags: scNoEndOfOptions, }, @@ -97,6 +100,9 @@ var gitCommands = map[string]gitCommand{ "merge-base": gitCommand{ flags: scReadOnly, }, + "mktag": gitCommand{ + flags: scNoRefUpdates | scNoEndOfOptions, + }, "multi-pack-index": gitCommand{ flags: scNoRefUpdates, }, diff --git a/internal/gitaly/server/auth_test.go b/internal/gitaly/server/auth_test.go index a47cf2911..fb9ee45e5 100644 --- a/internal/gitaly/server/auth_test.go +++ b/internal/gitaly/server/auth_test.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/gitaly/client" "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/internal/gitaly/config/auth" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/hook" "gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -197,7 +198,7 @@ func newOperationClient(t *testing.T, serverSocketPath string) (gitalypb.Operati func runServerWithRuby(t *testing.T, ruby *rubyserver.Server) (string, func()) { conns := client.NewPool() - srv := NewInsecure(ruby, nil, config.Config, conns) + srv := NewInsecure(ruby, hook.NewManager(config.NewLocator(config.Config), hook.GitlabAPIStub, config.Config), config.Config, conns) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() diff --git a/internal/gitaly/service/operations/tags.go b/internal/gitaly/service/operations/tags.go index 071f9f8d8..deda38220 100644 --- a/internal/gitaly/service/operations/tags.go +++ b/internal/gitaly/service/operations/tags.go @@ -1,11 +1,15 @@ package operations import ( + "bytes" "context" "errors" "fmt" + "regexp" "gitlab.com/gitlab-org/gitaly/internal/git" + "gitlab.com/gitlab-org/gitaly/internal/git/catfile" + "gitlab.com/gitlab-org/gitaly/internal/git/log" "gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver" "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -69,6 +73,13 @@ func (s *Server) UserDeleteTagGo(ctx context.Context, req *gitalypb.UserDeleteTa } func (s *Server) UserCreateTag(ctx context.Context, req *gitalypb.UserCreateTagRequest) (*gitalypb.UserCreateTagResponse, error) { + if featureflag.IsDisabled(ctx, featureflag.GoUserCreateTag) { + return s.userCreateTagRuby(ctx, req) + } + return s.userCreateTagGo(ctx, req) +} + +func (s *Server) userCreateTagRuby(ctx context.Context, req *gitalypb.UserCreateTagRequest) (*gitalypb.UserCreateTagResponse, error) { client, err := s.ruby.OperationServiceClient(ctx) if err != nil { return nil, err @@ -81,3 +92,202 @@ func (s *Server) UserCreateTag(ctx context.Context, req *gitalypb.UserCreateTagR return client.UserCreateTag(clientCtx, req) } + +func validateUserCreateTagGo(req *gitalypb.UserCreateTagRequest) error { + // Emulate validations done by Ruby. A lot of these (e.g. the + // upper-case error messages) can be simplified once we're not + // doing bug-for-bug Ruby emulation anymore) + if len(req.TagName) == 0 { + return status.Errorf(codes.InvalidArgument, "empty tag name") + } + + if bytes.Contains(req.TagName, []byte(" ")) { + return status.Errorf(codes.Unknown, "Gitlab::Git::CommitError: Could not update refs/tags/%s. Please refresh and try again.", req.TagName) + } + + if req.User == nil { + return status.Errorf(codes.InvalidArgument, "empty user") + } + + if len(req.TargetRevision) == 0 { + return status.Error(codes.InvalidArgument, "empty target revision") + } + + if bytes.Contains(req.Message, []byte("\000")) { + return status.Errorf(codes.Unknown, "ArgumentError: string contains null byte") + } + + // Our own Go-specific validation + if req.GetRepository() == nil { + return status.Errorf(codes.Internal, "empty repository") + } + + return nil +} + +func (s *Server) userCreateTagGo(ctx context.Context, req *gitalypb.UserCreateTagRequest) (*gitalypb.UserCreateTagResponse, error) { + // Validate the request + if err := validateUserCreateTagGo(req); err != nil { + return nil, err + } + + // Setup + repo := req.GetRepository() + catFile, err := catfile.New(ctx, s.locator, repo) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + // We allow all ways to name a revision that cat-file + // supports, not just OID. Resolve it. + targetRevision := string(req.TargetRevision) + targetInfo, err := catFile.Info(ctx, targetRevision) + if err != nil { + return nil, status.Errorf(codes.FailedPrecondition, "revspec '%s' not found", targetRevision) + } + targetObjectID, targetObjectType := targetInfo.Oid, targetInfo.Type + + // Whether we have a "message" parameter tells us if we're + // making a lightweight tag or an annotated tag. Maybe we can + // use strings.TrimSpace() eventually, but as the tests show + // the Ruby idea of Unicode whitespace is different than its + // idea. + makingTag := regexp.MustCompile(`\S`).Match(req.Message) + + // If we're creating a tag to another "tag" we'll need to peel + // (possibly recursively) all the way down to the + // non-tag. That'll be our returned TargetCommit if it's a + // commit (nil if tree or blob). + // + // If we're not pointing to a tag we pretend our "peeled" is + // the commit/tree/blob object itself in subsequent logic. + peeledTargetObjectID, peeledTargetObjectType := targetObjectID, targetObjectType + if targetObjectType == "tag" { + peeledTargetObjectInfo, err := catFile.Info(ctx, targetRevision+"^{}") + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + peeledTargetObjectID, peeledTargetObjectType = peeledTargetObjectInfo.Oid, peeledTargetObjectInfo.Type + + // If we're not making an annotated tag ourselves and + // the user pointed to a "tag" we'll ignore what they + // asked for and point to what we found when peeling + // that tag. + if !makingTag { + targetObjectID = peeledTargetObjectID + } + } + + // At this point we'll either be pointing to an object we were + // provided with, or creating a new tag object and pointing to + // that. + refObjectID := targetObjectID + var tagObject *gitalypb.Tag + if makingTag { + localRepo := git.NewRepository(repo) + tagObjectID, err := localRepo.WriteTag(ctx, targetObjectID, targetObjectType, req.TagName, req.User.Name, req.User.Email, req.Message) + if err != nil { + var FormatTagError git.FormatTagError + if errors.As(err, &FormatTagError) { + return nil, status.Errorf(codes.Unknown, "Rugged::InvalidError: failed to parse signature - expected prefix doesn't match actual") + } + + var MktagError git.MktagError + if errors.As(err, &MktagError) { + return nil, status.Errorf(codes.NotFound, "Gitlab::Git::CommitError: %s", err.Error()) + } + return nil, status.Error(codes.Internal, err.Error()) + } + + createdTag, err := log.GetTagCatfile(ctx, catFile, tagObjectID, string(req.TagName), false, false) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + tagObject = &gitalypb.Tag{ + Name: req.TagName, + Id: tagObjectID, + Message: createdTag.Message, + MessageSize: createdTag.MessageSize, + //TargetCommit: is filled in below if needed + } + + refObjectID = tagObjectID + } else { + tagObject = &gitalypb.Tag{ + Name: req.TagName, + Id: peeledTargetObjectID, + //TargetCommit: is filled in below if needed + } + } + + referenceName := fmt.Sprintf("refs/tags/%s", req.TagName) + if err := s.updateReferenceWithHooks(ctx, req.Repository, req.User, referenceName, refObjectID, git.NullSHA); err != nil { + var preReceiveError preReceiveError + if errors.As(err, &preReceiveError) { + return &gitalypb.UserCreateTagResponse{ + PreReceiveError: preReceiveError.message, + }, nil + } + + var updateRefError updateRefError + if errors.As(err, &updateRefError) { + refNameOK, err := git.CheckRefFormat(ctx, referenceName) + if refNameOK { + // The tag might not actually exist, + // perhaps update-ref died for some + // other reason. But saying that it + // does is what the Ruby code used to + // do, so let's follow it off that + // cliff. + return &gitalypb.UserCreateTagResponse{ + Tag: nil, + Exists: true, + }, nil + } + + var CheckRefFormatError git.CheckRefFormatError + if errors.As(err, &CheckRefFormatError) { + // It doesn't make sense either to + // tell the user to retry with an + // invalid ref name, but ditto on the + // Ruby bug-for-bug emulation. + return &gitalypb.UserCreateTagResponse{ + Tag: nil, + Exists: true, + }, status.Errorf(codes.Unknown, "Gitlab::Git::CommitError: Could not update refs/tags/%s. Please refresh and try again.", req.TagName) + } + + // Should only be reachable if "git + // check-ref-format"'s invocation is + // incorrect, or if it segfaults on startup + // etc. + return nil, status.Error(codes.Internal, err.Error()) + } + + // The Ruby code did not return this, but always an + // "Exists: true" on update-ref failure without any + // meaningful error. This is our "PANIC" response, if + // we've got an unknown error (it should all be + // updateRefError above) let's relay it to the + // caller. This should not happen. + return &gitalypb.UserCreateTagResponse{ + Tag: nil, + Exists: true, + }, status.Error(codes.Internal, err.Error()) + } + + // Save ourselves looking this up earlier in case update-ref + // died + if peeledTargetObjectType == "commit" { + peeledTargetCommit, err := log.GetCommitCatfile(ctx, catFile, peeledTargetObjectID) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + tagObject.TargetCommit = peeledTargetCommit + } + + return &gitalypb.UserCreateTagResponse{ + Tag: tagObject, + }, nil +} diff --git a/internal/gitaly/service/operations/tags_test.go b/internal/gitaly/service/operations/tags_test.go index 1c8b20cdd..9202af691 100644 --- a/internal/gitaly/service/operations/tags_test.go +++ b/internal/gitaly/service/operations/tags_test.go @@ -170,6 +170,7 @@ end`, config.Config.Git.BinPath) func TestSuccessfulUserCreateTagRequest(t *testing.T) { testhelper.NewFeatureSets([]featureflag.FeatureFlag{ featureflag.ReferenceTransactions, + featureflag.GoUserCreateTag, }).Run(t, testSuccessfulUserCreateTagRequest) } @@ -277,6 +278,7 @@ func testSuccessfulUserCreateTagRequest(t *testing.T, ctx context.Context) { func TestSuccessfulUserCreateTagRequestAnnotatedLightweightDisambiguation(t *testing.T) { testhelper.NewFeatureSets([]featureflag.FeatureFlag{ featureflag.ReferenceTransactions, + featureflag.GoUserCreateTag, }).Run(t, testSuccessfulUserCreateTagRequestAnnotatedLightweightDisambiguation) } @@ -382,6 +384,7 @@ func testSuccessfulUserCreateTagRequestAnnotatedLightweightDisambiguation(t *tes func TestSuccessfulUserCreateTagRequestWithParsedTargetRevision(t *testing.T) { testhelper.NewFeatureSets([]featureflag.FeatureFlag{ featureflag.ReferenceTransactions, + featureflag.GoUserCreateTag, }).Run(t, testSuccessfulUserCreateTagRequestWithParsedTargetRevision) } @@ -471,9 +474,11 @@ func testSuccessfulUserCreateTagRequestWithParsedTargetRevision(t *testing.T, ct } func TestSuccessfulUserCreateTagRequestToNonCommit(t *testing.T) { - ctx, cancel := testhelper.Context() - defer cancel() + testWithFeature(t, featureflag.GoUserCreateTag, testSuccessfulUserCreateTagRequestToNonCommit) +} +//nolint:golint +func testSuccessfulUserCreateTagRequestToNonCommit(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -593,9 +598,11 @@ func TestSuccessfulUserCreateTagRequestToNonCommit(t *testing.T) { } func TestSuccessfulUserCreateTagNestedTags(t *testing.T) { - ctx, cancel := testhelper.Context() - defer cancel() + testWithFeature(t, featureflag.GoUserCreateTag, testSuccessfulUserCreateTagNestedTags) +} +//nolint:golint +func testSuccessfulUserCreateTagNestedTags(t *testing.T, ctx context.Context) { locator := config.NewLocator(config.Config) serverSocketPath, stop := runOperationServiceServer(t) @@ -789,6 +796,11 @@ func testUserDeleteTagsuccessfulDeletionOfPrefixedTag(t *testing.T, ctx context. } func TestUserCreateTagsuccessfulCreationOfPrefixedTag(t *testing.T) { + testWithFeature(t, featureflag.GoUserCreateTag, testUserCreateTagsuccessfulCreationOfPrefixedTag) +} + +//nolint:golint +func testUserCreateTagsuccessfulCreationOfPrefixedTag(t *testing.T, ctx context.Context) { testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() @@ -827,9 +839,6 @@ func TestUserCreateTagsuccessfulCreationOfPrefixedTag(t *testing.T) { User: testCase.user, } - ctx, cancel := testhelper.Context() - defer cancel() - response, err := client.UserCreateTag(ctx, request) require.Equal(t, testCase.err, err) commitOk, err := log.GetCommit(ctx, locator, testRepo, testCase.tagTargetRevisionInput) @@ -852,10 +861,7 @@ func TestUserCreateTagsuccessfulCreationOfPrefixedTag(t *testing.T) { } func TestSuccessfulGitHooksForUserCreateTagRequest(t *testing.T) { - ctx, cancel := testhelper.Context() - defer cancel() - - testSuccessfulGitHooksForUserCreateTagRequest(t, ctx) + testWithFeature(t, featureflag.GoUserCreateTag, testSuccessfulGitHooksForUserCreateTagRequest) } func testSuccessfulGitHooksForUserCreateTagRequest(t *testing.T, ctx context.Context) { @@ -1026,6 +1032,11 @@ func testFailedUserDeleteTagDueToHooks(t *testing.T, ctx context.Context) { } func TestFailedUserCreateTagDueToHooks(t *testing.T) { + testWithFeature(t, featureflag.GoUserCreateTag, testFailedUserCreateTagDueToHooks) +} + +//nolint:golint +func testFailedUserCreateTagDueToHooks(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -1049,9 +1060,6 @@ func TestFailedUserCreateTagDueToHooks(t *testing.T) { require.NoError(t, err) defer remove() - ctx, cancel := testhelper.Context() - defer cancel() - response, err := client.UserCreateTag(ctx, request) require.Nil(t, err) require.Contains(t, response.PreReceiveError, "GL_ID="+testhelper.TestUser.GlId) @@ -1059,6 +1067,11 @@ func TestFailedUserCreateTagDueToHooks(t *testing.T) { } func TestFailedUserCreateTagRequestDueToTagExistence(t *testing.T) { + testWithFeature(t, featureflag.GoUserCreateTag, testFailedUserCreateTagRequestDueToTagExistence) +} + +//nolint:golint +func testFailedUserCreateTagRequestDueToTagExistence(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -1068,9 +1081,6 @@ func TestFailedUserCreateTagRequestDueToTagExistence(t *testing.T) { testRepo, _, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctx, cancel := testhelper.Context() - defer cancel() - testCases := []struct { desc string tagName string @@ -1117,6 +1127,11 @@ func TestFailedUserCreateTagRequestDueToTagExistence(t *testing.T) { } func TestFailedUserCreateTagRequestDueToValidation(t *testing.T) { + testWithFeature(t, featureflag.GoUserCreateTag, testFailedUserCreateTagRequestDueToValidation) +} + +//nolint:golint +func testFailedUserCreateTagRequestDueToValidation(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -1231,9 +1246,6 @@ func TestFailedUserCreateTagRequestDueToValidation(t *testing.T) { Message: []byte(testCase.message), } - ctx, cancel := testhelper.Context() - defer cancel() - response, err := client.UserCreateTag(ctx, request) require.Equal(t, testCase.err, err) require.Equal(t, testCase.response, response) @@ -1245,6 +1257,7 @@ func TestTagHookOutput(t *testing.T) { testhelper.NewFeatureSets([]featureflag.FeatureFlag{ featureflag.GoUserDeleteTag, featureflag.ReferenceTransactions, + featureflag.GoUserCreateTag, }).Run(t, testTagHookOutput) } diff --git a/internal/metadata/featureflag/feature_flags.go b/internal/metadata/featureflag/feature_flags.go index a4fce6ffc..1221d0ea7 100644 --- a/internal/metadata/featureflag/feature_flags.go +++ b/internal/metadata/featureflag/feature_flags.go @@ -38,6 +38,8 @@ var ( GoFetchRemote = FeatureFlag{Name: "go_fetch_remote", OnByDefault: true} // GoUserDeleteTag enables the Go implementation of UserDeleteTag GoUserDeleteTag = FeatureFlag{Name: "go_user_delete_tag", OnByDefault: false} + // GoUserCreateTag enables the Go implementation of UserCreateTag + GoUserCreateTag = FeatureFlag{Name: "go_user_create_tag", OnByDefault: false} // GoUserRevert enables the Go implementation of UserRevert GoUserRevert = FeatureFlag{Name: "go_user_revert", OnByDefault: false} @@ -121,6 +123,7 @@ var All = []FeatureFlag{ GoUserUpdateSubmodule, GoFetchRemote, GoUserDeleteTag, + GoUserCreateTag, GoUserRevert, TxApplyBfgObjectMapStream, TxResolveConflicts, |