@@ -2,71 +2,89 @@ package git
2
2
3
3
import (
4
4
"bufio"
5
+ "context"
6
+ "fmt"
5
7
"io"
6
- "os"
7
- "os/exec "
8
+
9
+ "github.com/github/git-sizer/internal/pipe "
8
10
)
9
11
10
12
// ReferenceIter is an iterator that interates over references.
11
13
type ReferenceIter struct {
12
- cmd * exec.Cmd
13
- out io.ReadCloser
14
- f * bufio.Reader
15
- errChan <- chan error
14
+ refCh chan Reference
15
+ errCh chan error
16
16
}
17
17
18
18
// NewReferenceIter returns an iterator that iterates over all of the
19
19
// references in `repo`.
20
- func (repo * Repository ) NewReferenceIter () (* ReferenceIter , error ) {
21
- cmd := repo .GitCommand (
22
- "for-each-ref" , "--format=%(objectname) %(objecttype) %(objectsize) %(refname)" ,
23
- )
24
-
25
- out , err := cmd .StdoutPipe ()
26
- if err != nil {
27
- return nil , err
20
+ func (repo * Repository ) NewReferenceIter (ctx context.Context ) (* ReferenceIter , error ) {
21
+ iter := ReferenceIter {
22
+ refCh : make (chan Reference ),
23
+ errCh : make (chan error ),
28
24
}
29
25
30
- cmd .Stderr = os .Stderr
26
+ p := pipe .New ()
27
+ p .Add (
28
+ // Output all references and their values:
29
+ pipe .CommandStage (
30
+ "git-for-each-ref" ,
31
+ repo .GitCommand (
32
+ "for-each-ref" ,
33
+ "--format=%(objectname) %(objecttype) %(objectsize) %(refname)" ,
34
+ ),
35
+ ),
36
+
37
+ // Read the references and send them to `iter.refCh`, then close
38
+ // the channel.
39
+ pipe .Function (
40
+ "parse-refs" ,
41
+ func (ctx context.Context , env pipe.Env , stdin io.Reader , stdout io.Writer ) error {
42
+ defer close (iter .refCh )
43
+
44
+ in := bufio .NewReader (stdin )
45
+ for {
46
+ line , err := in .ReadBytes ('\n' )
47
+ if err != nil {
48
+ if err == io .EOF {
49
+ return nil
50
+ }
51
+ return fmt .Errorf ("reading 'git for-each-ref' output: %w" , err )
52
+ }
53
+
54
+ ref , err := ParseReference (string (line [:len (line )- 1 ]))
55
+ if err != nil {
56
+ return fmt .Errorf ("parsing 'git for-each-ref' output: %w" , err )
57
+ }
58
+ select {
59
+ case iter .refCh <- ref :
60
+ case <- ctx .Done ():
61
+ return ctx .Err ()
62
+ }
63
+ }
64
+ },
65
+ ),
66
+ )
31
67
32
- err = cmd .Start ()
68
+ err := p .Start (ctx )
33
69
if err != nil {
34
70
return nil , err
35
71
}
36
72
37
- return & ReferenceIter {
38
- cmd : cmd ,
39
- out : out ,
40
- f : bufio .NewReader (out ),
41
- errChan : make (chan error , 1 ),
42
- }, nil
73
+ go func () {
74
+ iter .errCh <- p .Wait ()
75
+ }()
76
+
77
+ return & iter , nil
43
78
}
44
79
45
80
// Next returns either the next reference or a boolean `false` value
46
81
// indicating that the iteration is over. On errors, return an error
47
82
// (in this case, the caller must still call `Close()`).
48
83
func (iter * ReferenceIter ) Next () (Reference , bool , error ) {
49
- line , err := iter .f .ReadString ('\n' )
50
- if err != nil {
51
- if err != io .EOF {
52
- return Reference {}, false , err
53
- }
54
- return Reference {}, false , nil
55
- }
56
- ref , err := ParseReference (line [:len (line )- 1 ])
57
- if err != nil {
58
- return ref , false , err
84
+ ref , ok := <- iter .refCh
85
+ if ! ok {
86
+ return Reference {}, false , <- iter .errCh
59
87
}
60
88
61
89
return ref , true , nil
62
90
}
63
-
64
- // Close closes the iterator and frees up resources.
65
- func (iter * ReferenceIter ) Close () error {
66
- err := iter .out .Close ()
67
- err2 := iter .cmd .Wait ()
68
- if err == nil {
69
- err = err2
70
- }
71
- return err
72
- }
0 commit comments