diff options
-rw-r--r-- | cmd/gitaly-git2go/merge.go | 74 | ||||
-rw-r--r-- | internal/git2go/merge.go | 3 |
2 files changed, 76 insertions, 1 deletions
diff --git a/cmd/gitaly-git2go/merge.go b/cmd/gitaly-git2go/merge.go index 907e717c0..dc9efd808 100644 --- a/cmd/gitaly-git2go/merge.go +++ b/cmd/gitaly-git2go/merge.go @@ -12,6 +12,7 @@ import ( "time" git "github.com/libgit2/git2go/v30" + "gitlab.com/gitlab-org/gitaly/cmd/gitaly-git2go/conflicts" "gitlab.com/gitlab-org/gitaly/internal/git2go" ) @@ -64,7 +65,13 @@ func (cmd *mergeSubcommand) Run(context.Context, io.Reader, io.Writer) error { defer index.Free() if index.HasConflicts() { - return errors.New("could not auto-merge due to conflicts") + if !request.AllowConflicts { + return errors.New("could not auto-merge due to conflicts") + } + + if err := resolveConflicts(repo, index); err != nil { + return fmt.Errorf("could not resolve conflicts: %w", err) + } } tree, err := index.WriteTreeTo(repo) @@ -88,3 +95,68 @@ func (cmd *mergeSubcommand) Run(context.Context, io.Reader, io.Writer) error { return nil } + +func resolveConflicts(repo *git.Repository, index *git.Index) error { + // We need to get all conflicts up front as resolving conflicts as we + // iterate breaks the iterator. + indexConflicts, err := getConflicts(index) + if err != nil { + return err + } + + for _, conflict := range indexConflicts { + merge, err := conflicts.Merge(repo, conflict) + if err != nil { + return err + } + + mergedBlob, err := repo.CreateBlobFromBuffer(merge.Contents) + if err != nil { + return err + } + + mergedIndexEntry := git.IndexEntry{ + Path: merge.Path, + Mode: git.Filemode(merge.Mode), + Id: mergedBlob, + } + + if err := index.Add(&mergedIndexEntry); err != nil { + return err + } + + if err := index.RemoveConflict(merge.Path); err != nil { + return err + } + } + + if index.HasConflicts() { + return errors.New("index still has conflicts") + } + + return nil +} + +func getConflicts(index *git.Index) ([]git.IndexConflict, error) { + var conflicts []git.IndexConflict + + iterator, err := index.ConflictIterator() + if err != nil { + return nil, err + } + defer iterator.Free() + + for { + conflict, err := iterator.Next() + if err != nil { + if git.IsErrorCode(err, git.ErrIterOver) { + break + } + return nil, err + } + + conflicts = append(conflicts, conflict) + } + + return conflicts, nil +} diff --git a/internal/git2go/merge.go b/internal/git2go/merge.go index 7659930a1..e92856810 100644 --- a/internal/git2go/merge.go +++ b/internal/git2go/merge.go @@ -37,6 +37,9 @@ type MergeCommand struct { Ours string `json:"ours"` // Theirs is the commit into which ours is to be merged. Theirs string `json:"theirs"` + // AllowConflicts controls whether conflicts are allowed. If they are, + // then conflicts will be committed as part of the result. + AllowConflicts bool `json:"allow_conflicts"` } // MergeResult contains results from a merge. |