github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/git/filter_process_scanner.go (about) 1 // Package git contains various commands that shell out to git 2 // NOTE: Subject to change, do not rely on this package from outside git-lfs source 3 package git 4 5 import ( 6 "fmt" 7 "io" 8 "strings" 9 10 "github.com/git-lfs/git-lfs/errors" 11 "github.com/rubyist/tracerx" 12 ) 13 14 // FilterProcessScanner provides a scanner-like interface capable of 15 // initializing the filter process with the Git parent, and scanning for 16 // requests across the protocol. 17 // 18 // Reading a request (and errors) is as follows: 19 // 20 // s := NewFilterProcessScanner(os.Stdin, os.Stderr) 21 // for s.Scan() { 22 // req := s.Request() 23 // // ... 24 // } 25 // 26 // if err := s.Err(); err != nil { 27 // // ... 28 // } 29 type FilterProcessScanner struct { 30 // pl is the *pktline instance used to read and write packets back and 31 // forth between Git. 32 pl *pktline 33 34 // req is a temporary variable used to hold the value accessible by the 35 // `Request()` function. It is cleared at the beginning of each `Scan()` 36 // invocation, and written to at the end of each `Scan()` invocation. 37 req *Request 38 // err is a temporary variable used to hold the value accessible by the 39 // `Request()` function. It is cleared at the beginning of each `Scan()` 40 // invocation, and written to at the end of each `Scan()` invocation. 41 err error 42 } 43 44 // NewFilterProcessScanner constructs a new instance of the 45 // `*FilterProcessScanner` type which reads packets from the `io.Reader` "r", 46 // and writes packets to the `io.Writer`, "w". 47 // 48 // Both reader and writers SHOULD NOT be `*git.PacketReader` or 49 // `*git.PacketWriter`s, they will be transparently treated as such. In other 50 // words, it is safe (and recommended) to pass `os.Stdin` and `os.Stdout` 51 // directly. 52 func NewFilterProcessScanner(r io.Reader, w io.Writer) *FilterProcessScanner { 53 return &FilterProcessScanner{ 54 pl: newPktline(r, w), 55 } 56 } 57 58 // Init initializes the filter and ACKs back and forth between the Git LFS 59 // subprocess and the Git parent process that each is a git-filter-server and 60 // client respectively. 61 // 62 // If either side wrote an invalid sequence of data, or did not meet 63 // expectations, an error will be returned. If the filter type is not supported, 64 // an error will be returned. If the pkt-line welcome message was invalid, an 65 // error will be returned. 66 // 67 // If there was an error reading or writing any of the packets below, an error 68 // will be returned. 69 func (o *FilterProcessScanner) Init() error { 70 tracerx.Printf("Initialize filter-process") 71 reqVer := "version=2" 72 73 initMsg, err := o.pl.readPacketText() 74 if err != nil { 75 return errors.Wrap(err, "reading filter-process initialization") 76 } 77 if initMsg != "git-filter-client" { 78 return fmt.Errorf("invalid filter-process pkt-line welcome message: %s", initMsg) 79 } 80 81 supVers, err := o.pl.readPacketList() 82 if err != nil { 83 return errors.Wrap(err, "reading filter-process versions") 84 } 85 if !isStringInSlice(supVers, reqVer) { 86 return fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqVer, supVers) 87 } 88 89 err = o.pl.writePacketList([]string{"git-filter-server", reqVer}) 90 if err != nil { 91 return errors.Wrap(err, "writing filter-process initialization failed") 92 } 93 return nil 94 } 95 96 // NegotiateCapabilities executes the process of negotiating capabilities 97 // between the filter client and server. If we don't support any of the 98 // capabilities given to LFS by the parent, an error will be returned. If there 99 // was an error reading or writing capabilities between the two, an error will 100 // be returned. 101 func (o *FilterProcessScanner) NegotiateCapabilities() error { 102 reqCaps := []string{"capability=clean", "capability=smudge"} 103 104 supCaps, err := o.pl.readPacketList() 105 if err != nil { 106 return fmt.Errorf("reading filter-process capabilities failed with %s", err) 107 } 108 for _, reqCap := range reqCaps { 109 if !isStringInSlice(supCaps, reqCap) { 110 return fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqCap, supCaps) 111 } 112 } 113 114 err = o.pl.writePacketList(reqCaps) 115 if err != nil { 116 return fmt.Errorf("writing filter-process capabilities failed with %s", err) 117 } 118 119 return nil 120 } 121 122 // Request represents a single command sent to LFS from the parent Git process. 123 type Request struct { 124 // Header maps header strings to values, and is encoded as the first 125 // part of the packet. 126 Header map[string]string 127 // Payload represents the body of the packet, and contains the contents 128 // of the file in the index. 129 Payload io.Reader 130 } 131 132 // Scan scans for the next request, or error and returns whether or not the scan 133 // was successful, indicating the presence of a valid request. If the Scan 134 // failed, there was either an error reading the next request (and the results 135 // of calling `Err()` should be inspected), or the pipe was closed and no more 136 // requests are present. 137 // 138 // Closing the pipe is Git's way to communicate that no more files will be 139 // filtered. Git expects that the LFS process exits after this event. 140 func (o *FilterProcessScanner) Scan() bool { 141 o.req, o.err = nil, nil 142 143 req, err := o.readRequest() 144 if err != nil { 145 o.err = err 146 return false 147 } 148 149 o.req = req 150 return true 151 } 152 153 // Request returns the request read from a call to Scan(). It is available only 154 // after a call to `Scan()` has completed, and is re-initialized to nil at the 155 // beginning of the subsequent `Scan()` call. 156 func (o *FilterProcessScanner) Request() *Request { return o.req } 157 158 // Err returns any error encountered from the last call to Scan(). It is available only 159 // after a call to `Scan()` has completed, and is re-initialized to nil at the 160 // beginning of the subsequent `Scan()` call. 161 func (o *FilterProcessScanner) Err() error { return o.err } 162 163 // readRequest reads the headers of a request and yields an `io.Reader` which 164 // will read the body of the request. Since the body is _not_ offset, one 165 // request should be read in its entirety before consuming the next request. 166 func (o *FilterProcessScanner) readRequest() (*Request, error) { 167 tracerx.Printf("Read filter-process request.") 168 169 requestList, err := o.pl.readPacketList() 170 if err != nil { 171 return nil, err 172 } 173 174 req := &Request{ 175 Header: make(map[string]string), 176 Payload: &pktlineReader{pl: o.pl}, 177 } 178 179 for _, pair := range requestList { 180 v := strings.SplitN(pair, "=", 2) 181 req.Header[v[0]] = v[1] 182 } 183 184 return req, nil 185 } 186 187 func (o *FilterProcessScanner) WriteStatus(status string) error { 188 return o.pl.writePacketList([]string{"status=" + status}) 189 } 190 191 // isStringInSlice returns whether a given string "what" is contained in a 192 // slice, "s". 193 // 194 // isStringInSlice is copied from "github.com/xeipuuv/gojsonschema/utils.go" 195 func isStringInSlice(s []string, what string) bool { 196 for i := range s { 197 if s[i] == what { 198 return true 199 } 200 } 201 return false 202 }