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
10 "github.com/git-lfs/git-lfs/errors"
11 "github.com/rubyist/tracerx"
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.
18 // Reading a request (and errors) is as follows:
20 // s := NewFilterProcessScanner(os.Stdin, os.Stderr)
26 // if err := s.Err(); err != nil {
29 type FilterProcessScanner struct {
30 // pl is the *pktline instance used to read and write packets back and
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.
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.
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".
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`
52 func NewFilterProcessScanner(r io.Reader, w io.Writer) *FilterProcessScanner {
53 return &FilterProcessScanner{
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.
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.
67 // If there was an error reading or writing any of the packets below, an error
69 func (o *FilterProcessScanner) Init() error {
70 tracerx.Printf("Initialize filter-process")
73 initMsg, err := o.pl.readPacketText()
75 return errors.Wrap(err, "reading filter-process initialization")
77 if initMsg != "git-filter-client" {
78 return fmt.Errorf("invalid filter-process pkt-line welcome message: %s", initMsg)
81 supVers, err := o.pl.readPacketList()
83 return errors.Wrap(err, "reading filter-process versions")
85 if !isStringInSlice(supVers, reqVer) {
86 return fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqVer, supVers)
89 err = o.pl.writePacketList([]string{"git-filter-server", reqVer})
91 return errors.Wrap(err, "writing filter-process initialization failed")
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 Git, an error will be returned. If there was an
99 // error reading or writing capabilities between the two, an error will be
101 func (o *FilterProcessScanner) NegotiateCapabilities() ([]string, error) {
102 reqCaps := []string{"capability=clean", "capability=smudge"}
104 supCaps, err := o.pl.readPacketList()
106 return nil, fmt.Errorf("reading filter-process capabilities failed with %s", err)
109 for _, sup := range supCaps {
110 if sup == "capability=delay" {
111 reqCaps = append(reqCaps, "capability=delay")
116 for _, reqCap := range reqCaps {
117 if !isStringInSlice(supCaps, reqCap) {
118 return nil, fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqCap, supCaps)
122 err = o.pl.writePacketList(reqCaps)
124 return nil, fmt.Errorf("writing filter-process capabilities failed with %s", err)
130 // Request represents a single command sent to LFS from the parent Git process.
131 type Request struct {
132 // Header maps header strings to values, and is encoded as the first
133 // part of the packet.
134 Header map[string]string
135 // Payload represents the body of the packet, and contains the contents
136 // of the file in the index.
140 // Scan scans for the next request, or error and returns whether or not the scan
141 // was successful, indicating the presence of a valid request. If the Scan
142 // failed, there was either an error reading the next request (and the results
143 // of calling `Err()` should be inspected), or the pipe was closed and no more
144 // requests are present.
146 // Closing the pipe is Git's way to communicate that no more files will be
147 // filtered. Git expects that the LFS process exits after this event.
148 func (o *FilterProcessScanner) Scan() bool {
149 o.req, o.err = nil, nil
151 req, err := o.readRequest()
161 // Request returns the request read from a call to Scan(). It is available only
162 // after a call to `Scan()` has completed, and is re-initialized to nil at the
163 // beginning of the subsequent `Scan()` call.
164 func (o *FilterProcessScanner) Request() *Request { return o.req }
166 // Err returns any error encountered from the last call to Scan(). It is available only
167 // after a call to `Scan()` has completed, and is re-initialized to nil at the
168 // beginning of the subsequent `Scan()` call.
169 func (o *FilterProcessScanner) Err() error { return o.err }
171 // readRequest reads the headers of a request and yields an `io.Reader` which
172 // will read the body of the request. Since the body is _not_ offset, one
173 // request should be read in its entirety before consuming the next request.
174 func (o *FilterProcessScanner) readRequest() (*Request, error) {
175 requestList, err := o.pl.readPacketList()
181 Header: make(map[string]string),
182 Payload: &pktlineReader{pl: o.pl},
185 for _, pair := range requestList {
186 v := strings.SplitN(pair, "=", 2)
187 req.Header[v[0]] = v[1]
193 // WriteList writes a list of strings to the underlying pktline data stream in
195 func (o *FilterProcessScanner) WriteList(list []string) error {
196 return o.pl.writePacketList(list)
199 func (o *FilterProcessScanner) WriteStatus(status FilterProcessStatus) error {
200 return o.pl.writePacketList([]string{"status=" + status.String()})
203 // isStringInSlice returns whether a given string "what" is contained in a
206 // isStringInSlice is copied from "github.com/xeipuuv/gojsonschema/utils.go"
207 func isStringInSlice(s []string, what string) bool {