286c64c34b
When writing files to the LFS file storage, we create a temporary file and rename it into its correct place. Use the function that was recently introduced to create a temporary file that honors the umask. This should make all uses of Git LFS honor the umask, since Git handles writing the working tree files for us. We compute the proper permissions value on demand. In a future commit, we'll need to read the configuration file, and on clone, we'll want to wait to read the configuration until we have a repository. Add a test for this and skip it on Windows, since we cannot be guaranteed to have POSIX permission support there.
163 lines
3.4 KiB
Go
163 lines
3.4 KiB
Go
package lfs
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/git-lfs/git-lfs/config"
|
|
)
|
|
|
|
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 *exec.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 = fmt.Errorf("Invalid action: " + 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)
|
|
}
|
|
cmd := exec.Command(name, args...)
|
|
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 = fmt.Errorf("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
|
|
}
|