1package git2go
2
3import (
4	"context"
5	"encoding/gob"
6	"fmt"
7	"io"
8
9	"gitlab.com/gitlab-org/gitaly/v14/internal/git"
10	"gitlab.com/gitlab-org/gitaly/v14/internal/git/repository"
11)
12
13// ErrMergeConflict is returned when there is a merge conflict.
14var ErrMergeConflict = wrapError{Message: "merge conflict"}
15
16// Patch represents a single patch.
17type Patch struct {
18	// Author is the author of the patch.
19	Author Signature
20	// Message is used as the commit message when applying the patch.
21	Message string
22	// Diff contains the diff of the patch.
23	Diff []byte
24}
25
26// ApplyParams are the parameters for Apply.
27type ApplyParams struct {
28	// Repository is the path to the repository.
29	Repository string
30	// Committer is the committer applying the patch.
31	Committer Signature
32	// ParentCommit is the OID of the commit to apply the patches against.
33	ParentCommit string
34	// Patches iterates over all the patches to be applied.
35	Patches PatchIterator
36}
37
38// PatchIterator iterates over a stream of patches.
39type PatchIterator interface {
40	// Next returns whether there is a next patch.
41	Next() bool
42	// Value returns the patch being currently iterated upon.
43	Value() Patch
44	// Err returns the iteration error. Err should
45	// be always checked after Next returns false.
46	Err() error
47}
48
49type slicePatchIterator struct {
50	value   Patch
51	patches []Patch
52}
53
54// NewSlicePatchIterator returns a PatchIterator that iterates over the slice
55// of patches.
56func NewSlicePatchIterator(patches []Patch) PatchIterator {
57	return &slicePatchIterator{patches: patches}
58}
59
60func (iter *slicePatchIterator) Next() bool {
61	if len(iter.patches) == 0 {
62		return false
63	}
64
65	iter.value = iter.patches[0]
66	iter.patches = iter.patches[1:]
67	return true
68}
69
70func (iter *slicePatchIterator) Value() Patch { return iter.value }
71
72func (iter *slicePatchIterator) Err() error { return nil }
73
74// Apply applies the provided patches and returns the OID of the commit with the patches
75// applied.
76func (b Executor) Apply(ctx context.Context, repo repository.GitRepo, params ApplyParams) (git.ObjectID, error) {
77	reader, writer := io.Pipe()
78	defer writer.Close()
79
80	go func() {
81		// CloseWithError is documented to always return nil.
82		_ = writer.CloseWithError(func() error {
83			patches := params.Patches
84			params.Patches = nil
85
86			encoder := gob.NewEncoder(writer)
87			if err := encoder.Encode(params); err != nil {
88				return fmt.Errorf("encode header: %w", err)
89			}
90
91			for patches.Next() {
92				if err := encoder.Encode(patches.Value()); err != nil {
93					return fmt.Errorf("encode patch: %w", err)
94				}
95			}
96
97			if err := patches.Err(); err != nil {
98				return fmt.Errorf("patch iterator: %w", err)
99			}
100
101			return nil
102		}())
103	}()
104
105	var result Result
106	output, err := b.run(ctx, repo, reader, "apply", "-git-binary-path", b.gitBinaryPath)
107	if err != nil {
108		return "", fmt.Errorf("run: %w", err)
109	}
110
111	if err := gob.NewDecoder(output).Decode(&result); err != nil {
112		return "", fmt.Errorf("decode: %w", err)
113	}
114
115	if result.Error != nil {
116		return "", result.Error
117	}
118
119	commitID, err := git.NewObjectIDFromHex(result.CommitID)
120	if err != nil {
121		return "", fmt.Errorf("could not parse commit ID: %w", err)
122	}
123
124	return commitID, nil
125}
126