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:
authorPaul Okstad <pokstad@gitlab.com>2020-10-16 09:57:59 +0300
committerPaul Okstad <pokstad@gitlab.com>2020-10-17 00:35:08 +0300
commit73a4037cb51c1f2febebdd2b7bbf3ae0f59db43a (patch)
tree1c2cc24749eac71918e3d59ef91eb27a95d9c73f
parentac6f26f20a651168007f8621111e1b75c3c18243 (diff)
Subcommand for resolve conflicts
-rw-r--r--cmd/gitaly-git2go/resolve_conflicts.go194
-rw-r--r--internal/git2go/resolve_conflicts.go52
-rw-r--r--internal/gitaly/service/conflicts/resolve_conflicts.go74
3 files changed, 320 insertions, 0 deletions
diff --git a/cmd/gitaly-git2go/resolve_conflicts.go b/cmd/gitaly-git2go/resolve_conflicts.go
new file mode 100644
index 000000000..3c8bf6eed
--- /dev/null
+++ b/cmd/gitaly-git2go/resolve_conflicts.go
@@ -0,0 +1,194 @@
+// +build static,system_libgit2
+
+package main
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "strings"
+ "time"
+
+ git "github.com/libgit2/git2go/v30"
+ "gitlab.com/gitlab-org/gitaly/internal/git/conflict"
+ "gitlab.com/gitlab-org/gitaly/internal/git2go"
+)
+
+type resolveSubcommand struct {
+ request string
+}
+
+func (cmd *resolveSubcommand) Flags() *flag.FlagSet {
+ flags := flag.NewFlagSet("resolve", flag.ExitOnError)
+ flags.StringVar(&cmd.request, "request", "", "git2go.MergeCommand")
+ return flags
+}
+
+func (cmd resolveSubcommand) Run() error {
+ request, err := git2go.ResolveCommandFromSerialized(cmd.request)
+ if err != nil {
+ return err
+ }
+
+ if request.AuthorDate.IsZero() {
+ request.AuthorDate = time.Now()
+ }
+
+ repo, err := git.OpenRepository(request.Repository)
+ if err != nil {
+ return fmt.Errorf("could not open repository: %w", err)
+ }
+
+ ours, err := lookupCommit(repo, request.Ours)
+ if err != nil {
+ return fmt.Errorf("could not lookup commit %q: %w", request.Ours, err)
+ }
+
+ theirs, err := lookupCommit(repo, request.Theirs)
+ if err != nil {
+ return fmt.Errorf("could not lookup commit %q: %w", request.Theirs, err)
+ }
+
+ index, err := repo.MergeCommits(ours, theirs, nil)
+ if err != nil {
+ return fmt.Errorf("could not merge commits: %w", err)
+ }
+
+ ci, err := index.ConflictIterator()
+ if err != nil {
+ return err
+ }
+
+ type paths struct {
+ theirs, ours string
+ }
+ conflicts := map[paths]git.IndexConflict{}
+
+ for c, err := ci.Next(); err != nil; c, err = ci.Next() {
+ if c.Our.Path == "" || c.Their.Path == "" {
+ return errors.New("conflict side missing")
+ }
+
+ k := paths{
+ theirs: c.Their.Path,
+ ours: c.Our.Path,
+ }
+ conflicts[k] = c
+ }
+
+ for _, r := range request.Resolutions {
+ c, ok := conflicts[paths{
+ theirs: r.OldPath,
+ ours: r.NewPath,
+ }]
+ if !ok {
+ continue
+ }
+
+ odb, err := repo.Odb()
+ if err != nil {
+ return err
+ }
+
+ mfr, err := mergeFileResult(odb, c)
+ if err != nil {
+ return err
+ }
+
+ f, err := conflict.Parse(
+ bytes.NewReader(mfr.Contents),
+ c.Our.Path,
+ c.Their.Path,
+ c.Ancestor.Path,
+ )
+ if err != nil {
+ return err
+ }
+
+ resolvedBlob, err := f.Resolve(r)
+ if err != nil {
+ return err
+ }
+
+ resolvedBlobOID, err := odb.Write(resolvedBlob, git.ObjectBlob)
+ if err != nil {
+ return err
+ }
+
+ ourResolvedEntry := *c.Our // copy by value
+ ourResolvedEntry.Id = resolvedBlobOID
+ if err := index.Add(&ourResolvedEntry); err != nil {
+ return err
+ }
+
+ // TODO: verify we need to remove conflict
+ if err := index.RemoveConflict(ourResolvedEntry.Path); err != nil {
+ return err
+ }
+ }
+
+ if index.HasConflicts() {
+ ci, err := index.ConflictIterator()
+ if err != nil {
+ return err
+ }
+
+ var conflictPaths []string
+ for {
+ c, err := ci.Next()
+ if err != nil {
+ return err
+ }
+ conflictPaths = append(conflictPaths, c.Ancestor.Path)
+ }
+
+ return fmt.Errorf(
+ "Missing resolutions for the following files: %s",
+ strings.Join(conflictPaths, ", "),
+ )
+ }
+
+ return nil
+}
+
+func mergeFileResult(odb *git.Odb, c git.IndexConflict) (*git.MergeFileResult, error) {
+ ancestorBlob, err := odb.Read(c.Ancestor.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ ourBlob, err := odb.Read(c.Our.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ theirBlob, err := odb.Read(c.Their.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ mfr, err := git.MergeFile(
+ git.MergeFileInput{
+ Path: c.Ancestor.Path,
+ Mode: uint(c.Ancestor.Mode),
+ Contents: ancestorBlob.Data(),
+ },
+ git.MergeFileInput{
+ Path: c.Our.Path,
+ Mode: uint(c.Our.Mode),
+ Contents: ourBlob.Data(),
+ },
+ git.MergeFileInput{
+ Path: c.Their.Path,
+ Mode: uint(c.Their.Mode),
+ Contents: theirBlob.Data(),
+ },
+ nil,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return mfr, nil
+}
diff --git a/internal/git2go/resolve_conflicts.go b/internal/git2go/resolve_conflicts.go
new file mode 100644
index 000000000..5eb2535b2
--- /dev/null
+++ b/internal/git2go/resolve_conflicts.go
@@ -0,0 +1,52 @@
+package git2go
+
+import (
+ "context"
+ "fmt"
+
+ "gitlab.com/gitlab-org/gitaly/internal/git/conflict"
+ "gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
+)
+
+type ResolveCommand struct {
+ MergeCommand `json:"merge_command"`
+ Resolutions []conflict.Resolution `json:"conflict_files"`
+}
+
+type ResolveResult struct{}
+
+func ResolveCommandFromSerialized(serialized string) (ResolveCommand, error) {
+ var request ResolveCommand
+ if err := deserialize(serialized, &request); err != nil {
+ return ResolveCommand{}, err
+ }
+
+ if err := request.verify(); err != nil {
+ return ResolveCommand{}, fmt.Errorf("resolve: %w: %s", ErrInvalidArgument, err.Error())
+ }
+
+ return request, nil
+}
+
+func (r ResolveCommand) Run(ctx context.Context, cfg config.Cfg) (ResolveResult, error) {
+ if err := r.verify(); err != nil {
+ return ResolveResult{}, fmt.Errorf("resolve: %w: %s", ErrInvalidArgument, err.Error())
+ }
+
+ serialized, err := serialize(r)
+ if err != nil {
+ return ResolveResult{}, err
+ }
+
+ stdout, err := run(ctx, cfg, "resolve", serialized)
+ if err != nil {
+ return ResolveResult{}, err
+ }
+
+ var response ResolveResult
+ if err := deserialize(stdout, &response); err != nil {
+ return ResolveResult{}, err
+ }
+
+ return response, nil
+}
diff --git a/internal/gitaly/service/conflicts/resolve_conflicts.go b/internal/gitaly/service/conflicts/resolve_conflicts.go
index d8734ef8d..9b283f475 100644
--- a/internal/gitaly/service/conflicts/resolve_conflicts.go
+++ b/internal/gitaly/service/conflicts/resolve_conflicts.go
@@ -1,9 +1,17 @@
package conflicts
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
+ "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/gitaly/rubyserver"
+ "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"
@@ -24,6 +32,10 @@ func (s *server) ResolveConflicts(stream gitalypb.ConflictsService_ResolveConfli
return status.Errorf(codes.InvalidArgument, "ResolveConflicts: %v", err)
}
+ if featureflag.IsEnabled(stream.Context(), featureflag.GoResolveConflicts) {
+ return s.resolveConflicts(header, stream)
+ }
+
ctx := stream.Context()
client, err := s.ruby.ConflictsServiceClient(ctx)
if err != nil {
@@ -89,3 +101,65 @@ 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 {
+ req, err := stream.Recv()
+ switch err {
+ case io.EOF:
+ return nil
+ case nil:
+ // do nothing, continue
+ default:
+ return err
+ }
+
+ if _, err := b.Write(req.GetFilesJson()); err != nil {
+ return err
+ }
+ }
+
+ var conflicts []map[string]conflictFile
+ if err := json.NewDecoder(b).Decode(&conflicts); err != nil {
+ return err
+ }
+
+ srcRepo := git.NewRepository(header.GetRepository())
+
+ 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
+ }
+ }
+
+ targetOID, err := dstRepo.ResolveRefish(ctx, string(header.TargetBranch))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}