Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToon Claes <toon@gitlab.com>2021-01-11 12:05:16 +0300
committerToon Claes <toon@gitlab.com>2021-01-11 12:05:16 +0300
commit04496625daa52aaa82fe4140d898258e7e6fda22 (patch)
tree862b76cfa0fbab04c6d5f5c84c6a1b321ac973e5
parent42f8c9df6f29d8983c0f976b352129ec91499667 (diff)
parent649c7a79344c8b86cabd40a650c1f3279d1edf27 (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.yml5
-rw-r--r--internal/git/reference.go37
-rw-r--r--internal/git/reference_test.go53
-rw-r--r--internal/git/repository.go92
-rw-r--r--internal/git/repository_test.go89
-rw-r--r--internal/git/subcommand.go6
-rw-r--r--internal/gitaly/server/auth_test.go3
-rw-r--r--internal/gitaly/service/operations/tags.go210
-rw-r--r--internal/gitaly/service/operations/tags_test.go53
-rw-r--r--internal/metadata/featureflag/feature_flags.go3
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,