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 }