@@ -6,7 +6,12 @@ package git
6
6
7
7
import (
8
8
"bytes"
9
+ "context"
9
10
"fmt"
11
+ "io"
12
+ "os"
13
+ "strconv"
14
+ "strings"
10
15
)
11
16
12
17
// CheckAttributeOpts represents the possible options to CheckAttribute
@@ -21,7 +26,7 @@ type CheckAttributeOpts struct {
21
26
func (repo * Repository ) CheckAttribute (opts CheckAttributeOpts ) (map [string ]map [string ]string , error ) {
22
27
err := LoadGitVersion ()
23
28
if err != nil {
24
- return nil , fmt .Errorf ("Git version missing: %v" , err )
29
+ return nil , fmt .Errorf ("git version missing: %v" , err )
25
30
}
26
31
27
32
stdOut := new (bytes.Buffer )
@@ -55,13 +60,14 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
55
60
cmd := NewCommand (cmdArgs ... )
56
61
57
62
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 ())
59
64
}
60
65
66
+ // FIXME: This is incorrect on versions < 1.8.5
61
67
fields := bytes .Split (stdOut .Bytes (), []byte {'\000' })
62
68
63
69
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" )
65
71
}
66
72
67
73
var name2attribute2info = make (map [string ]map [string ]string )
@@ -80,3 +86,276 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
80
86
81
87
return name2attribute2info , nil
82
88
}
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