Imported Upstream version 2.5.1
[scm/test.git] / git / filter_process_scanner.go
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 Git, an error will be returned. If there was an
99 // error reading or writing capabilities between the two, an error will be
100 // returned.
101 func (o *FilterProcessScanner) NegotiateCapabilities() ([]string, error) {
102         reqCaps := []string{"capability=clean", "capability=smudge"}
103
104         supCaps, err := o.pl.readPacketList()
105         if err != nil {
106                 return nil, fmt.Errorf("reading filter-process capabilities failed with %s", err)
107         }
108
109         for _, sup := range supCaps {
110                 if sup == "capability=delay" {
111                         reqCaps = append(reqCaps, "capability=delay")
112                         break
113                 }
114         }
115
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)
119                 }
120         }
121
122         err = o.pl.writePacketList(reqCaps)
123         if err != nil {
124                 return nil, fmt.Errorf("writing filter-process capabilities failed with %s", err)
125         }
126
127         return supCaps, nil
128 }
129
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.
137         Payload io.Reader
138 }
139
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.
145 //
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
150
151         req, err := o.readRequest()
152         if err != nil {
153                 o.err = err
154                 return false
155         }
156
157         o.req = req
158         return true
159 }
160
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 }
165
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 }
170
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()
176         if err != nil {
177                 return nil, err
178         }
179
180         req := &Request{
181                 Header:  make(map[string]string),
182                 Payload: &pktlineReader{pl: o.pl},
183         }
184
185         for _, pair := range requestList {
186                 v := strings.SplitN(pair, "=", 2)
187                 req.Header[v[0]] = v[1]
188         }
189
190         return req, nil
191 }
192
193 // WriteList writes a list of strings to the underlying pktline data stream in
194 // pktline format.
195 func (o *FilterProcessScanner) WriteList(list []string) error {
196         return o.pl.writePacketList(list)
197 }
198
199 func (o *FilterProcessScanner) WriteStatus(status FilterProcessStatus) error {
200         return o.pl.writePacketList([]string{"status=" + status.String()})
201 }
202
203 // isStringInSlice returns whether a given string "what" is contained in a
204 // slice, "s".
205 //
206 // isStringInSlice is copied from "github.com/xeipuuv/gojsonschema/utils.go"
207 func isStringInSlice(s []string, what string) bool {
208         for i := range s {
209                 if s[i] == what {
210                         return true
211                 }
212         }
213         return false
214 }