@@ -6,7 +6,12 @@ package git
66
77import (
88 "bytes"
9+ "context"
910 "fmt"
11+ "io"
12+ "os"
13+ "strconv"
14+ "strings"
1015)
1116
1217// CheckAttributeOpts represents the possible options to CheckAttribute
@@ -21,7 +26,7 @@ type CheckAttributeOpts struct {
2126func (repo * Repository ) CheckAttribute (opts CheckAttributeOpts ) (map [string ]map [string ]string , error ) {
2227 err := LoadGitVersion ()
2328 if err != nil {
24- return nil , fmt .Errorf ("Git version missing: %v" , err )
29+ return nil , fmt .Errorf ("git version missing: %v" , err )
2530 }
2631
2732 stdOut := new (bytes.Buffer )
@@ -55,13 +60,14 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
5560 cmd := NewCommand (cmdArgs ... )
5661
5762 if err := cmd .RunInDirPipeline (repo .Path , stdOut , stdErr ); err != nil {
58- return nil , fmt .Errorf ("Failed to run check-attr: %v\n %s\n %s" , err , stdOut .String (), stdErr .String ())
63+ return nil , fmt .Errorf ("failed to run check-attr: %v\n %s\n %s" , err , stdOut .String (), stdErr .String ())
5964 }
6065
66+ // FIXME: This is incorrect on versions < 1.8.5
6167 fields := bytes .Split (stdOut .Bytes (), []byte {'\000' })
6268
6369 if len (fields )% 3 != 1 {
64- return nil , fmt .Errorf ("Wrong number of fields in return from check-attr" )
70+ return nil , fmt .Errorf ("wrong number of fields in return from check-attr" )
6571 }
6672
6773 var name2attribute2info = make (map [string ]map [string ]string )
@@ -80,3 +86,276 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
8086
8187 return name2attribute2info , nil
8288}
89+
90+ // CheckAttributeReader provides a reader for check-attribute content that can be long running
91+ type CheckAttributeReader struct {
92+ // params
93+ Attributes []string
94+ Repo * Repository
95+ IndexFile string
96+ WorkTree string
97+
98+ stdinReader io.ReadCloser
99+ stdinWriter * os.File
100+ stdOut attributeWriter
101+ cmd * Command
102+ env []string
103+ ctx context.Context
104+ cancel context.CancelFunc
105+ running chan struct {}
106+ }
107+
108+ // Init initializes the cmd
109+ func (c * CheckAttributeReader ) Init (ctx context.Context ) error {
110+ c .running = make (chan struct {})
111+ cmdArgs := []string {"check-attr" , "--stdin" , "-z" }
112+
113+ if len (c .IndexFile ) > 0 && CheckGitVersionAtLeast ("1.7.8" ) == nil {
114+ cmdArgs = append (cmdArgs , "--cached" )
115+ c .env = []string {"GIT_INDEX_FILE=" + c .IndexFile }
116+ }
117+
118+ if len (c .WorkTree ) > 0 && CheckGitVersionAtLeast ("1.7.8" ) == nil {
119+ c .env = []string {"GIT_WORK_TREE=" + c .WorkTree }
120+ }
121+
122+ if len (c .Attributes ) > 0 {
123+ cmdArgs = append (cmdArgs , c .Attributes ... )
124+ cmdArgs = append (cmdArgs , "--" )
125+ } else {
126+ lw := new (nulSeparatedAttributeWriter )
127+ lw .attributes = make (chan attributeTriple )
128+
129+ c .stdOut = lw
130+ c .stdOut .Close ()
131+ return fmt .Errorf ("no provided Attributes to check" )
132+ }
133+
134+ c .ctx , c .cancel = context .WithCancel (ctx )
135+ c .cmd = NewCommandContext (c .ctx , cmdArgs ... )
136+ var err error
137+ c .stdinReader , c .stdinWriter , err = os .Pipe ()
138+ if err != nil {
139+ return err
140+ }
141+
142+ if CheckGitVersionAtLeast ("1.8.5" ) == nil {
143+ lw := new (nulSeparatedAttributeWriter )
144+ lw .attributes = make (chan attributeTriple , 5 )
145+
146+ c .stdOut = lw
147+ } else {
148+ lw := new (lineSeparatedAttributeWriter )
149+ lw .attributes = make (chan attributeTriple , 5 )
150+
151+ c .stdOut = lw
152+ }
153+ return nil
154+ }
155+
156+ // Run run cmd
157+ func (c * CheckAttributeReader ) Run () error {
158+ stdErr := new (bytes.Buffer )
159+ err := c .cmd .RunInDirTimeoutEnvFullPipelineFunc (c .env , - 1 , c .Repo .Path , c .stdOut , stdErr , c .stdinReader , func (_ context.Context , _ context.CancelFunc ) error {
160+ close (c .running )
161+ return nil
162+ })
163+ defer c .cancel ()
164+ _ = c .stdOut .Close ()
165+ if err != nil && c .ctx .Err () != nil && err .Error () != "signal: killed" {
166+ return fmt .Errorf ("failed to run attr-check. Error: %w\n Stderr: %s" , err , stdErr .String ())
167+ }
168+
169+ return nil
170+ }
171+
172+ // CheckPath check attr for given path
173+ func (c * CheckAttributeReader ) CheckPath (path string ) (map [string ]string , error ) {
174+ select {
175+ case <- c .ctx .Done ():
176+ return nil , c .ctx .Err ()
177+ case <- c .running :
178+ }
179+
180+ if _ , err := c .stdinWriter .Write ([]byte (path + "\x00 " )); err != nil {
181+ defer c .cancel ()
182+ return nil , err
183+ }
184+
185+ if err := c .stdinWriter .Sync (); err != nil {
186+ defer c .cancel ()
187+ return nil , err
188+ }
189+
190+ rs := make (map [string ]string )
191+ for range c .Attributes {
192+ select {
193+ case attr := <- c .stdOut .ReadAttribute ():
194+ rs [attr .Attribute ] = attr .Value
195+ case <- c .ctx .Done ():
196+ return nil , c .ctx .Err ()
197+ }
198+ }
199+ return rs , nil
200+ }
201+
202+ // Close close pip after use
203+ func (c * CheckAttributeReader ) Close () error {
204+ select {
205+ case <- c .running :
206+ default :
207+ close (c .running )
208+ }
209+ defer c .cancel ()
210+ return c .stdinWriter .Close ()
211+ }
212+
213+ type attributeWriter interface {
214+ io.WriteCloser
215+ ReadAttribute () <- chan attributeTriple
216+ }
217+
218+ type attributeTriple struct {
219+ Filename string
220+ Attribute string
221+ Value string
222+ }
223+
224+ type nulSeparatedAttributeWriter struct {
225+ tmp []byte
226+ attributes chan attributeTriple
227+ working attributeTriple
228+ pos int
229+ }
230+
231+ func (wr * nulSeparatedAttributeWriter ) Write (p []byte ) (n int , err error ) {
232+ l , read := len (p ), 0
233+
234+ nulIdx := bytes .IndexByte (p , '\x00' )
235+ for nulIdx >= 0 {
236+ wr .tmp = append (wr .tmp , p [:nulIdx ]... )
237+ switch wr .pos {
238+ case 0 :
239+ wr .working = attributeTriple {
240+ Filename : string (wr .tmp ),
241+ }
242+ case 1 :
243+ wr .working .Attribute = string (wr .tmp )
244+ case 2 :
245+ wr .working .Value = string (wr .tmp )
246+ }
247+ wr .tmp = wr .tmp [:0 ]
248+ wr .pos ++
249+ if wr .pos > 2 {
250+ wr .attributes <- wr .working
251+ wr .pos = 0
252+ }
253+ read += nulIdx + 1
254+ if l > read {
255+ p = p [nulIdx + 1 :]
256+ nulIdx = bytes .IndexByte (p , '\x00' )
257+ } else {
258+ return l , nil
259+ }
260+ }
261+ wr .tmp = append (wr .tmp , p ... )
262+ return len (p ), nil
263+ }
264+
265+ func (wr * nulSeparatedAttributeWriter ) ReadAttribute () <- chan attributeTriple {
266+ return wr .attributes
267+ }
268+
269+ func (wr * nulSeparatedAttributeWriter ) Close () error {
270+ close (wr .attributes )
271+ return nil
272+ }
273+
274+ type lineSeparatedAttributeWriter struct {
275+ tmp []byte
276+ attributes chan attributeTriple
277+ }
278+
279+ func (wr * lineSeparatedAttributeWriter ) Write (p []byte ) (n int , err error ) {
280+ l := len (p )
281+
282+ nlIdx := bytes .IndexByte (p , '\n' )
283+ for nlIdx >= 0 {
284+ wr .tmp = append (wr .tmp , p [:nlIdx ]... )
285+
286+ if len (wr .tmp ) == 0 {
287+ // This should not happen
288+ if len (p ) > nlIdx + 1 {
289+ wr .tmp = wr .tmp [:0 ]
290+ p = p [nlIdx + 1 :]
291+ nlIdx = bytes .IndexByte (p , '\n' )
292+ continue
293+ } else {
294+ return l , nil
295+ }
296+ }
297+
298+ working := attributeTriple {}
299+ if wr .tmp [0 ] == '"' {
300+ sb := new (strings.Builder )
301+ remaining := string (wr .tmp [1 :])
302+ for len (remaining ) > 0 {
303+ rn , _ , tail , err := strconv .UnquoteChar (remaining , '"' )
304+ if err != nil {
305+ if len (remaining ) > 2 && remaining [0 ] == '"' && remaining [1 ] == ':' && remaining [2 ] == ' ' {
306+ working .Filename = sb .String ()
307+ wr .tmp = []byte (remaining [3 :])
308+ break
309+ }
310+ return l , fmt .Errorf ("unexpected tail %s" , string (remaining ))
311+ }
312+ _ , _ = sb .WriteRune (rn )
313+ remaining = tail
314+ }
315+ } else {
316+ idx := bytes .IndexByte (wr .tmp , ':' )
317+ if idx < 0 {
318+ return l , fmt .Errorf ("unexpected input %s" , string (wr .tmp ))
319+ }
320+ working .Filename = string (wr .tmp [:idx ])
321+ if len (wr .tmp ) < idx + 2 {
322+ return l , fmt .Errorf ("unexpected input %s" , string (wr .tmp ))
323+ }
324+ wr .tmp = wr .tmp [idx + 2 :]
325+ }
326+
327+ idx := bytes .IndexByte (wr .tmp , ':' )
328+ if idx < 0 {
329+ return l , fmt .Errorf ("unexpected input %s" , string (wr .tmp ))
330+ }
331+
332+ working .Attribute = string (wr .tmp [:idx ])
333+ if len (wr .tmp ) < idx + 2 {
334+ return l , fmt .Errorf ("unexpected input %s" , string (wr .tmp ))
335+ }
336+
337+ working .Value = string (wr .tmp [idx + 2 :])
338+
339+ wr .attributes <- working
340+ wr .tmp = wr .tmp [:0 ]
341+ if len (p ) > nlIdx + 1 {
342+ p = p [nlIdx + 1 :]
343+ nlIdx = bytes .IndexByte (p , '\n' )
344+ continue
345+ } else {
346+ return l , nil
347+ }
348+ }
349+
350+ wr .tmp = append (wr .tmp , p ... )
351+ return l , nil
352+ }
353+
354+ func (wr * lineSeparatedAttributeWriter ) ReadAttribute () <- chan attributeTriple {
355+ return wr .attributes
356+ }
357+
358+ func (wr * lineSeparatedAttributeWriter ) Close () error {
359+ close (wr .attributes )
360+ return nil
361+ }
0 commit comments