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:
authorJohn Cai <jcai@gitlab.com>2022-11-13 05:10:03 +0300
committerJohn Cai <jcai@gitlab.com>2023-03-04 01:19:28 +0300
commit2e7a4fef2525e66e473ff555b027be1496910110 (patch)
tree9840beea937df4ef32b1e01e4e01c8522d197660
parentd47eef8f6b234b30d4034e1c5d2febad83e373af (diff)
operations: Reimplement UserUpdateSubmodule using git plumbing commands
Updating a submodule is simply updating the oid of the commit at the path. We can do this by manually rewriting trees and writing a new commit. If we impelement it this way, we don't need to rely on Git2Go.
-rw-r--r--internal/gitaly/service/operations/submodules.go126
-rw-r--r--internal/gitaly/service/operations/submodules_test.go40
2 files changed, 164 insertions, 2 deletions
diff --git a/internal/gitaly/service/operations/submodules.go b/internal/gitaly/service/operations/submodules.go
index 52cd4a724..74da62969 100644
--- a/internal/gitaly/service/operations/submodules.go
+++ b/internal/gitaly/service/operations/submodules.go
@@ -4,15 +4,18 @@ import (
"context"
"errors"
"fmt"
+ "path/filepath"
"regexp"
"strings"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
"gitlab.com/gitlab-org/gitaly/v15/internal/git"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/git/lstree"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/updateref"
"gitlab.com/gitlab-org/gitaly/v15/internal/git2go"
"gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/service"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag"
"gitlab.com/gitlab-org/gitaly/v15/internal/structerr"
"gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
)
@@ -58,6 +61,120 @@ func validateUserUpdateSubmoduleRequest(req *gitalypb.UserUpdateSubmoduleRequest
return nil
}
+func (s *Server) updateSubmodule(ctx context.Context, quarantineRepo *localrepo.Repo, req *gitalypb.UserUpdateSubmoduleRequest) (string, error) {
+ path := filepath.Dir(string(req.GetSubmodule()))
+ base := filepath.Base(string(req.GetSubmodule()))
+ replaceWith := git.ObjectID(req.GetCommitSha())
+
+ var submoduleFound bool
+
+ // First find the tree containing the submodule to be updated.
+ // Write a new tree object abc with the updated submodule. Then, write a new
+ // tree with the new tree abcabc. Continue iterating up the tree,
+ // writing a new tree object each time.
+ for {
+ entries, err := lstree.ListEntries(
+ ctx,
+ quarantineRepo,
+ git.Revision("refs/heads/"+string(req.GetBranch())),
+ &lstree.ListEntriesConfig{
+ RelativePath: path,
+ },
+ )
+ if err != nil {
+ if strings.Contains(err.Error(), "invalid object name") {
+ return "", fmt.Errorf("submodule: %s", git2go.LegacyErrPrefixInvalidSubmodulePath)
+ }
+
+ return "", fmt.Errorf("error reading tree: %w", err)
+ }
+
+ var newEntries []localrepo.TreeEntry
+ var newTreeID git.ObjectID
+
+ for _, entry := range entries {
+ // If the entry's path does not match, then we simply
+ // want to retain this tree entry.
+ if entry.Path != base {
+ newEntries = append(newEntries, *entry)
+ continue
+ }
+
+ // If we are at the submodule we want to replace, check
+ // if it's already at the value we want to replace, or
+ // if it's not a submodule.
+ if filepath.Join(path, entry.Path) == string(req.GetSubmodule()) {
+ if string(entry.OID) == req.GetCommitSha() {
+ return "",
+ //nolint:stylecheck
+ fmt.Errorf(
+ "The submodule %s is already at %s",
+ req.GetSubmodule(),
+ replaceWith,
+ )
+ }
+
+ if entry.Type != localrepo.Submodule {
+ return "", structerr.NewInvalidArgument("submodule %s is not a commit", req.GetSubmodule())
+ }
+ }
+
+ // Otherwise, create a new tree entry
+ submoduleFound = true
+
+ newEntries = append(newEntries, localrepo.TreeEntry{
+ Mode: entry.Mode,
+ Path: entry.Path,
+ OID: replaceWith,
+ })
+ }
+
+ newTreeID, err = quarantineRepo.WriteTree(ctx, newEntries)
+ if err != nil {
+ return "", fmt.Errorf("write tree: %w", err)
+ }
+ replaceWith = newTreeID
+
+ if path == "." {
+ break
+ }
+
+ base = filepath.Base(path)
+ path = filepath.Dir(path)
+ }
+
+ if !submoduleFound {
+ return "", fmt.Errorf("submodule: %s", git2go.LegacyErrPrefixInvalidSubmodulePath)
+ }
+
+ currentBranchCommit, err := quarantineRepo.ResolveRevision(ctx, git.Revision(req.GetBranch()))
+ if err != nil {
+ return "", fmt.Errorf("resolving submodule branch: %w", err)
+ }
+
+ authorDate, err := dateFromProto(req)
+ if err != nil {
+ return "", structerr.NewInvalidArgument("%w", err)
+ }
+
+ newCommitID, err := quarantineRepo.WriteCommit(ctx, localrepo.WriteCommitConfig{
+ Parents: []git.ObjectID{currentBranchCommit},
+ AuthorDate: authorDate,
+ AuthorName: string(req.GetUser().GetName()),
+ AuthorEmail: string(req.GetUser().GetEmail()),
+ CommitterName: string(req.GetUser().GetName()),
+ CommitterEmail: string(req.GetUser().GetEmail()),
+ CommitterDate: authorDate,
+ Message: string(req.GetCommitMessage()),
+ TreeID: replaceWith,
+ })
+ if err != nil {
+ return "", fmt.Errorf("creating commit %w", err)
+ }
+
+ return string(newCommitID), nil
+}
+
func (s *Server) updateSubmoduleWithGit2Go(ctx context.Context, quarantineRepo *localrepo.Repo, req *gitalypb.UserUpdateSubmoduleRequest) (string, error) {
repoPath, err := quarantineRepo.Path()
if err != nil {
@@ -131,7 +248,14 @@ func (s *Server) userUpdateSubmodule(ctx context.Context, req *gitalypb.UserUpda
}
}
- commitID, err := s.updateSubmoduleWithGit2Go(ctx, quarantineRepo, req)
+ var commitID string
+
+ if featureflag.SubmoduleInGit.IsEnabled(ctx) {
+ commitID, err = s.updateSubmodule(ctx, quarantineRepo, req)
+ } else {
+ commitID, err = s.updateSubmoduleWithGit2Go(ctx, quarantineRepo, req)
+ }
+
if err != nil {
errStr := strings.TrimPrefix(err.Error(), "submodule: ")
errStr = strings.TrimSpace(errStr)
diff --git a/internal/gitaly/service/operations/submodules_test.go b/internal/gitaly/service/operations/submodules_test.go
index 8047932fc..4f60ee7db 100644
--- a/internal/gitaly/service/operations/submodules_test.go
+++ b/internal/gitaly/service/operations/submodules_test.go
@@ -4,6 +4,7 @@ package operations
import (
"bytes"
+ "context"
"fmt"
"path/filepath"
"testing"
@@ -14,6 +15,7 @@ import (
"gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/lstree"
"gitlab.com/gitlab-org/gitaly/v15/internal/helper/text"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag"
"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"
@@ -22,7 +24,11 @@ import (
func TestUserUpdateSubmodule(t *testing.T) {
t.Parallel()
- ctx := testhelper.Context(t)
+ testhelper.NewFeatureSets(featureflag.SubmoduleInGit).
+ Run(t, testUserUpdateSubmodule)
+}
+
+func testUserUpdateSubmodule(t *testing.T, ctx context.Context) {
ctx, cfg, client := setupOperationsServiceWithoutRepo(t, ctx)
type setupData struct {
@@ -454,6 +460,38 @@ func TestUserUpdateSubmodule(t *testing.T) {
},
},
{
+ desc: "failure due to invalid submodule path",
+ subPath: "sub",
+ branch: "master",
+ setup: func(repoPath, subRepoPath string, repoProto, subRepoProto *gitalypb.Repository) setupData {
+ subCommitID := gittest.WriteCommit(t, cfg, subRepoPath)
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"), gittest.WithTreeEntries(
+ gittest.TreeEntry{
+ Mode: "100644",
+ Path: ".gitmodules",
+ Content: fmt.Sprintf(`[submodule "%s"]\n\tpath = %s\n\turl = file://%s`, "sub", "sub", subRepoPath),
+ },
+ gittest.TreeEntry{OID: subCommitID, Mode: "160000", Path: "sub"},
+ ))
+ commitID := gittest.WriteCommit(t, cfg, subRepoPath, gittest.WithParents(subCommitID))
+
+ return setupData{
+ request: &gitalypb.UserUpdateSubmoduleRequest{
+ User: gittest.TestUser,
+ CommitSha: string(commitID),
+ Branch: []byte("master"),
+ Repository: repoProto,
+ Submodule: []byte("foobar/does/not/exist"),
+ CommitMessage: []byte("Updating Submodule: sub"),
+ },
+ expectedResponse: &gitalypb.UserUpdateSubmoduleResponse{
+ CommitError: "Invalid submodule path",
+ },
+ verify: func(t *testing.T) {},
+ }
+ },
+ },
+ {
desc: "failure due to same submodule reference",
subPath: "sub",
branch: "master",