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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
package lfs
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"hash"
"io"
"os"
"strings"
"github.com/git-lfs/git-lfs/v3/config"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/subprocess"
"github.com/git-lfs/git-lfs/v3/tr"
)
type pipeRequest struct {
action string
reader io.Reader
fileName string
extensions []config.Extension
}
type pipeResponse struct {
file *os.File
results []*pipeExtResult
}
type pipeExtResult struct {
name string
oidIn string
oidOut string
}
type extCommand struct {
cmd *subprocess.Cmd
out io.WriteCloser
err *bytes.Buffer
hasher hash.Hash
result *pipeExtResult
}
func pipeExtensions(cfg *config.Configuration, request *pipeRequest) (response pipeResponse, err error) {
var extcmds []*extCommand
defer func() {
// In the case of an early return before the end of this
// function (in response to an error, etc), kill all running
// processes. Errors are ignored since the function has already
// returned.
//
// In the happy path, the commands will have already been
// `Wait()`-ed upon and e.cmd.Process.Kill() will return an
// error, but we can ignore it.
for _, e := range extcmds {
if e.cmd.Process != nil {
e.cmd.Process.Kill()
}
}
}()
for _, e := range request.extensions {
var pieces []string
switch request.action {
case "clean":
pieces = strings.Split(e.Clean, " ")
case "smudge":
pieces = strings.Split(e.Smudge, " ")
default:
err = errors.New(tr.Tr.Get("Invalid action: %s", request.action))
return
}
name := strings.Trim(pieces[0], " ")
var args []string
for _, value := range pieces[1:] {
arg := strings.Replace(value, "%f", request.fileName, -1)
args = append(args, arg)
}
var cmd *subprocess.Cmd
cmd, err = subprocess.ExecCommand(name, args...)
if err != nil {
return
}
ec := &extCommand{cmd: cmd, result: &pipeExtResult{name: e.Name}}
extcmds = append(extcmds, ec)
}
hasher := sha256.New()
pipeReader, pipeWriter := io.Pipe()
multiWriter := io.MultiWriter(hasher, pipeWriter)
var input io.Reader
var output io.WriteCloser
input = pipeReader
extcmds[0].cmd.Stdin = input
if response.file, err = TempFile(cfg, ""); err != nil {
return
}
defer response.file.Close()
output = response.file
last := len(extcmds) - 1
for i, ec := range extcmds {
ec.hasher = sha256.New()
if i == last {
ec.cmd.Stdout = io.MultiWriter(ec.hasher, output)
ec.out = output
continue
}
nextec := extcmds[i+1]
var nextStdin io.WriteCloser
var stdout io.ReadCloser
if nextStdin, err = nextec.cmd.StdinPipe(); err != nil {
return
}
if stdout, err = ec.cmd.StdoutPipe(); err != nil {
return
}
ec.cmd.Stdin = input
ec.cmd.Stdout = io.MultiWriter(ec.hasher, nextStdin)
ec.out = nextStdin
input = stdout
var errBuff bytes.Buffer
ec.err = &errBuff
ec.cmd.Stderr = ec.err
}
for _, ec := range extcmds {
if err = ec.cmd.Start(); err != nil {
return
}
}
if _, err = io.Copy(multiWriter, request.reader); err != nil {
return
}
if err = pipeWriter.Close(); err != nil {
return
}
for _, ec := range extcmds {
if err = ec.cmd.Wait(); err != nil {
if ec.err != nil {
errStr := ec.err.String()
err = errors.New(tr.Tr.Get("extension '%s' failed with: %s", ec.result.name, errStr))
}
return
}
if err = ec.out.Close(); err != nil {
return
}
}
oid := hex.EncodeToString(hasher.Sum(nil))
for _, ec := range extcmds {
ec.result.oidIn = oid
oid = hex.EncodeToString(ec.hasher.Sum(nil))
ec.result.oidOut = oid
response.results = append(response.results, ec.result)
}
return
}
|