github.com/hawser/git-hawser@v2.5.2+incompatible/lfs/extension.go (about)

     1  package lfs
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"hash"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"strings"
    14  
    15  	"github.com/git-lfs/git-lfs/config"
    16  )
    17  
    18  type pipeRequest struct {
    19  	action     string
    20  	reader     io.Reader
    21  	fileName   string
    22  	extensions []config.Extension
    23  }
    24  
    25  type pipeResponse struct {
    26  	file    *os.File
    27  	results []*pipeExtResult
    28  }
    29  
    30  type pipeExtResult struct {
    31  	name   string
    32  	oidIn  string
    33  	oidOut string
    34  }
    35  
    36  type extCommand struct {
    37  	cmd    *exec.Cmd
    38  	out    io.WriteCloser
    39  	err    *bytes.Buffer
    40  	hasher hash.Hash
    41  	result *pipeExtResult
    42  }
    43  
    44  func pipeExtensions(cfg *config.Configuration, request *pipeRequest) (response pipeResponse, err error) {
    45  	var extcmds []*extCommand
    46  	defer func() {
    47  		// In the case of an early return before the end of this
    48  		// function (in response to an error, etc), kill all running
    49  		// processes. Errors are ignored since the function has already
    50  		// returned.
    51  		//
    52  		// In the happy path, the commands will have already been
    53  		// `Wait()`-ed upon and e.cmd.Process.Kill() will return an
    54  		// error, but we can ignore it.
    55  		for _, e := range extcmds {
    56  			if e.cmd.Process != nil {
    57  				e.cmd.Process.Kill()
    58  			}
    59  		}
    60  	}()
    61  
    62  	for _, e := range request.extensions {
    63  		var pieces []string
    64  		switch request.action {
    65  		case "clean":
    66  			pieces = strings.Split(e.Clean, " ")
    67  		case "smudge":
    68  			pieces = strings.Split(e.Smudge, " ")
    69  		default:
    70  			err = fmt.Errorf("Invalid action: " + request.action)
    71  			return
    72  		}
    73  		name := strings.Trim(pieces[0], " ")
    74  		var args []string
    75  		for _, value := range pieces[1:] {
    76  			arg := strings.Replace(value, "%f", request.fileName, -1)
    77  			args = append(args, arg)
    78  		}
    79  		cmd := exec.Command(name, args...)
    80  		ec := &extCommand{cmd: cmd, result: &pipeExtResult{name: e.Name}}
    81  		extcmds = append(extcmds, ec)
    82  	}
    83  
    84  	hasher := sha256.New()
    85  	pipeReader, pipeWriter := io.Pipe()
    86  	multiWriter := io.MultiWriter(hasher, pipeWriter)
    87  
    88  	var input io.Reader
    89  	var output io.WriteCloser
    90  	input = pipeReader
    91  	extcmds[0].cmd.Stdin = input
    92  	if response.file, err = ioutil.TempFile(cfg.TempDir(), ""); err != nil {
    93  		return
    94  	}
    95  	defer response.file.Close()
    96  	output = response.file
    97  
    98  	last := len(extcmds) - 1
    99  	for i, ec := range extcmds {
   100  		ec.hasher = sha256.New()
   101  
   102  		if i == last {
   103  			ec.cmd.Stdout = io.MultiWriter(ec.hasher, output)
   104  			ec.out = output
   105  			continue
   106  		}
   107  
   108  		nextec := extcmds[i+1]
   109  		var nextStdin io.WriteCloser
   110  		var stdout io.ReadCloser
   111  		if nextStdin, err = nextec.cmd.StdinPipe(); err != nil {
   112  			return
   113  		}
   114  		if stdout, err = ec.cmd.StdoutPipe(); err != nil {
   115  			return
   116  		}
   117  
   118  		ec.cmd.Stdin = input
   119  		ec.cmd.Stdout = io.MultiWriter(ec.hasher, nextStdin)
   120  		ec.out = nextStdin
   121  
   122  		input = stdout
   123  
   124  		var errBuff bytes.Buffer
   125  		ec.err = &errBuff
   126  		ec.cmd.Stderr = ec.err
   127  	}
   128  
   129  	for _, ec := range extcmds {
   130  		if err = ec.cmd.Start(); err != nil {
   131  			return
   132  		}
   133  	}
   134  
   135  	if _, err = io.Copy(multiWriter, request.reader); err != nil {
   136  		return
   137  	}
   138  	if err = pipeWriter.Close(); err != nil {
   139  		return
   140  	}
   141  
   142  	for _, ec := range extcmds {
   143  		if err = ec.cmd.Wait(); err != nil {
   144  			if ec.err != nil {
   145  				errStr := ec.err.String()
   146  				err = fmt.Errorf("Extension '%s' failed with: %s", ec.result.name, errStr)
   147  			}
   148  			return
   149  		}
   150  		if err = ec.out.Close(); err != nil {
   151  			return
   152  		}
   153  	}
   154  
   155  	oid := hex.EncodeToString(hasher.Sum(nil))
   156  	for _, ec := range extcmds {
   157  		ec.result.oidIn = oid
   158  		oid = hex.EncodeToString(ec.hasher.Sum(nil))
   159  		ec.result.oidOut = oid
   160  		response.results = append(response.results, ec.result)
   161  	}
   162  	return
   163  }