Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 3ab40da

Browse files
committed
plumbing: object, add APIs for traversing over commit graphs
Signed-off-by: Filip Navara <[email protected]>
1 parent c95d1f6 commit 3ab40da

File tree

3 files changed

+499
-0
lines changed

3 files changed

+499
-0
lines changed

plumbing/object/commitnode.go

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
package object
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"time"
7+
8+
"gopkg.in/src-d/go-git.v4/plumbing"
9+
"gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph"
10+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
11+
)
12+
13+
// CommitNode is generic interface encapsulating either Commit object or
14+
// graphCommitNode object
15+
type CommitNode interface {
16+
ID() plumbing.Hash
17+
Tree() (*Tree, error)
18+
CommitTime() time.Time
19+
}
20+
21+
// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects
22+
// and accessor methods for walking it as a directed graph
23+
type CommitNodeIndex interface {
24+
NumParents(node CommitNode) int
25+
ParentNodes(node CommitNode) CommitNodeIter
26+
ParentNode(node CommitNode, i int) (CommitNode, error)
27+
ParentHashes(node CommitNode) []plumbing.Hash
28+
29+
Get(hash plumbing.Hash) (CommitNode, error)
30+
31+
// Commit returns the full commit object from the node
32+
Commit(node CommitNode) (*Commit, error)
33+
}
34+
35+
// CommitNodeIter is a generic closable interface for iterating over commit nodes.
36+
type CommitNodeIter interface {
37+
Next() (CommitNode, error)
38+
ForEach(func(CommitNode) error) error
39+
Close()
40+
}
41+
42+
// graphCommitNode is a reduced representation of Commit as presented in the commit
43+
// graph file (commitgraph.Node). It is merely useful as an optimization for walking
44+
// the commit graphs.
45+
//
46+
// graphCommitNode implements the CommitNode interface.
47+
type graphCommitNode struct {
48+
// Hash for the Commit object
49+
hash plumbing.Hash
50+
// Index of the node in the commit graph file
51+
index int
52+
53+
node *commitgraph.Node
54+
gci *graphCommitNodeIndex
55+
}
56+
57+
// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit
58+
// graph files and the object store.
59+
//
60+
// graphCommitNodeIndex implements the CommitNodeIndex interface
61+
type graphCommitNodeIndex struct {
62+
commitGraph commitgraph.Index
63+
s storer.EncodedObjectStorer
64+
}
65+
66+
// objectCommitNode is a representation of Commit as presented in the GIT object format.
67+
//
68+
// objectCommitNode implements the CommitNode interface.
69+
type objectCommitNode struct {
70+
commit *Commit
71+
}
72+
73+
// objectCommitNodeIndex is an index that can load CommitNode objects only from the
74+
// object store.
75+
//
76+
// objectCommitNodeIndex implements the CommitNodeIndex interface
77+
type objectCommitNodeIndex struct {
78+
s storer.EncodedObjectStorer
79+
}
80+
81+
// ID returns the Commit object id referenced by the commit graph node.
82+
func (c *graphCommitNode) ID() plumbing.Hash {
83+
return c.hash
84+
}
85+
86+
// Tree returns the Tree referenced by the commit graph node.
87+
func (c *graphCommitNode) Tree() (*Tree, error) {
88+
return GetTree(c.gci.s, c.node.TreeHash)
89+
}
90+
91+
// CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node.
92+
func (c *graphCommitNode) CommitTime() time.Time {
93+
return c.node.When
94+
}
95+
96+
func (c *graphCommitNode) String() string {
97+
return fmt.Sprintf(
98+
"%s %s\nDate: %s",
99+
plumbing.CommitObject, c.ID(),
100+
c.CommitTime().Format(DateFormat),
101+
)
102+
}
103+
104+
func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex {
105+
return &graphCommitNodeIndex{commitGraph, s}
106+
}
107+
108+
// NumParents returns the number of parents in a commit.
109+
func (gci *graphCommitNodeIndex) NumParents(node CommitNode) int {
110+
if cgn, ok := node.(*graphCommitNode); ok {
111+
return len(cgn.node.ParentIndexes)
112+
}
113+
co := node.(*objectCommitNode)
114+
return co.commit.NumParents()
115+
}
116+
117+
// ParentNodes return a CommitNodeIter for parents of specified node.
118+
func (gci *graphCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter {
119+
return newParentgraphCommitNodeIter(gci, node)
120+
}
121+
122+
// ParentNode returns the ith parent of a commit.
123+
func (gci *graphCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) {
124+
if cgn, ok := node.(*graphCommitNode); ok {
125+
if len(cgn.node.ParentIndexes) == 0 || i >= len(cgn.node.ParentIndexes) {
126+
return nil, ErrParentNotFound
127+
}
128+
129+
parent, err := gci.commitGraph.GetNodeByIndex(cgn.node.ParentIndexes[i])
130+
if err != nil {
131+
return nil, err
132+
}
133+
134+
return &graphCommitNode{
135+
hash: cgn.node.ParentHashes[i],
136+
index: cgn.node.ParentIndexes[i],
137+
node: parent,
138+
gci: gci,
139+
}, nil
140+
}
141+
142+
co := node.(*objectCommitNode)
143+
if len(co.commit.ParentHashes) == 0 || i >= len(co.commit.ParentHashes) {
144+
return nil, ErrParentNotFound
145+
}
146+
147+
parentHash := co.commit.ParentHashes[i]
148+
return gci.Get(parentHash)
149+
}
150+
151+
// ParentHashes returns hashes of the parent commits for a specified node
152+
func (gci *graphCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash {
153+
if cgn, ok := node.(*graphCommitNode); ok {
154+
return cgn.node.ParentHashes
155+
}
156+
co := node.(*objectCommitNode)
157+
return co.commit.ParentHashes
158+
}
159+
160+
// NodeFromHash looks up a commit node by it's object hash
161+
func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) {
162+
// Check the commit graph first
163+
parentIndex, err := gci.commitGraph.GetIndexByHash(hash)
164+
if err == nil {
165+
parent, err := gci.commitGraph.GetNodeByIndex(parentIndex)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
return &graphCommitNode{
171+
hash: hash,
172+
index: parentIndex,
173+
node: parent,
174+
gci: gci,
175+
}, nil
176+
}
177+
178+
// Fallback to loading full commit object
179+
commit, err := GetCommit(gci.s, hash)
180+
if err != nil {
181+
return nil, err
182+
}
183+
184+
return &objectCommitNode{commit: commit}, nil
185+
}
186+
187+
// Commit returns the full Commit object representing the commit graph node.
188+
func (gci *graphCommitNodeIndex) Commit(node CommitNode) (*Commit, error) {
189+
if cgn, ok := node.(*graphCommitNode); ok {
190+
return GetCommit(gci.s, cgn.ID())
191+
}
192+
co := node.(*objectCommitNode)
193+
return co.commit, nil
194+
}
195+
196+
// CommitTime returns the time when the commit was performed.
197+
//
198+
// CommitTime is present to fulfill the CommitNode interface.
199+
func (c *objectCommitNode) CommitTime() time.Time {
200+
return c.commit.Committer.When
201+
}
202+
203+
// ID returns the Commit object id referenced by the node.
204+
func (c *objectCommitNode) ID() plumbing.Hash {
205+
return c.commit.ID()
206+
}
207+
208+
// Tree returns the Tree referenced by the node.
209+
func (c *objectCommitNode) Tree() (*Tree, error) {
210+
return c.commit.Tree()
211+
}
212+
213+
func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex {
214+
return &objectCommitNodeIndex{s}
215+
}
216+
217+
// NumParents returns the number of parents in a commit.
218+
func (oci *objectCommitNodeIndex) NumParents(node CommitNode) int {
219+
co := node.(*objectCommitNode)
220+
return co.commit.NumParents()
221+
}
222+
223+
// ParentNodes return a CommitNodeIter for parents of specified node.
224+
func (oci *objectCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter {
225+
return newParentgraphCommitNodeIter(oci, node)
226+
}
227+
228+
// ParentNode returns the ith parent of a commit.
229+
func (oci *objectCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) {
230+
co := node.(*objectCommitNode)
231+
parent, err := co.commit.Parent(i)
232+
if err != nil {
233+
return nil, err
234+
}
235+
return &objectCommitNode{commit: parent}, nil
236+
}
237+
238+
// ParentHashes returns hashes of the parent commits for a specified node
239+
func (oci *objectCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash {
240+
co := node.(*objectCommitNode)
241+
return co.commit.ParentHashes
242+
}
243+
244+
// NodeFromHash looks up a commit node by it's object hash
245+
func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) {
246+
commit, err := GetCommit(oci.s, hash)
247+
if err != nil {
248+
return nil, err
249+
}
250+
251+
return &objectCommitNode{commit: commit}, nil
252+
}
253+
254+
// Commit returns the full Commit object representing the commit graph node.
255+
func (oci *objectCommitNodeIndex) Commit(node CommitNode) (*Commit, error) {
256+
co := node.(*objectCommitNode)
257+
return co.commit, nil
258+
}
259+
260+
// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex.
261+
type parentCommitNodeIter struct {
262+
gci CommitNodeIndex
263+
node CommitNode
264+
i int
265+
}
266+
267+
func newParentgraphCommitNodeIter(gci CommitNodeIndex, node CommitNode) CommitNodeIter {
268+
return &parentCommitNodeIter{gci, node, 0}
269+
}
270+
271+
// Next moves the iterator to the next commit and returns a pointer to it. If
272+
// there are no more commits, it returns io.EOF.
273+
func (iter *parentCommitNodeIter) Next() (CommitNode, error) {
274+
obj, err := iter.gci.ParentNode(iter.node, iter.i)
275+
if err == ErrParentNotFound {
276+
return nil, io.EOF
277+
}
278+
if err == nil {
279+
iter.i++
280+
}
281+
282+
return obj, err
283+
}
284+
285+
// ForEach call the cb function for each commit contained on this iter until
286+
// an error appends or the end of the iter is reached. If ErrStop is sent
287+
// the iteration is stopped but no error is returned. The iterator is closed.
288+
func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error {
289+
for {
290+
obj, err := iter.Next()
291+
if err != nil {
292+
if err == io.EOF {
293+
return nil
294+
}
295+
296+
return err
297+
}
298+
299+
if err := cb(obj); err != nil {
300+
if err == storer.ErrStop {
301+
return nil
302+
}
303+
304+
return err
305+
}
306+
}
307+
}
308+
309+
func (iter *parentCommitNodeIter) Close() {
310+
}

plumbing/object/commitnode_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package object
2+
3+
import (
4+
"path"
5+
6+
"golang.org/x/exp/mmap"
7+
. "gopkg.in/check.v1"
8+
"gopkg.in/src-d/go-git-fixtures.v3"
9+
"gopkg.in/src-d/go-git.v4/plumbing"
10+
"gopkg.in/src-d/go-git.v4/plumbing/cache"
11+
"gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph"
12+
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
13+
"gopkg.in/src-d/go-git.v4/storage/filesystem"
14+
)
15+
16+
type CommitNodeSuite struct {
17+
fixtures.Suite
18+
}
19+
20+
var _ = Suite(&CommitNodeSuite{})
21+
22+
func testWalker(c *C, nodeIndex CommitNodeIndex) {
23+
head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c"))
24+
c.Assert(err, IsNil)
25+
26+
iter := NewCommitNodeIterCTime(
27+
head,
28+
nodeIndex,
29+
nil,
30+
nil,
31+
)
32+
33+
var commits []CommitNode
34+
iter.ForEach(func(c CommitNode) error {
35+
commits = append(commits, c)
36+
return nil
37+
})
38+
39+
c.Assert(commits, HasLen, 9)
40+
41+
expected := []string{
42+
"b9d69064b190e7aedccf84731ca1d917871f8a1c",
43+
"6f6c5d2be7852c782be1dd13e36496dd7ad39560",
44+
"a45273fe2d63300e1962a9e26a6b15c276cd7082",
45+
"c0edf780dd0da6a65a7a49a86032fcf8a0c2d467",
46+
"bb13916df33ed23004c3ce9ed3b8487528e655c1",
47+
"03d2c021ff68954cf3ef0a36825e194a4b98f981",
48+
"ce275064ad67d51e99f026084e20827901a8361c",
49+
"e713b52d7e13807e87a002e812041f248db3f643",
50+
"347c91919944a68e9413581a1bc15519550a3afe",
51+
}
52+
for i, commit := range commits {
53+
c.Assert(commit.ID().String(), Equals, expected[i])
54+
}
55+
}
56+
57+
func (s *CommitNodeSuite) TestWalkObject(c *C) {
58+
f := fixtures.ByTag("commit-graph").One()
59+
storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
60+
p := f.Packfile()
61+
defer p.Close()
62+
err := packfile.UpdateObjectStorage(storer, p)
63+
c.Assert(err, IsNil)
64+
65+
nodeIndex := NewObjectCommitNodeIndex(storer)
66+
testWalker(c, nodeIndex)
67+
}
68+
69+
func (s *CommitNodeSuite) TestWalkCommitGraph(c *C) {
70+
f := fixtures.ByTag("commit-graph").One()
71+
dotgit := f.DotGit()
72+
storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault())
73+
reader, err := mmap.Open(path.Join(dotgit.Root(), "objects", "info", "commit-graph"))
74+
c.Assert(err, IsNil)
75+
defer reader.Close()
76+
index, err := commitgraph.OpenFileIndex(reader)
77+
c.Assert(err, IsNil)
78+
79+
nodeIndex := NewGraphCommitNodeIndex(index, storer)
80+
testWalker(c, nodeIndex)
81+
}

0 commit comments

Comments
 (0)