diff options
Diffstat (limited to 'internal/gitaly/service/operations/submodules.go')
-rw-r--r-- | internal/gitaly/service/operations/submodules.go | 116 |
1 files changed, 115 insertions, 1 deletions
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 +} |