Skip to content

Commit 939c176

Browse files
committed
git/git.go: split the code out into multiple files:
* `git/batch_obj_iter.go` * `git/commit.go` * `git/git.go` * `git/git_command.go` * `git/obj_head_iter.go` * `git/obj_iter.go` * `git/oid.go` * `git/ref_iter.go` * `git/reference.go` * `git/tag.go` * `git/tree.go`
1 parent bb80a01 commit 939c176

File tree

10 files changed

+676
-605
lines changed

10 files changed

+676
-605
lines changed

git/batch_obj_iter.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package git
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
"os/exec"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/github/git-sizer/counts"
13+
)
14+
15+
// BatchObjectIter iterates over objects whose names are fed into its
16+
// stdin. The output is buffered, so it has to be closed before you
17+
// can be sure that you have gotten all of the objects.
18+
type BatchObjectIter struct {
19+
cmd *exec.Cmd
20+
out io.ReadCloser
21+
f *bufio.Reader
22+
}
23+
24+
// NewBatchObjectIter returns a `*BatchObjectIterator` and an
25+
// `io.WriteCloser`. The iterator iterates over objects whose names
26+
// are fed into the `io.WriteCloser`, one per line. The
27+
// `io.WriteCloser` should normally be closed and the iterator's
28+
// output drained before `Close()` is called.
29+
func (repo *Repository) NewBatchObjectIter() (*BatchObjectIter, io.WriteCloser, error) {
30+
cmd := repo.gitCommand("cat-file", "--batch", "--buffer")
31+
32+
in, err := cmd.StdinPipe()
33+
if err != nil {
34+
return nil, nil, err
35+
}
36+
37+
out, err := cmd.StdoutPipe()
38+
if err != nil {
39+
return nil, nil, err
40+
}
41+
42+
cmd.Stderr = os.Stderr
43+
44+
err = cmd.Start()
45+
if err != nil {
46+
return nil, nil, err
47+
}
48+
49+
return &BatchObjectIter{
50+
cmd: cmd,
51+
out: out,
52+
f: bufio.NewReader(out),
53+
}, in, nil
54+
}
55+
56+
// Next returns the next object: its OID, type, size, and contents.
57+
// When no more data are available, it returns an `io.EOF` error.
58+
func (iter *BatchObjectIter) Next() (OID, ObjectType, counts.Count32, []byte, error) {
59+
header, err := iter.f.ReadString('\n')
60+
if err != nil {
61+
return OID{}, "", 0, nil, err
62+
}
63+
oid, objectType, objectSize, err := parseBatchHeader("", header)
64+
if err != nil {
65+
return OID{}, "", 0, nil, err
66+
}
67+
// +1 for LF:
68+
data := make([]byte, objectSize+1)
69+
_, err = io.ReadFull(iter.f, data)
70+
if err != nil {
71+
return OID{}, "", 0, nil, err
72+
}
73+
data = data[:len(data)-1]
74+
return oid, objectType, objectSize, data, nil
75+
}
76+
77+
// Close closes the iterator and frees up resources. If any iterator
78+
// output hasn't been read yet, it will be lost.
79+
func (iter *BatchObjectIter) Close() error {
80+
err := iter.out.Close()
81+
err2 := iter.cmd.Wait()
82+
if err == nil {
83+
err = err2
84+
}
85+
return err
86+
}
87+
88+
// Parse a `cat-file --batch[-check]` output header line (including
89+
// the trailing LF). `spec`, if not "", is used in error messages.
90+
func parseBatchHeader(spec string, header string) (OID, ObjectType, counts.Count32, error) {
91+
header = header[:len(header)-1]
92+
words := strings.Split(header, " ")
93+
if words[len(words)-1] == "missing" {
94+
if spec == "" {
95+
spec = words[0]
96+
}
97+
return OID{}, "missing", 0, fmt.Errorf("missing object %s", spec)
98+
}
99+
100+
oid, err := NewOID(words[0])
101+
if err != nil {
102+
return OID{}, "missing", 0, err
103+
}
104+
105+
size, err := strconv.ParseUint(words[2], 10, 0)
106+
if err != nil {
107+
return OID{}, "missing", 0, err
108+
}
109+
return oid, ObjectType(words[1]), counts.NewCount32(size), nil
110+
}

git/commit.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package git
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/github/git-sizer/counts"
7+
)
8+
9+
// Commit represents the parts of a commit object that we need.
10+
type Commit struct {
11+
Size counts.Count32
12+
Parents []OID
13+
Tree OID
14+
}
15+
16+
// ParseCommit parses the commit object whose contents are in `data`.
17+
// `oid` is used only in error messages.
18+
func ParseCommit(oid OID, data []byte) (*Commit, error) {
19+
var parents []OID
20+
var tree OID
21+
var treeFound bool
22+
iter, err := NewObjectHeaderIter(oid.String(), data)
23+
if err != nil {
24+
return nil, err
25+
}
26+
for iter.HasNext() {
27+
key, value, err := iter.Next()
28+
if err != nil {
29+
return nil, err
30+
}
31+
switch key {
32+
case "parent":
33+
parent, err := NewOID(value)
34+
if err != nil {
35+
return nil, fmt.Errorf("malformed parent header in commit %s", oid)
36+
}
37+
parents = append(parents, parent)
38+
case "tree":
39+
if treeFound {
40+
return nil, fmt.Errorf("multiple trees found in commit %s", oid)
41+
}
42+
tree, err = NewOID(value)
43+
if err != nil {
44+
return nil, fmt.Errorf("malformed tree header in commit %s", oid)
45+
}
46+
treeFound = true
47+
}
48+
}
49+
if !treeFound {
50+
return nil, fmt.Errorf("no tree found in commit %s", oid)
51+
}
52+
return &Commit{
53+
Size: counts.NewCount32(uint64(len(data))),
54+
Parents: parents,
55+
Tree: tree,
56+
}, nil
57+
}

0 commit comments

Comments
 (0)