From a3b2b64e6de53d5cdb9a4f51c6bff2c349479f53 Mon Sep 17 00:00:00 2001 From: Paul Okstad Date: Fri, 16 Oct 2020 14:18:57 -0700 Subject: resolve --- cmd/gitaly-git2go/resolve_conflicts.go | 27 ++++ go.mod | 1 + go.sum | 1 + internal/git2go/resolve_conflicts.go | 4 +- .../gitaly/service/conflicts/resolve_conflicts.go | 157 ++++++++++++++++----- 5 files changed, 157 insertions(+), 33 deletions(-) diff --git a/cmd/gitaly-git2go/resolve_conflicts.go b/cmd/gitaly-git2go/resolve_conflicts.go index 3c8bf6eed..ba1d516cb 100644 --- a/cmd/gitaly-git2go/resolve_conflicts.go +++ b/cmd/gitaly-git2go/resolve_conflicts.go @@ -7,6 +7,7 @@ import ( "errors" "flag" "fmt" + "os" "strings" "time" @@ -149,6 +150,32 @@ func (cmd resolveSubcommand) Run() error { ) } + tree, err := index.WriteTreeTo(repo) + if err != nil { + return err + } + + committer := git.Signature{ + Name: sanitizeSignatureInfo(request.AuthorName), + Email: sanitizeSignatureInfo(request.AuthorMail), + When: request.AuthorDate, + } + + commit, err := repo.CreateCommitFromIds("", &committer, &committer, request.Message, tree, ours.Id(), theirs.Id()) + if err != nil { + return fmt.Errorf("could not create resolve conflict commit: %w", err) + } + + response := git2go.ResolveResult{ + git2go.MergeResult{ + CommitID: commit.String(), + }, + } + + if err := response.SerializeTo(os.Stdout); err != nil { + return err + } + return nil } diff --git a/go.mod b/go.mod index a862936d6..a0bda0dfb 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( golang.org/x/sys v0.0.0-20200918174421-af09f7315aff golang.org/x/text v0.3.3 // indirect google.golang.org/grpc v1.24.0 + gopkg.in/errgo.v2 v2.1.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index d1071f462..25b882e1b 100644 --- a/go.sum +++ b/go.sum @@ -605,6 +605,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/internal/git2go/resolve_conflicts.go b/internal/git2go/resolve_conflicts.go index 5eb2535b2..bc1057526 100644 --- a/internal/git2go/resolve_conflicts.go +++ b/internal/git2go/resolve_conflicts.go @@ -13,7 +13,9 @@ type ResolveCommand struct { Resolutions []conflict.Resolution `json:"conflict_files"` } -type ResolveResult struct{} +type ResolveResult struct { + MergeResult `json:"merge_result"` +} func ResolveCommandFromSerialized(serialized string) (ResolveCommand, error) { var request ResolveCommand diff --git a/internal/gitaly/service/conflicts/resolve_conflicts.go b/internal/gitaly/service/conflicts/resolve_conflicts.go index 9b283f475..5f6aa3eee 100644 --- a/internal/gitaly/service/conflicts/resolve_conflicts.go +++ b/internal/gitaly/service/conflicts/resolve_conflicts.go @@ -2,19 +2,24 @@ package conflicts import ( "bytes" + "context" "encoding/json" "fmt" "io" + "sort" "gitlab.com/gitlab-org/gitaly/client" "gitlab.com/gitlab-org/gitaly/internal/git" - "gitlab.com/gitlab-org/gitaly/internal/git/repository" - "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/internal/git/conflict" + "gitlab.com/gitlab-org/gitaly/internal/git2go" "gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver" + "gitlab.com/gitlab-org/gitaly/internal/gitalyssh" + "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" + "gopkg.in/errgo.v2/errors" ) func (s *server) ResolveConflicts(stream gitalypb.ConflictsService_ResolveConflictsServer) error { @@ -102,21 +107,6 @@ func validateResolveConflictsHeader(header *gitalypb.ResolveConflictsRequestHead return nil } -type conflictFile struct { - OldPath string `json:"old_path"` - NewPath string `json:"new_path"` - Sections map[string]string `json:"sections"` -} - -func cfgContainsRepo(cfg config.Cfg, r repository.GitRepo) bool { - for _, s := range cfg.Storages { - if r.GetStorageName() == s.Name { - return true - } - } - return false -} - func (s *server) resolveConflicts(header *gitalypb.ResolveConflictsRequestHeader, stream gitalypb.ConflictsService_ResolveConflictsServer) error { b := bytes.NewBuffer(nil) for { @@ -135,31 +125,134 @@ func (s *server) resolveConflicts(header *gitalypb.ResolveConflictsRequestHeader } } - var conflicts []map[string]conflictFile - if err := json.NewDecoder(b).Decode(&conflicts); err != nil { + var resolutions []conflict.Resolution + if err := json.NewDecoder(b).Decode(&resolutions); err != nil { + return err + } + + if err := s.repoWithBranchCommit( + stream.Context(), + header.GetRepository(), + header.GetTargetRepository(), + header.SourceBranch, + header.TargetBranch, + ); err != nil { return err } - srcRepo := git.NewRepository(header.GetRepository()) + repoPath, err := s.locator.GetRepoPath(header.GetRepository()) + if err != nil { + return err + } - var dstRepo git.Repository - switch dst := header.GetTargetRepository(); { - case dst == nil: - dstRepo = srcRepo - case cfgContainsRepo(s.cfg, dst): - dstRepo = git.NewRepository(dst) - default: - var err error - dstRepo, err = git.NewRemoteRepository(stream.Context(), dst, client.NewPool()) - if err != nil { - return err + _, err = git2go.ResolveCommand{ + MergeCommand: git2go.MergeCommand{ + Repository: repoPath, + AuthorName: string(header.User.Name), + AuthorMail: string(header.User.Email), + Message: string(header.CommitMessage), + Ours: header.GetOurCommitOid(), + Theirs: header.GetTheirCommitOid(), + }, + Resolutions: resolutions, + }.Run(stream.Context(), s.cfg) + if err != nil { + if errors.Is(git2go.ErrInvalidArgument)(err) { + return helper.ErrInvalidArgument(err) + } + return err + } + + return stream.SendAndClose(&gitalypb.ResolveConflictsResponse{ + ResolutionError: err.Error(), + }) +} + +func sameRepo(left, right *gitalypb.Repository) bool { + lgaod := left.GetGitAlternateObjectDirectories() + rgaod := right.GetGitAlternateObjectDirectories() + if len(lgaod) != len(rgaod) { + return false + } + sort.Strings(lgaod) + sort.Strings(rgaod) + for i := 0; i < len(lgaod); i++ { + if lgaod[i] != rgaod[i] { + return false } } + if left.GetGitObjectDirectory() != right.GetGitObjectDirectory() { + return false + } + if left.GetRelativePath() != right.GetRelativePath() { + return false + } + if left.GetStorageName() != right.GetStorageName() { + return false + } + return true +} + +const gitalyInternalURL = "ssh://gitaly/internal.git" + +// repoWithCommit ensures that the source repo contains the same commit we +// hope to merge with from the target branch, else it will be fetched from the +// target repo. This is necessary since all merge/resolve logic occurs on the +// same filesystem +func (s *server) repoWithBranchCommit(ctx context.Context, srcRepo, targetRepo *gitalypb.Repository, srcBranch, targetBranch []byte) error { + src := git.NewRepository(srcRepo) + if sameRepo(srcRepo, targetRepo) { + _, err := src.ResolveRefish(ctx, string(targetBranch)) + return err + } + + target, err := git.NewRemoteRepository(ctx, targetRepo, client.NewPool()) + if err != nil { + return err + } + + oid, err := target.ResolveRefish(ctx, string(targetBranch)) + if err != nil { + return err + } + + ok, err := src.ContainsRef(ctx, oid) + if err != nil { + return err + } + if ok { + // target branch commit already exists in source repo; nothing + // to do + return err + } + + env, err := gitalyssh.UploadPackEnv(ctx, &gitalypb.SSHUploadPackRequest{Repository: targetRepo}) + if err != nil { + return err + } + // to enable fetching a specific SHA: + env = append(env, "uploadpack.allowAnySHA1InWant=true") - targetOID, err := dstRepo.ResolveRefish(ctx, string(header.TargetBranch)) + srcRepoPath, err := s.locator.GetRepoPath(srcRepo) if err != nil { return err } + cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, env, + []git.Option{git.ValueFlag{"--git-dir", srcRepoPath}}, + git.SubCmd{ + Name: "fetch", + Flags: []git.Option{git.Flag{Name: "--no-tags"}}, + Args: []string{gitalyInternalURL, oid}, + }, + ) + if err != nil { + return err + } + + if err := cmd.Wait(); err != nil { + return err + } + return nil } -- cgit v1.2.3