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:
-rw-r--r--cmd/gitaly-git2go/main.go1
-rw-r--r--cmd/gitaly-git2go/submodule.go136
-rw-r--r--cmd/gitaly-git2go/submodule_test.go126
-rw-r--r--internal/git2go/submodule.go110
-rw-r--r--internal/git2go/submodule_test.go186
-rw-r--r--internal/gitaly/service/operations/submodules.go116
-rw-r--r--internal/gitaly/service/operations/submodules_test.go44
-rw-r--r--internal/helper/fstype/detect_unix.go2
-rw-r--r--internal/metadata/featureflag/feature_flags.go4
9 files changed, 710 insertions, 15 deletions
diff --git a/cmd/gitaly-git2go/main.go b/cmd/gitaly-git2go/main.go
index 488300ba2..04eab4dff 100644
--- a/cmd/gitaly-git2go/main.go
+++ b/cmd/gitaly-git2go/main.go
@@ -23,6 +23,7 @@ var subcommands = map[string]subcmd{
"merge": &mergeSubcommand{},
"revert": &revertSubcommand{},
"resolve": &resolveSubcommand{},
+ "submodule": &submoduleSubcommand{},
}
const programName = "gitaly-git2go"
diff --git a/cmd/gitaly-git2go/submodule.go b/cmd/gitaly-git2go/submodule.go
new file mode 100644
index 000000000..d256188b1
--- /dev/null
+++ b/cmd/gitaly-git2go/submodule.go
@@ -0,0 +1,136 @@
+// +build static,system_libgit2
+
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "time"
+
+ git "github.com/libgit2/git2go/v30"
+ "gitlab.com/gitlab-org/gitaly/internal/git2go"
+)
+
+type submoduleSubcommand struct {
+ request string
+}
+
+func (cmd *submoduleSubcommand) Flags() *flag.FlagSet {
+ flags := flag.NewFlagSet("submodule", flag.ExitOnError)
+ flags.StringVar(&cmd.request, "request", "", "git2go.SubmoduleCommand")
+ return flags
+}
+
+func (cmd *submoduleSubcommand) Run(_ context.Context, _ io.Reader, w io.Writer) error {
+ request, err := git2go.SubmoduleCommandFromSerialized(cmd.request)
+ if err != nil {
+ return fmt.Errorf("deserializing submodule command request: %w", err)
+ }
+
+ if request.AuthorDate.IsZero() {
+ request.AuthorDate = time.Now()
+ }
+
+ smCommitOID, err := git.NewOid(request.CommitSHA)
+ if err != nil {
+ return fmt.Errorf("converting %s to OID: %w", request.CommitSHA, err)
+ }
+
+ repo, err := git.OpenRepository(request.Repository)
+ if err != nil {
+ return fmt.Errorf("open repository: %w", err)
+ }
+
+ fullBranchRefName := "refs/heads/" + request.Branch
+ o, err := repo.RevparseSingle(fullBranchRefName)
+ if err != nil {
+ return fmt.Errorf("%s: %w", git2go.LegacyErrPrefixInvalidBranch, err) //nolint
+ }
+
+ startCommit, err := o.AsCommit()
+ if err != nil {
+ return fmt.Errorf("peeling %s as a commit: %w", o.Id(), err)
+ }
+
+ rootTree, err := startCommit.Tree()
+ if err != nil {
+ return fmt.Errorf("root tree from starting commit: %w", err)
+ }
+
+ index, err := git.NewIndex()
+ if err != nil {
+ return fmt.Errorf("creating new index: %w", err)
+ }
+
+ if err := index.ReadTree(rootTree); err != nil {
+ return fmt.Errorf("reading root tree into index: %w", err)
+ }
+
+ smEntry, err := index.EntryByPath(request.Submodule, 0)
+ if err != nil {
+ return fmt.Errorf(
+ "%s: %w",
+ git2go.LegacyErrPrefixInvalidSubmodulePath, err,
+ ) //nolint
+ }
+
+ if smEntry.Id.Cmp(smCommitOID) == 0 {
+ //nolint
+ return fmt.Errorf(
+ "The submodule %s is already at %s",
+ request.Submodule, request.CommitSHA,
+ )
+ }
+
+ if smEntry.Mode != git.FilemodeCommit {
+ return fmt.Errorf(
+ "%s: %w",
+ git2go.LegacyErrPrefixInvalidSubmodulePath, err,
+ ) //nolint
+ }
+
+ newEntry := *smEntry // copy by value
+ newEntry.Id = smCommitOID // assign new commit SHA
+ if err := index.Add(&newEntry); err != nil {
+ return fmt.Errorf("add new submodule entry to index: %w", err)
+ }
+
+ newRootTreeOID, err := index.WriteTreeTo(repo)
+ if err != nil {
+ return fmt.Errorf("write index to repo: %w", err)
+ }
+
+ newTree, err := repo.LookupTree(newRootTreeOID)
+ if err != nil {
+ return fmt.Errorf("looking up new submodule entry root tree: %w", err)
+ }
+
+ committer := git.Signature(
+ git2go.NewSignature(
+ request.AuthorName,
+ request.AuthorMail,
+ request.AuthorDate,
+ ),
+ )
+ newCommitOID, err := repo.CreateCommit(
+ "", // caller should update branch with hooks
+ &committer,
+ &committer,
+ request.Message,
+ newTree,
+ startCommit,
+ )
+ if err != nil {
+ // nolint
+ return fmt.Errorf(
+ "%s: %w",
+ git2go.LegacyErrPrefixFailedCommit, err,
+ )
+ }
+
+ return git2go.SubmoduleResult{
+ CommitID: newCommitOID.String(),
+ }.SerializeTo(w)
+}
diff --git a/cmd/gitaly-git2go/submodule_test.go b/cmd/gitaly-git2go/submodule_test.go
new file mode 100644
index 000000000..33ee1c081
--- /dev/null
+++ b/cmd/gitaly-git2go/submodule_test.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/git/log"
+ "gitlab.com/gitlab-org/gitaly/internal/git/lstree"
+ "gitlab.com/gitlab-org/gitaly/internal/git2go"
+ "gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+)
+
+func TestSubmodule(t *testing.T) {
+ commitMessage := []byte("Update Submodule message")
+
+ testCases := []struct {
+ desc string
+ command git2go.SubmoduleCommand
+ expectedStderr string
+ }{
+ {
+ desc: "Update submodule",
+ command: git2go.SubmoduleCommand{
+ AuthorName: string(testhelper.TestUser.Name),
+ AuthorMail: string(testhelper.TestUser.Email),
+ Message: string(commitMessage),
+ CommitSHA: "41fa1bc9e0f0630ced6a8a211d60c2af425ecc2d",
+ Submodule: "gitlab-grack",
+ Branch: "master",
+ },
+ },
+ {
+ desc: "Update submodule inside folder",
+ command: git2go.SubmoduleCommand{
+ AuthorName: string(testhelper.TestUser.Name),
+ AuthorMail: string(testhelper.TestUser.Email),
+ Message: string(commitMessage),
+ CommitSHA: "e25eda1fece24ac7a03624ed1320f82396f35bd8",
+ Submodule: "test_inside_folder/another_folder/six",
+ Branch: "submodule_inside_folder",
+ },
+ },
+ {
+ desc: "Invalid branch",
+ command: git2go.SubmoduleCommand{
+ AuthorName: string(testhelper.TestUser.Name),
+ AuthorMail: string(testhelper.TestUser.Email),
+ Message: string(commitMessage),
+ CommitSHA: "e25eda1fece24ac7a03624ed1320f82396f35bd8",
+ Submodule: "test_inside_folder/another_folder/six",
+ Branch: "non/existent",
+ },
+ expectedStderr: "Invalid branch",
+ },
+ {
+ desc: "Invalid submodule",
+ command: git2go.SubmoduleCommand{
+ AuthorName: string(testhelper.TestUser.Name),
+ AuthorMail: string(testhelper.TestUser.Email),
+ Message: string(commitMessage),
+ CommitSHA: "e25eda1fece24ac7a03624ed1320f82396f35bd8",
+ Submodule: "non-existent-submodule",
+ Branch: "master",
+ },
+ expectedStderr: "Invalid submodule path",
+ },
+ {
+ desc: "Duplicate reference",
+ command: git2go.SubmoduleCommand{
+ AuthorName: string(testhelper.TestUser.Name),
+ AuthorMail: string(testhelper.TestUser.Email),
+ Message: string(commitMessage),
+ CommitSHA: "409f37c4f05865e4fb208c771485f211a22c4c2d",
+ Submodule: "six",
+ Branch: "master",
+ },
+ expectedStderr: "The submodule six is already at 409f37c4f",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ testRepo, testRepoPath, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+
+ tc.command.Repository = testRepoPath
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ response, err := tc.command.Run(ctx, config.Config)
+ if tc.expectedStderr != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.expectedStderr)
+ return
+ }
+ require.NoError(t, err)
+
+ commit, err := log.GetCommit(ctx, testRepo, response.CommitID)
+ require.NoError(t, err)
+ require.Equal(t, commit.Author.Email, testhelper.TestUser.Email)
+ require.Equal(t, commit.Committer.Email, testhelper.TestUser.Email)
+ require.Equal(t, commit.Subject, commitMessage)
+
+ entry := testhelper.MustRunCommand(
+ t,
+ nil,
+ "git",
+ "-C",
+ testRepoPath,
+ "ls-tree",
+ "-z",
+ fmt.Sprintf("%s^{tree}:", response.CommitID),
+ tc.command.Submodule,
+ )
+ parser := lstree.NewParser(bytes.NewReader(entry))
+ parsedEntry, err := parser.NextEntry()
+ require.NoError(t, err)
+ require.Equal(t, tc.command.Submodule, parsedEntry.Path)
+ require.Equal(t, tc.command.CommitSHA, parsedEntry.Oid)
+ })
+ }
+}
diff --git a/internal/git2go/submodule.go b/internal/git2go/submodule.go
new file mode 100644
index 000000000..c4e8fe078
--- /dev/null
+++ b/internal/git2go/submodule.go
@@ -0,0 +1,110 @@
+package git2go
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "time"
+
+ "gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
+)
+
+// Error strings present in the legacy Ruby implementation
+const (
+ LegacyErrPrefixInvalidBranch = "Invalid branch"
+ LegacyErrPrefixInvalidSubmodulePath = "Invalid submodule path"
+ LegacyErrPrefixFailedCommit = "Failed to create commit"
+)
+
+// SubmoduleCommand instructs how to commit a submodule update to a repo
+type SubmoduleCommand struct {
+ // Repository is the path to commit the submodule change
+ Repository string `json:"repository"`
+
+ // AuthorName is the author name of submodule commit.
+ AuthorName string `json:"author_name"`
+ // AuthorMail is the author mail of submodule commit.
+ AuthorMail string `json:"author_mail"`
+ // AuthorDate is the auithor date of submodule commit.
+ AuthorDate time.Time `json:"author_date"`
+ // Message is the message to be used for the submodule commit.
+ Message string `json:"message"`
+
+ // CommitSHA is where the submodule should point
+ CommitSHA string `json:"commit_sha"`
+ // Submodule is the actual submodule string to commit to the tree
+ Submodule string `json:"submodule"`
+ // Branch where to commit submodule update
+ Branch string `json:"branch"`
+}
+
+// SubmoduleResult contains results from a committing a submodule update
+type SubmoduleResult struct {
+ // CommitID is the object ID of the generated submodule commit.
+ CommitID string `json:"commit_id"`
+}
+
+// SubmoduleCommandFromSerialized deserializes the submodule request from its JSON representation encoded with base64.
+func SubmoduleCommandFromSerialized(serialized string) (SubmoduleCommand, error) {
+ var request SubmoduleCommand
+ if err := deserialize(serialized, &request); err != nil {
+ return SubmoduleCommand{}, err
+ }
+
+ if err := request.verify(); err != nil {
+ return SubmoduleCommand{}, fmt.Errorf("submodule: %w", err)
+ }
+
+ return request, nil
+}
+
+// SerializeTo serializes the submodule result and writes it into the writer.
+func (s SubmoduleResult) SerializeTo(w io.Writer) error {
+ return serializeTo(w, s)
+}
+
+// Run attempts to commit the request submodule change
+func (s SubmoduleCommand) Run(ctx context.Context, cfg config.Cfg) (SubmoduleResult, error) {
+ if err := s.verify(); err != nil {
+ return SubmoduleResult{}, fmt.Errorf("submodule: %w", err)
+ }
+
+ serialized, err := serialize(s)
+ if err != nil {
+ return SubmoduleResult{}, err
+ }
+
+ stdout, err := run(ctx, binaryPathFromCfg(cfg), nil, "submodule", "-request", serialized)
+ if err != nil {
+ return SubmoduleResult{}, err
+ }
+
+ var response SubmoduleResult
+ if err := deserialize(stdout.String(), &response); err != nil {
+ return SubmoduleResult{}, err
+ }
+
+ return response, nil
+}
+
+func (s SubmoduleCommand) verify() (err error) {
+ if s.Repository == "" {
+ return InvalidArgumentError("missing repository")
+ }
+ if s.AuthorName == "" {
+ return InvalidArgumentError("missing author name")
+ }
+ if s.AuthorMail == "" {
+ return InvalidArgumentError("missing author mail")
+ }
+ if s.CommitSHA == "" {
+ return InvalidArgumentError("missing commit SHA")
+ }
+ if s.Branch == "" {
+ return InvalidArgumentError("missing branch name")
+ }
+ if s.Submodule == "" {
+ return InvalidArgumentError("missing submodule")
+ }
+ return nil
+}
diff --git a/internal/git2go/submodule_test.go b/internal/git2go/submodule_test.go
new file mode 100644
index 000000000..9327025bd
--- /dev/null
+++ b/internal/git2go/submodule_test.go
@@ -0,0 +1,186 @@
+package git2go
+
+import (
+ "bytes"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestGit2Go_SubmoduleCommandSerialization(t *testing.T) {
+ testcases := []struct {
+ desc string
+ cmd SubmoduleCommand
+ err string
+ }{
+ {
+ desc: "missing repository",
+ cmd: SubmoduleCommand{},
+ err: "missing repository",
+ },
+ {
+ desc: "missing author name",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ },
+ err: "missing author name",
+ },
+ {
+ desc: "missing author mail",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ },
+ err: "missing author mail",
+ },
+ {
+ desc: "missing commit SHA",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ },
+ err: "missing commit SHA",
+ },
+ {
+ desc: "missing branch",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ CommitSHA: "deadbeef1010",
+ },
+ err: "missing branch name",
+ },
+ {
+ desc: "missing submodule path",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ CommitSHA: "deadbeef1010",
+ Branch: "master",
+ },
+ err: "missing submodule",
+ },
+ {
+ desc: "valid command",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ CommitSHA: "deadbeef1010",
+ Branch: "master",
+ Submodule: "path/to/my/subby",
+ },
+ },
+ {
+ desc: "valid command with message",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ Message: "meow to you my friend",
+ CommitSHA: "deadbeef1010",
+ Branch: "master",
+ Submodule: "path/to/my/subby",
+ },
+ },
+ {
+ desc: "valid command with date",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ AuthorDate: time.Now().UTC(),
+ Message: "Message",
+ CommitSHA: "deadbeef1010",
+ Branch: "master",
+ Submodule: "path/to/my/subby",
+ },
+ },
+ {
+ desc: "valid command with message and date",
+ cmd: SubmoduleCommand{
+ Repository: "foo",
+ AuthorName: "Au Thor",
+ AuthorMail: "au@thor.com",
+ AuthorDate: time.Now().UTC(),
+ Message: "woof for dayz",
+ CommitSHA: "deadbeef1010",
+ Branch: "master",
+ Submodule: "path/to/my/subby",
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ serialized, err := serialize(tc.cmd)
+ require.NoError(t, err)
+
+ deserialized, err := SubmoduleCommandFromSerialized(serialized)
+
+ if tc.err != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, deserialized, tc.cmd)
+ }
+ })
+ }
+}
+
+func TestGit2Go_SubmoduleResultSerialization(t *testing.T) {
+ serializeResult := func(t *testing.T, result SubmoduleResult) string {
+ t.Helper()
+ var buf bytes.Buffer
+ err := result.SerializeTo(&buf)
+ require.NoError(t, err)
+ return buf.String()
+ }
+
+ testcases := []struct {
+ desc string
+ serialized string
+ expected SubmoduleResult
+ err string
+ }{
+ {
+ desc: "empty merge result",
+ serialized: serializeResult(t, SubmoduleResult{}),
+ expected: SubmoduleResult{},
+ },
+ {
+ desc: "merge result with commit",
+ serialized: serializeResult(t, SubmoduleResult{
+ CommitID: "1234",
+ }),
+ expected: SubmoduleResult{
+ CommitID: "1234",
+ },
+ },
+ {
+ desc: "invalid serialized representation",
+ serialized: "xvlc",
+ err: "invalid character",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ var deserialized SubmoduleResult
+ err := deserialize(tc.serialized, &deserialized)
+
+ if tc.err != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, deserialized, tc.expected)
+ }
+ })
+ }
+}
diff --git a/internal/gitaly/service/operations/submodules.go b/internal/gitaly/service/operations/submodules.go
index 3a65fef65..b415bf2b6 100644
--- a/internal/gitaly/service/operations/submodules.go
+++ b/internal/gitaly/service/operations/submodules.go
@@ -2,18 +2,32 @@ package operations
import (
"context"
+ "errors"
"fmt"
"regexp"
+ "strings"
+ "time"
+ "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
+ "gitlab.com/gitlab-org/gitaly/internal/git"
+ "gitlab.com/gitlab-org/gitaly/internal/git2go"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver"
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+ "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
+const userUpdateSubmoduleName = "UserUpdateSubmodule"
+
func (s *server) UserUpdateSubmodule(ctx context.Context, req *gitalypb.UserUpdateSubmoduleRequest) (*gitalypb.UserUpdateSubmoduleResponse, error) {
if err := validateUserUpdateSubmoduleRequest(req); err != nil {
- return nil, status.Errorf(codes.InvalidArgument, "UserUpdateSubmodule: %v", err)
+ return nil, status.Errorf(codes.InvalidArgument, userUpdateSubmoduleName+": %v", err)
+ }
+
+ if featureflag.IsEnabled(ctx, featureflag.GoUserUpdateSubmodule) {
+ return s.userUpdateSubmodule(ctx, req)
}
client, err := s.ruby.OperationServiceClient(ctx)
@@ -60,3 +74,103 @@ func validateUserUpdateSubmoduleRequest(req *gitalypb.UserUpdateSubmoduleRequest
return nil
}
+
+func (s *server) userUpdateSubmodule(ctx context.Context, req *gitalypb.UserUpdateSubmoduleRequest) (*gitalypb.UserUpdateSubmoduleResponse, error) {
+ repo := git.NewRepository(req.GetRepository())
+ branches, err := repo.GetBranches(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("%s: get branches: %w", userUpdateSubmoduleName, err)
+ }
+ if len(branches) == 0 {
+ return &gitalypb.UserUpdateSubmoduleResponse{
+ CommitError: "Repository is empty",
+ }, nil
+ }
+
+ branchRef, err := repo.GetBranch(ctx, string(req.GetBranch()))
+ if err != nil {
+ if errors.Is(err, git.ErrReferenceNotFound) {
+ return nil, helper.ErrInvalidArgumentf("Cannot find branch") //nolint
+ }
+ return nil, fmt.Errorf("%s: get branch: %w", userUpdateSubmoduleName, err)
+ }
+
+ repoPath, err := s.locator.GetRepoPath(req.GetRepository())
+ if err != nil {
+ return nil, fmt.Errorf("%s: locate repo: %w", userUpdateSubmoduleName, err)
+ }
+
+ result, err := git2go.SubmoduleCommand{
+ Repository: repoPath,
+ AuthorMail: string(req.GetUser().GetEmail()),
+ AuthorName: string(req.GetUser().GetName()),
+ AuthorDate: time.Now(),
+ Branch: string(req.GetBranch()),
+ CommitSHA: req.GetCommitSha(),
+ Submodule: string(req.GetSubmodule()),
+ Message: string(req.GetCommitMessage()),
+ }.Run(ctx, s.cfg)
+ if err != nil {
+ errStr := strings.TrimPrefix(err.Error(), "submodule: ")
+ errStr = strings.TrimSpace(errStr)
+
+ var resp *gitalypb.UserUpdateSubmoduleResponse
+ for _, legacyErr := range []string{
+ git2go.LegacyErrPrefixInvalidBranch,
+ git2go.LegacyErrPrefixInvalidSubmodulePath,
+ git2go.LegacyErrPrefixFailedCommit,
+ } {
+ if strings.HasPrefix(errStr, legacyErr) {
+ resp = &gitalypb.UserUpdateSubmoduleResponse{
+ CommitError: legacyErr,
+ }
+ ctxlogrus.
+ Extract(ctx).
+ WithError(err).
+ Error(userUpdateSubmoduleName + ": git2go subcommand failure")
+ break
+ }
+ }
+ if strings.Contains(errStr, "is already at") {
+ resp = &gitalypb.UserUpdateSubmoduleResponse{
+ CommitError: errStr,
+ }
+ }
+ if resp != nil {
+ return resp, nil
+ }
+ return nil, fmt.Errorf("%s: submodule subcommand: %w", userUpdateSubmoduleName, err)
+ }
+
+ if err := s.updateReferenceWithHooks(
+ ctx,
+ req.GetRepository(),
+ req.GetUser(),
+ "refs/heads/"+string(req.GetBranch()),
+ result.CommitID,
+ branchRef.Target,
+ ); err != nil {
+ var preReceiveError preReceiveError
+ if errors.As(err, &preReceiveError) {
+ return &gitalypb.UserUpdateSubmoduleResponse{
+ PreReceiveError: preReceiveError.Error(),
+ }, nil
+ }
+
+ var updateRefError updateRefError
+ if errors.As(err, &updateRefError) {
+ // When an error happens updating the reference, e.g. because of a race
+ // with another update, then Ruby code didn't send an error but just an
+ // empty response.
+ return &gitalypb.UserUpdateSubmoduleResponse{}, nil
+ }
+ }
+
+ return &gitalypb.UserUpdateSubmoduleResponse{
+ BranchUpdate: &gitalypb.OperationBranchUpdate{
+ CommitId: result.CommitID,
+ BranchCreated: false,
+ RepoCreated: false,
+ },
+ }, nil
+}
diff --git a/internal/gitaly/service/operations/submodules_test.go b/internal/gitaly/service/operations/submodules_test.go
index e3de793b0..779d9ca4f 100644
--- a/internal/gitaly/service/operations/submodules_test.go
+++ b/internal/gitaly/service/operations/submodules_test.go
@@ -9,16 +9,16 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/internal/git/log"
"gitlab.com/gitlab-org/gitaly/internal/git/lstree"
+ "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag"
"gitlab.com/gitlab-org/gitaly/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
"google.golang.org/grpc/codes"
)
func TestSuccessfulUserUpdateSubmoduleRequest(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
-
- testSuccessfulUserUpdateSubmoduleRequest(t, ctx)
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testSuccessfulUserUpdateSubmoduleRequest)
}
func testSuccessfulUserUpdateSubmoduleRequest(t *testing.T, ctx context.Context) {
@@ -86,6 +86,12 @@ func testSuccessfulUserUpdateSubmoduleRequest(t *testing.T, ctx context.Context)
}
func TestFailedUserUpdateSubmoduleRequestDueToValidations(t *testing.T) {
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testFailedUserUpdateSubmoduleRequestDueToValidations)
+}
+
+func testFailedUserUpdateSubmoduleRequestDueToValidations(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runOperationServiceServer(t)
defer stop()
@@ -200,7 +206,7 @@ func TestFailedUserUpdateSubmoduleRequestDueToValidations(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.desc, func(t *testing.T) {
- ctx, cancel := testhelper.Context()
+ ctx, cancel := context.WithCancel(ctx)
defer cancel()
_, err := client.UserUpdateSubmodule(ctx, testCase.request)
@@ -211,9 +217,12 @@ func TestFailedUserUpdateSubmoduleRequestDueToValidations(t *testing.T) {
}
func TestFailedUserUpdateSubmoduleRequestDueToInvalidBranch(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testFailedUserUpdateSubmoduleRequestDueToInvalidBranch)
+}
+func testFailedUserUpdateSubmoduleRequestDueToInvalidBranch(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runOperationServiceServer(t)
defer stop()
@@ -238,9 +247,12 @@ func TestFailedUserUpdateSubmoduleRequestDueToInvalidBranch(t *testing.T) {
}
func TestFailedUserUpdateSubmoduleRequestDueToInvalidSubmodule(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testFailedUserUpdateSubmoduleRequestDueToInvalidSubmodule)
+}
+func testFailedUserUpdateSubmoduleRequestDueToInvalidSubmodule(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runOperationServiceServer(t)
defer stop()
@@ -265,9 +277,12 @@ func TestFailedUserUpdateSubmoduleRequestDueToInvalidSubmodule(t *testing.T) {
}
func TestFailedUserUpdateSubmoduleRequestDueToSameReference(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testFailedUserUpdateSubmoduleRequestDueToSameReference)
+}
+func testFailedUserUpdateSubmoduleRequestDueToSameReference(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runOperationServiceServer(t)
defer stop()
@@ -295,9 +310,12 @@ func TestFailedUserUpdateSubmoduleRequestDueToSameReference(t *testing.T) {
}
func TestFailedUserUpdateSubmoduleRequestDueToRepositoryEmpty(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
+ testhelper.NewFeatureSets(
+ []featureflag.FeatureFlag{featureflag.GoUserUpdateSubmodule},
+ ).Run(t, testFailedUserUpdateSubmoduleRequestDueToRepositoryEmpty)
+}
+func testFailedUserUpdateSubmoduleRequestDueToRepositoryEmpty(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runOperationServiceServer(t)
defer stop()
diff --git a/internal/helper/fstype/detect_unix.go b/internal/helper/fstype/detect_unix.go
index f946002e0..e5665dae5 100644
--- a/internal/helper/fstype/detect_unix.go
+++ b/internal/helper/fstype/detect_unix.go
@@ -15,7 +15,7 @@ func detectFileSystem(path string) string {
if c == 0 {
break
}
- buf = append(buf, byte(c))
+ buf = append(buf, c)
}
if len(buf) == 0 {
diff --git a/internal/metadata/featureflag/feature_flags.go b/internal/metadata/featureflag/feature_flags.go
index 94ecf25f4..381bfe57c 100644
--- a/internal/metadata/featureflag/feature_flags.go
+++ b/internal/metadata/featureflag/feature_flags.go
@@ -36,6 +36,9 @@ var (
GoUserCommitFiles = FeatureFlag{Name: "go_user_commit_files", OnByDefault: false}
// GoResolveConflicts enables the Go implementation of ResolveConflicts
GoResolveConflicts = FeatureFlag{Name: "go_resolve_conflicts", OnByDefault: false}
+ // GoUserUpdateSubmodule enables the Go implementation of
+ // UserUpdateSubmodules
+ GoUserUpdateSubmodule = FeatureFlag{Name: "go_user_update_submodule", OnByDefault: false}
)
// All includes all feature flags.
@@ -52,6 +55,7 @@ var All = []FeatureFlag{
GoListConflictFiles,
GoUserCommitFiles,
GoResolveConflicts,
+ GoUserUpdateSubmodule,
}
const (