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:
Diffstat (limited to 'cmd/gitaly-git2go/conflicts.go')
-rw-r--r--cmd/gitaly-git2go/conflicts.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/cmd/gitaly-git2go/conflicts.go b/cmd/gitaly-git2go/conflicts.go
new file mode 100644
index 000000000..1e2fbb4e0
--- /dev/null
+++ b/cmd/gitaly-git2go/conflicts.go
@@ -0,0 +1,171 @@
+//go:build static && system_libgit2
+
+package main
+
+import (
+ "context"
+ "encoding/gob"
+ "errors"
+ "flag"
+ "fmt"
+
+ git "github.com/libgit2/git2go/v33"
+ "gitlab.com/gitlab-org/gitaly/v15/cmd/gitaly-git2go/git2goutil"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/git2go"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/helper"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+type conflictsSubcommand struct{}
+
+func (cmd *conflictsSubcommand) Flags() *flag.FlagSet {
+ return flag.NewFlagSet("conflicts", flag.ExitOnError)
+}
+
+func (cmd *conflictsSubcommand) Run(_ context.Context, decoder *gob.Decoder, encoder *gob.Encoder) error {
+ var request git2go.ConflictsCommand
+ if err := decoder.Decode(&request); err != nil {
+ return err
+ }
+ res := cmd.conflicts(request)
+ return encoder.Encode(res)
+}
+
+func (conflictsSubcommand) conflicts(request git2go.ConflictsCommand) git2go.ConflictsResult {
+ repo, err := git2goutil.OpenRepository(request.Repository)
+ if err != nil {
+ return conflictError(codes.Internal, fmt.Errorf("could not open repository: %w", err).Error())
+ }
+
+ oursOid, err := git.NewOid(request.Ours)
+ if err != nil {
+ return conflictError(codes.InvalidArgument, err.Error())
+ }
+
+ ours, err := repo.LookupCommit(oursOid)
+ if err != nil {
+ return convertError(err, git.ErrorCodeNotFound, codes.InvalidArgument)
+ }
+
+ theirsOid, err := git.NewOid(request.Theirs)
+ if err != nil {
+ return conflictError(codes.InvalidArgument, err.Error())
+ }
+
+ theirs, err := repo.LookupCommit(theirsOid)
+ if err != nil {
+ return convertError(err, git.ErrorCodeNotFound, codes.InvalidArgument)
+ }
+
+ index, err := repo.MergeCommits(ours, theirs, nil)
+ if err != nil {
+ return conflictError(codes.FailedPrecondition, fmt.Sprintf("could not merge commits: %v", err))
+ }
+
+ iterator, err := index.ConflictIterator()
+ if err != nil {
+ return conflictError(codes.Internal, fmt.Errorf("could not get conflicts: %w", err).Error())
+ }
+
+ var result git2go.ConflictsResult
+ for {
+ conflict, err := iterator.Next()
+ if err != nil {
+ var gitError *git.GitError
+ if errors.As(err, &gitError) && gitError.Code == git.ErrorCodeIterOver {
+ break
+ }
+ return conflictError(codes.Internal, err.Error())
+ }
+
+ merge, err := Merge(repo, conflict)
+ if err != nil {
+ if s, ok := status.FromError(err); ok {
+ return conflictError(s.Code(), s.Message())
+ }
+ return conflictError(codes.Internal, err.Error())
+ }
+
+ result.Conflicts = append(result.Conflicts, git2go.Conflict{
+ Ancestor: conflictEntryFromIndex(conflict.Ancestor),
+ Our: conflictEntryFromIndex(conflict.Our),
+ Their: conflictEntryFromIndex(conflict.Their),
+ Content: merge.Contents,
+ })
+ }
+
+ return result
+}
+
+// Merge will merge the given index conflict and produce a file with conflict
+// markers.
+func Merge(repo *git.Repository, conflict git.IndexConflict) (*git.MergeFileResult, error) {
+ var ancestor, our, their git.MergeFileInput
+
+ for entry, input := range map[*git.IndexEntry]*git.MergeFileInput{
+ conflict.Ancestor: &ancestor,
+ conflict.Our: &our,
+ conflict.Their: &their,
+ } {
+ if entry == nil {
+ continue
+ }
+
+ blob, err := repo.LookupBlob(entry.Id)
+ if err != nil {
+ return nil, helper.ErrFailedPreconditionf("could not get conflicting blob: %w", err)
+ }
+
+ input.Path = entry.Path
+ input.Mode = uint(entry.Mode)
+ input.Contents = blob.Contents()
+ }
+
+ merge, err := git.MergeFile(ancestor, our, their, nil)
+ if err != nil {
+ return nil, fmt.Errorf("could not compute conflicts: %w", err)
+ }
+
+ // In a case of tree-based conflicts (e.g. no ancestor), fallback to `Path`
+ // of `their` side. If that's also blank, fallback to `Path` of `our` side.
+ // This is to ensure that there's always a `Path` when we try to merge
+ // conflicts.
+ if merge.Path == "" {
+ if their.Path != "" {
+ merge.Path = their.Path
+ } else {
+ merge.Path = our.Path
+ }
+ }
+
+ return merge, nil
+}
+
+func conflictEntryFromIndex(entry *git.IndexEntry) git2go.ConflictEntry {
+ if entry == nil {
+ return git2go.ConflictEntry{}
+ }
+ return git2go.ConflictEntry{
+ Path: entry.Path,
+ Mode: int32(entry.Mode),
+ }
+}
+
+func conflictError(code codes.Code, message string) git2go.ConflictsResult {
+ err := git2go.ConflictError{
+ Code: code,
+ Message: message,
+ }
+ return git2go.ConflictsResult{
+ Err: err,
+ }
+}
+
+func convertError(err error, errorCode git.ErrorCode, returnCode codes.Code) git2go.ConflictsResult {
+ var gitError *git.GitError
+ if errors.As(err, &gitError) && gitError.Code == errorCode {
+ return conflictError(returnCode, err.Error())
+ }
+ return conflictError(codes.Internal, err.Error())
+}