@@ -2,71 +2,89 @@ package git
22
33import (
44 "bufio"
5+ "context"
6+ "fmt"
57 "io"
6- "os"
7- "os/exec "
8+
9+ "github.com/github/git-sizer/internal/pipe "
810)
911
1012// ReferenceIter is an iterator that interates over references.
1113type 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
1616}
1717
1818// NewReferenceIter returns an iterator that iterates over all of the
1919// 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 ),
2824 }
2925
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+ )
3167
32- err = cmd .Start ()
68+ err := p .Start (ctx )
3369 if err != nil {
3470 return nil , err
3571 }
3672
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
4378}
4479
4580// Next returns either the next reference or a boolean `false` value
4681// indicating that the iteration is over. On errors, return an error
4782// (in this case, the caller must still call `Close()`).
4883func (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
5987 }
6088
6189 return ref , true , nil
6290}
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