Skip to content

Commit c8151f9

Browse files
committed
ReferenceIter: use a pipeline
Allow iteration to be canceled using a context.
1 parent 4029e5d commit c8151f9

File tree

2 files changed

+65
-50
lines changed

2 files changed

+65
-50
lines changed

git/ref_iter.go

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,71 +2,89 @@ package git
22

33
import (
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.
1113
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
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()`).
4883
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
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-
}

sizes/graph.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sizes
22

33
import (
44
"bufio"
5+
"context"
56
"errors"
67
"fmt"
78
"io"
@@ -69,15 +70,13 @@ func ScanRepositoryUsingGraph(
6970
) (HistorySize, error) {
7071
graph := NewGraph(rg, nameStyle)
7172

72-
refIter, err := repo.NewReferenceIter()
73+
ctx, cancel := context.WithCancel(context.TODO())
74+
defer cancel()
75+
76+
refIter, err := repo.NewReferenceIter(ctx)
7377
if err != nil {
7478
return HistorySize{}, err
7579
}
76-
defer func() {
77-
if refIter != nil {
78-
refIter.Close()
79-
}
80-
}()
8180

8281
iter, in, err := repo.NewObjectIter("--stdin", "--date-order")
8382
if err != nil {
@@ -134,8 +133,6 @@ func ScanRepositoryUsingGraph(
134133
return
135134
}
136135
}
137-
err := refIter.Close()
138-
refIter = nil
139136
errChan <- err
140137
}()
141138

0 commit comments

Comments
 (0)