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