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