1package git2go
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"io"
8
9	"gitlab.com/gitlab-org/gitaly/v14/internal/git/repository"
10	"google.golang.org/grpc/codes"
11	"google.golang.org/grpc/status"
12)
13
14// ConflictsCommand contains parameters to perform a merge and return its conflicts.
15type ConflictsCommand struct {
16	// Repository is the path to execute merge in.
17	Repository string `json:"repository"`
18	// Ours is the commit that is to be merged into theirs.
19	Ours string `json:"ours"`
20	// Theirs is the commit into which ours is to be merged.
21	Theirs string `json:"theirs"`
22}
23
24// ConflictEntry represents a conflict entry which is one of the sides of a conflict.
25type ConflictEntry struct {
26	// Path is the path of the conflicting file.
27	Path string `json:"path"`
28	// Mode is the mode of the conflicting file.
29	Mode int32 `json:"mode"`
30}
31
32// Conflict represents a merge conflict for a single file.
33type Conflict struct {
34	// Ancestor is the conflict entry of the merge-base.
35	Ancestor ConflictEntry `json:"ancestor"`
36	// Our is the conflict entry of ours.
37	Our ConflictEntry `json:"our"`
38	// Their is the conflict entry of theirs.
39	Their ConflictEntry `json:"their"`
40	// Content contains the conflicting merge results.
41	Content []byte `json:"content"`
42}
43
44// ConflictError is an error which happened during conflict resolution.
45type ConflictError struct {
46	// Code is the GRPC error code
47	Code codes.Code
48	// Message is the error message
49	Message string
50}
51
52// ConflictsResult contains all conflicts resulting from a merge.
53type ConflictsResult struct {
54	// Conflicts
55	Conflicts []Conflict `json:"conflicts"`
56	// Error is an optional conflict error
57	Error ConflictError `json:"error"`
58}
59
60// ConflictsCommandFromSerialized constructs a ConflictsCommand from its serialized representation.
61func ConflictsCommandFromSerialized(serialized string) (ConflictsCommand, error) {
62	var request ConflictsCommand
63	if err := deserialize(serialized, &request); err != nil {
64		return ConflictsCommand{}, err
65	}
66
67	if err := request.verify(); err != nil {
68		return ConflictsCommand{}, fmt.Errorf("conflicts: %w: %s", ErrInvalidArgument, err.Error())
69	}
70
71	return request, nil
72}
73
74// SerializeTo serializes the conflicts result and writes it into the writer.
75func (m ConflictsResult) SerializeTo(writer io.Writer) error {
76	return serializeTo(writer, m)
77}
78
79// Conflicts performs a merge via gitaly-git2go and returns all resulting conflicts.
80func (b Executor) Conflicts(ctx context.Context, repo repository.GitRepo, c ConflictsCommand) (ConflictsResult, error) {
81	if err := c.verify(); err != nil {
82		return ConflictsResult{}, fmt.Errorf("conflicts: %w: %s", ErrInvalidArgument, err.Error())
83	}
84
85	serialized, err := serialize(c)
86	if err != nil {
87		return ConflictsResult{}, err
88	}
89
90	stdout, err := b.run(ctx, repo, nil, "conflicts", "-request", serialized)
91	if err != nil {
92		return ConflictsResult{}, err
93	}
94
95	var response ConflictsResult
96	if err := deserialize(stdout.String(), &response); err != nil {
97		return ConflictsResult{}, err
98	}
99
100	if response.Error.Code != codes.OK {
101		return ConflictsResult{}, status.Error(response.Error.Code, response.Error.Message)
102	}
103
104	return response, nil
105}
106
107func (c ConflictsCommand) verify() error {
108	if c.Repository == "" {
109		return errors.New("missing repository")
110	}
111	if c.Ours == "" {
112		return errors.New("missing ours")
113	}
114	if c.Theirs == "" {
115		return errors.New("missing theirs")
116	}
117	return nil
118}
119