From ec65d90feaf3172a8bd1bf51bb85e7bdaaf28a54 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 23 Apr 2019 19:11:43 +0200 Subject: plumbing: object, add APIs for traversing over commit graphs Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 310 +++++++++++++++++++++++++++++ plumbing/object/commitnode_test.go | 81 ++++++++ plumbing/object/commitnode_walker_ctime.go | 108 ++++++++++ 3 files changed, 499 insertions(+) create mode 100644 plumbing/object/commitnode.go create mode 100644 plumbing/object/commitnode_test.go create mode 100644 plumbing/object/commitnode_walker_ctime.go (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go new file mode 100644 index 0000000..a613eb4 --- /dev/null +++ b/plumbing/object/commitnode.go @@ -0,0 +1,310 @@ +package object + +import ( + "fmt" + "io" + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// CommitNode is generic interface encapsulating either Commit object or +// graphCommitNode object +type CommitNode interface { + ID() plumbing.Hash + Tree() (*Tree, error) + CommitTime() time.Time +} + +// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects +// and accessor methods for walking it as a directed graph +type CommitNodeIndex interface { + NumParents(node CommitNode) int + ParentNodes(node CommitNode) CommitNodeIter + ParentNode(node CommitNode, i int) (CommitNode, error) + ParentHashes(node CommitNode) []plumbing.Hash + + Get(hash plumbing.Hash) (CommitNode, error) + + // Commit returns the full commit object from the node + Commit(node CommitNode) (*Commit, error) +} + +// CommitNodeIter is a generic closable interface for iterating over commit nodes. +type CommitNodeIter interface { + Next() (CommitNode, error) + ForEach(func(CommitNode) error) error + Close() +} + +// graphCommitNode is a reduced representation of Commit as presented in the commit +// graph file (commitgraph.Node). It is merely useful as an optimization for walking +// the commit graphs. +// +// graphCommitNode implements the CommitNode interface. +type graphCommitNode struct { + // Hash for the Commit object + hash plumbing.Hash + // Index of the node in the commit graph file + index int + + node *commitgraph.Node + gci *graphCommitNodeIndex +} + +// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit +// graph files and the object store. +// +// graphCommitNodeIndex implements the CommitNodeIndex interface +type graphCommitNodeIndex struct { + commitGraph commitgraph.Index + s storer.EncodedObjectStorer +} + +// objectCommitNode is a representation of Commit as presented in the GIT object format. +// +// objectCommitNode implements the CommitNode interface. +type objectCommitNode struct { + commit *Commit +} + +// objectCommitNodeIndex is an index that can load CommitNode objects only from the +// object store. +// +// objectCommitNodeIndex implements the CommitNodeIndex interface +type objectCommitNodeIndex struct { + s storer.EncodedObjectStorer +} + +// ID returns the Commit object id referenced by the commit graph node. +func (c *graphCommitNode) ID() plumbing.Hash { + return c.hash +} + +// Tree returns the Tree referenced by the commit graph node. +func (c *graphCommitNode) Tree() (*Tree, error) { + return GetTree(c.gci.s, c.node.TreeHash) +} + +// CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. +func (c *graphCommitNode) CommitTime() time.Time { + return c.node.When +} + +func (c *graphCommitNode) String() string { + return fmt.Sprintf( + "%s %s\nDate: %s", + plumbing.CommitObject, c.ID(), + c.CommitTime().Format(DateFormat), + ) +} + +func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { + return &graphCommitNodeIndex{commitGraph, s} +} + +// NumParents returns the number of parents in a commit. +func (gci *graphCommitNodeIndex) NumParents(node CommitNode) int { + if cgn, ok := node.(*graphCommitNode); ok { + return len(cgn.node.ParentIndexes) + } + co := node.(*objectCommitNode) + return co.commit.NumParents() +} + +// ParentNodes return a CommitNodeIter for parents of specified node. +func (gci *graphCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter { + return newParentgraphCommitNodeIter(gci, node) +} + +// ParentNode returns the ith parent of a commit. +func (gci *graphCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) { + if cgn, ok := node.(*graphCommitNode); ok { + if len(cgn.node.ParentIndexes) == 0 || i >= len(cgn.node.ParentIndexes) { + return nil, ErrParentNotFound + } + + parent, err := gci.commitGraph.GetNodeByIndex(cgn.node.ParentIndexes[i]) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: cgn.node.ParentHashes[i], + index: cgn.node.ParentIndexes[i], + node: parent, + gci: gci, + }, nil + } + + co := node.(*objectCommitNode) + if len(co.commit.ParentHashes) == 0 || i >= len(co.commit.ParentHashes) { + return nil, ErrParentNotFound + } + + parentHash := co.commit.ParentHashes[i] + return gci.Get(parentHash) +} + +// ParentHashes returns hashes of the parent commits for a specified node +func (gci *graphCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash { + if cgn, ok := node.(*graphCommitNode); ok { + return cgn.node.ParentHashes + } + co := node.(*objectCommitNode) + return co.commit.ParentHashes +} + +// NodeFromHash looks up a commit node by it's object hash +func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + // Check the commit graph first + parentIndex, err := gci.commitGraph.GetIndexByHash(hash) + if err == nil { + parent, err := gci.commitGraph.GetNodeByIndex(parentIndex) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: hash, + index: parentIndex, + node: parent, + gci: gci, + }, nil + } + + // Fallback to loading full commit object + commit, err := GetCommit(gci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{commit: commit}, nil +} + +// Commit returns the full Commit object representing the commit graph node. +func (gci *graphCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { + if cgn, ok := node.(*graphCommitNode); ok { + return GetCommit(gci.s, cgn.ID()) + } + co := node.(*objectCommitNode) + return co.commit, nil +} + +// CommitTime returns the time when the commit was performed. +// +// CommitTime is present to fulfill the CommitNode interface. +func (c *objectCommitNode) CommitTime() time.Time { + return c.commit.Committer.When +} + +// ID returns the Commit object id referenced by the node. +func (c *objectCommitNode) ID() plumbing.Hash { + return c.commit.ID() +} + +// Tree returns the Tree referenced by the node. +func (c *objectCommitNode) Tree() (*Tree, error) { + return c.commit.Tree() +} + +func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { + return &objectCommitNodeIndex{s} +} + +// NumParents returns the number of parents in a commit. +func (oci *objectCommitNodeIndex) NumParents(node CommitNode) int { + co := node.(*objectCommitNode) + return co.commit.NumParents() +} + +// ParentNodes return a CommitNodeIter for parents of specified node. +func (oci *objectCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter { + return newParentgraphCommitNodeIter(oci, node) +} + +// ParentNode returns the ith parent of a commit. +func (oci *objectCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) { + co := node.(*objectCommitNode) + parent, err := co.commit.Parent(i) + if err != nil { + return nil, err + } + return &objectCommitNode{commit: parent}, nil +} + +// ParentHashes returns hashes of the parent commits for a specified node +func (oci *objectCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash { + co := node.(*objectCommitNode) + return co.commit.ParentHashes +} + +// NodeFromHash looks up a commit node by it's object hash +func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + commit, err := GetCommit(oci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{commit: commit}, nil +} + +// Commit returns the full Commit object representing the commit graph node. +func (oci *objectCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { + co := node.(*objectCommitNode) + return co.commit, nil +} + +// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. +type parentCommitNodeIter struct { + gci CommitNodeIndex + node CommitNode + i int +} + +func newParentgraphCommitNodeIter(gci CommitNodeIndex, node CommitNode) CommitNodeIter { + return &parentCommitNodeIter{gci, node, 0} +} + +// Next moves the iterator to the next commit and returns a pointer to it. If +// there are no more commits, it returns io.EOF. +func (iter *parentCommitNodeIter) Next() (CommitNode, error) { + obj, err := iter.gci.ParentNode(iter.node, iter.i) + if err == ErrParentNotFound { + return nil, io.EOF + } + if err == nil { + iter.i++ + } + + return obj, err +} + +// ForEach call the cb function for each commit contained on this iter until +// an error appends or the end of the iter is reached. If ErrStop is sent +// the iteration is stopped but no error is returned. The iterator is closed. +func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error { + for { + obj, err := iter.Next() + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + + if err := cb(obj); err != nil { + if err == storer.ErrStop { + return nil + } + + return err + } + } +} + +func (iter *parentCommitNodeIter) Close() { +} diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go new file mode 100644 index 0000000..8f59665 --- /dev/null +++ b/plumbing/object/commitnode_test.go @@ -0,0 +1,81 @@ +package object + +import ( + "path" + + "golang.org/x/exp/mmap" + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git-fixtures.v3" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/cache" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" + "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" + "gopkg.in/src-d/go-git.v4/storage/filesystem" +) + +type CommitNodeSuite struct { + fixtures.Suite +} + +var _ = Suite(&CommitNodeSuite{}) + +func testWalker(c *C, nodeIndex CommitNodeIndex) { + head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) + c.Assert(err, IsNil) + + iter := NewCommitNodeIterCTime( + head, + nodeIndex, + nil, + nil, + ) + + var commits []CommitNode + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c) + return nil + }) + + c.Assert(commits, HasLen, 9) + + expected := []string{ + "b9d69064b190e7aedccf84731ca1d917871f8a1c", + "6f6c5d2be7852c782be1dd13e36496dd7ad39560", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + "c0edf780dd0da6a65a7a49a86032fcf8a0c2d467", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "03d2c021ff68954cf3ef0a36825e194a4b98f981", + "ce275064ad67d51e99f026084e20827901a8361c", + "e713b52d7e13807e87a002e812041f248db3f643", + "347c91919944a68e9413581a1bc15519550a3afe", + } + for i, commit := range commits { + c.Assert(commit.ID().String(), Equals, expected[i]) + } +} + +func (s *CommitNodeSuite) TestWalkObject(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + err := packfile.UpdateObjectStorage(storer, p) + c.Assert(err, IsNil) + + nodeIndex := NewObjectCommitNodeIndex(storer) + testWalker(c, nodeIndex) +} + +func (s *CommitNodeSuite) TestWalkCommitGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + dotgit := f.DotGit() + storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()) + reader, err := mmap.Open(path.Join(dotgit.Root(), "objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + + nodeIndex := NewGraphCommitNodeIndex(index, storer) + testWalker(c, nodeIndex) +} diff --git a/plumbing/object/commitnode_walker_ctime.go b/plumbing/object/commitnode_walker_ctime.go new file mode 100644 index 0000000..86b6c57 --- /dev/null +++ b/plumbing/object/commitnode_walker_ctime.go @@ -0,0 +1,108 @@ +package object + +import ( + "io" + + "github.com/emirpasic/gods/trees/binaryheap" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +type commitNodeIteratorByCTime struct { + heap *binaryheap.Heap + seenExternal map[plumbing.Hash]bool + seen map[plumbing.Hash]bool + nodeIndex CommitNodeIndex +} + +// NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents while preserving Committer Time order. +// this appears to be the closest order to `git log` +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitNodeIterCTime( + c CommitNode, + nodeIndex CommitNodeIndex, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := make(map[plumbing.Hash]bool) + for _, h := range ignore { + seen[h] = true + } + + heap := binaryheap.NewWith(func(a, b interface{}) int { + if a.(CommitNode).CommitTime().Before(b.(CommitNode).CommitTime()) { + return 1 + } + return -1 + }) + + heap.Push(c) + + return &commitNodeIteratorByCTime{ + heap: heap, + seenExternal: seenExternal, + seen: seen, + nodeIndex: nodeIndex, + } +} + +func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { + var c CommitNode + for { + cIn, ok := w.heap.Pop() + if !ok { + return nil, io.EOF + } + c = cIn.(CommitNode) + cID := c.ID() + + if w.seen[cID] || w.seenExternal[cID] { + continue + } + + w.seen[cID] = true + + for i, h := range w.nodeIndex.ParentHashes(c) { + if w.seen[h] || w.seenExternal[h] { + continue + } + pc, err := w.nodeIndex.ParentNode(c, i) + if err != nil { + return nil, err + } + w.heap.Push(pc) + } + + return c, nil + } +} + +func (w *commitNodeIteratorByCTime) ForEach(cb func(CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +func (w *commitNodeIteratorByCTime) Close() {} -- cgit From 9eb627fb7b86b2941fb96020f152cb5fd2df2bd3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 11:13:53 +0200 Subject: Simplify the CommitNode API, make it look more like Commit Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 144 +++++++++++++---------------- plumbing/object/commitnode_test.go | 1 - plumbing/object/commitnode_walker_ctime.go | 7 +- 3 files changed, 65 insertions(+), 87 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go index a613eb4..a7af4a9 100644 --- a/plumbing/object/commitnode.go +++ b/plumbing/object/commitnode.go @@ -16,18 +16,17 @@ type CommitNode interface { ID() plumbing.Hash Tree() (*Tree, error) CommitTime() time.Time + NumParents() int + ParentNodes() CommitNodeIter + ParentNode(i int) (CommitNode, error) + ParentHashes() []plumbing.Hash } // CommitNodeIndex is generic interface encapsulating an index of CommitNode objects // and accessor methods for walking it as a directed graph type CommitNodeIndex interface { - NumParents(node CommitNode) int - ParentNodes(node CommitNode) CommitNodeIter - ParentNode(node CommitNode, i int) (CommitNode, error) - ParentHashes(node CommitNode) []plumbing.Hash - + // Get returns a commit node from a commit hash Get(hash plumbing.Hash) (CommitNode, error) - // Commit returns the full commit object from the node Commit(node CommitNode) (*Commit, error) } @@ -67,7 +66,8 @@ type graphCommitNodeIndex struct { // // objectCommitNode implements the CommitNode interface. type objectCommitNode struct { - commit *Commit + nodeIndex CommitNodeIndex + commit *Commit } // objectCommitNodeIndex is an index that can load CommitNode objects only from the @@ -93,68 +93,50 @@ func (c *graphCommitNode) CommitTime() time.Time { return c.node.When } -func (c *graphCommitNode) String() string { - return fmt.Sprintf( - "%s %s\nDate: %s", - plumbing.CommitObject, c.ID(), - c.CommitTime().Format(DateFormat), - ) -} - -func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { - return &graphCommitNodeIndex{commitGraph, s} -} - // NumParents returns the number of parents in a commit. -func (gci *graphCommitNodeIndex) NumParents(node CommitNode) int { - if cgn, ok := node.(*graphCommitNode); ok { - return len(cgn.node.ParentIndexes) - } - co := node.(*objectCommitNode) - return co.commit.NumParents() +func (c *graphCommitNode) NumParents() int { + return len(c.node.ParentIndexes) } // ParentNodes return a CommitNodeIter for parents of specified node. -func (gci *graphCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter { - return newParentgraphCommitNodeIter(gci, node) +func (c *graphCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) } // ParentNode returns the ith parent of a commit. -func (gci *graphCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) { - if cgn, ok := node.(*graphCommitNode); ok { - if len(cgn.node.ParentIndexes) == 0 || i >= len(cgn.node.ParentIndexes) { - return nil, ErrParentNotFound - } - - parent, err := gci.commitGraph.GetNodeByIndex(cgn.node.ParentIndexes[i]) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: cgn.node.ParentHashes[i], - index: cgn.node.ParentIndexes[i], - node: parent, - gci: gci, - }, nil +func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.node.ParentIndexes) { + return nil, ErrParentNotFound } - co := node.(*objectCommitNode) - if len(co.commit.ParentHashes) == 0 || i >= len(co.commit.ParentHashes) { - return nil, ErrParentNotFound + parent, err := c.gci.commitGraph.GetNodeByIndex(c.node.ParentIndexes[i]) + if err != nil { + return nil, err } - parentHash := co.commit.ParentHashes[i] - return gci.Get(parentHash) + return &graphCommitNode{ + hash: c.node.ParentHashes[i], + index: c.node.ParentIndexes[i], + node: parent, + gci: c.gci, + }, nil } // ParentHashes returns hashes of the parent commits for a specified node -func (gci *graphCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash { - if cgn, ok := node.(*graphCommitNode); ok { - return cgn.node.ParentHashes - } - co := node.(*objectCommitNode) - return co.commit.ParentHashes +func (c *graphCommitNode) ParentHashes() []plumbing.Hash { + return c.node.ParentHashes +} + +func (c *graphCommitNode) String() string { + return fmt.Sprintf( + "%s %s\nDate: %s", + plumbing.CommitObject, c.ID(), + c.CommitTime().Format(DateFormat), + ) +} + +func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { + return &graphCommitNodeIndex{commitGraph, s} } // NodeFromHash looks up a commit node by it's object hash @@ -181,7 +163,10 @@ func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { return nil, err } - return &objectCommitNode{commit: commit}, nil + return &objectCommitNode{ + nodeIndex: gci, + commit: commit, + }, nil } // Commit returns the full Commit object representing the commit graph node. @@ -194,8 +179,6 @@ func (gci *graphCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { } // CommitTime returns the time when the commit was performed. -// -// CommitTime is present to fulfill the CommitNode interface. func (c *objectCommitNode) CommitTime() time.Time { return c.commit.Committer.When } @@ -210,35 +193,32 @@ func (c *objectCommitNode) Tree() (*Tree, error) { return c.commit.Tree() } -func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { - return &objectCommitNodeIndex{s} -} - // NumParents returns the number of parents in a commit. -func (oci *objectCommitNodeIndex) NumParents(node CommitNode) int { - co := node.(*objectCommitNode) - return co.commit.NumParents() +func (c *objectCommitNode) NumParents() int { + return c.commit.NumParents() } // ParentNodes return a CommitNodeIter for parents of specified node. -func (oci *objectCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter { - return newParentgraphCommitNodeIter(oci, node) +func (c *objectCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) } // ParentNode returns the ith parent of a commit. -func (oci *objectCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) { - co := node.(*objectCommitNode) - parent, err := co.commit.Parent(i) - if err != nil { - return nil, err +func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commit.ParentHashes) { + return nil, ErrParentNotFound } - return &objectCommitNode{commit: parent}, nil + + return c.nodeIndex.Get(c.commit.ParentHashes[i]) } // ParentHashes returns hashes of the parent commits for a specified node -func (oci *objectCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash { - co := node.(*objectCommitNode) - return co.commit.ParentHashes +func (c *objectCommitNode) ParentHashes() []plumbing.Hash { + return c.commit.ParentHashes +} + +func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { + return &objectCommitNodeIndex{s} } // NodeFromHash looks up a commit node by it's object hash @@ -248,7 +228,10 @@ func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { return nil, err } - return &objectCommitNode{commit: commit}, nil + return &objectCommitNode{ + nodeIndex: oci, + commit: commit, + }, nil } // Commit returns the full Commit object representing the commit graph node. @@ -259,19 +242,18 @@ func (oci *objectCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { // parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. type parentCommitNodeIter struct { - gci CommitNodeIndex node CommitNode i int } -func newParentgraphCommitNodeIter(gci CommitNodeIndex, node CommitNode) CommitNodeIter { - return &parentCommitNodeIter{gci, node, 0} +func newParentgraphCommitNodeIter(node CommitNode) CommitNodeIter { + return &parentCommitNodeIter{node, 0} } // Next moves the iterator to the next commit and returns a pointer to it. If // there are no more commits, it returns io.EOF. func (iter *parentCommitNodeIter) Next() (CommitNode, error) { - obj, err := iter.gci.ParentNode(iter.node, iter.i) + obj, err := iter.node.ParentNode(iter.i) if err == ErrParentNotFound { return nil, io.EOF } diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go index 8f59665..883befc 100644 --- a/plumbing/object/commitnode_test.go +++ b/plumbing/object/commitnode_test.go @@ -25,7 +25,6 @@ func testWalker(c *C, nodeIndex CommitNodeIndex) { iter := NewCommitNodeIterCTime( head, - nodeIndex, nil, nil, ) diff --git a/plumbing/object/commitnode_walker_ctime.go b/plumbing/object/commitnode_walker_ctime.go index 86b6c57..e55b4ad 100644 --- a/plumbing/object/commitnode_walker_ctime.go +++ b/plumbing/object/commitnode_walker_ctime.go @@ -13,7 +13,6 @@ type commitNodeIteratorByCTime struct { heap *binaryheap.Heap seenExternal map[plumbing.Hash]bool seen map[plumbing.Hash]bool - nodeIndex CommitNodeIndex } // NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, @@ -26,7 +25,6 @@ type commitNodeIteratorByCTime struct { // commits from being iterated. func NewCommitNodeIterCTime( c CommitNode, - nodeIndex CommitNodeIndex, seenExternal map[plumbing.Hash]bool, ignore []plumbing.Hash, ) CommitNodeIter { @@ -48,7 +46,6 @@ func NewCommitNodeIterCTime( heap: heap, seenExternal: seenExternal, seen: seen, - nodeIndex: nodeIndex, } } @@ -68,11 +65,11 @@ func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { w.seen[cID] = true - for i, h := range w.nodeIndex.ParentHashes(c) { + for i, h := range c.ParentHashes() { if w.seen[h] || w.seenExternal[h] { continue } - pc, err := w.nodeIndex.ParentNode(c, i) + pc, err := c.ParentNode(i) if err != nil { return nil, err } -- cgit From 58c731411944090126f86208bcf0419d6ba84122 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 11:31:59 +0200 Subject: Move Commit() to CommitNode API Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go index a7af4a9..0717a65 100644 --- a/plumbing/object/commitnode.go +++ b/plumbing/object/commitnode.go @@ -13,13 +13,22 @@ import ( // CommitNode is generic interface encapsulating either Commit object or // graphCommitNode object type CommitNode interface { + // ID returns the Commit object id referenced by the commit graph node. ID() plumbing.Hash + // Tree returns the Tree referenced by the commit graph node. Tree() (*Tree, error) + // CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. CommitTime() time.Time + // NumParents returns the number of parents in a commit. NumParents() int + // ParentNodes return a CommitNodeIter for parents of specified node. ParentNodes() CommitNodeIter + // ParentNode returns the ith parent of a commit. ParentNode(i int) (CommitNode, error) + // ParentHashes returns hashes of the parent commits for a specified node ParentHashes() []plumbing.Hash + // Commit returns the full commit object from the node + Commit() (*Commit, error) } // CommitNodeIndex is generic interface encapsulating an index of CommitNode objects @@ -27,8 +36,6 @@ type CommitNode interface { type CommitNodeIndex interface { // Get returns a commit node from a commit hash Get(hash plumbing.Hash) (CommitNode, error) - // Commit returns the full commit object from the node - Commit(node CommitNode) (*Commit, error) } // CommitNodeIter is a generic closable interface for iterating over commit nodes. @@ -127,6 +134,11 @@ func (c *graphCommitNode) ParentHashes() []plumbing.Hash { return c.node.ParentHashes } +// Commit returns the full Commit object representing the commit graph node. +func (c *graphCommitNode) Commit() (*Commit, error) { + return GetCommit(c.gci.s, c.hash) +} + func (c *graphCommitNode) String() string { return fmt.Sprintf( "%s %s\nDate: %s", @@ -169,15 +181,6 @@ func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { }, nil } -// Commit returns the full Commit object representing the commit graph node. -func (gci *graphCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { - if cgn, ok := node.(*graphCommitNode); ok { - return GetCommit(gci.s, cgn.ID()) - } - co := node.(*objectCommitNode) - return co.commit, nil -} - // CommitTime returns the time when the commit was performed. func (c *objectCommitNode) CommitTime() time.Time { return c.commit.Committer.When @@ -217,6 +220,11 @@ func (c *objectCommitNode) ParentHashes() []plumbing.Hash { return c.commit.ParentHashes } +// Commit returns the full Commit object representing the commit graph node. +func (c *objectCommitNode) Commit() (*Commit, error) { + return c.commit, nil +} + func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { return &objectCommitNodeIndex{s} } -- cgit From f4c1a9140f8b2700d9910d35cbab62b2de1fc857 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 11:52:12 +0200 Subject: Code cleanup, split into more files for clarity Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 205 ----------------------------------- plumbing/object/commitnode_graph.go | 121 +++++++++++++++++++++ plumbing/object/commitnode_object.go | 79 ++++++++++++++ 3 files changed, 200 insertions(+), 205 deletions(-) create mode 100644 plumbing/object/commitnode_graph.go create mode 100644 plumbing/object/commitnode_object.go (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go index 0717a65..62c0aef 100644 --- a/plumbing/object/commitnode.go +++ b/plumbing/object/commitnode.go @@ -1,12 +1,10 @@ package object import ( - "fmt" "io" "time" "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" "gopkg.in/src-d/go-git.v4/plumbing/storer" ) @@ -45,209 +43,6 @@ type CommitNodeIter interface { Close() } -// graphCommitNode is a reduced representation of Commit as presented in the commit -// graph file (commitgraph.Node). It is merely useful as an optimization for walking -// the commit graphs. -// -// graphCommitNode implements the CommitNode interface. -type graphCommitNode struct { - // Hash for the Commit object - hash plumbing.Hash - // Index of the node in the commit graph file - index int - - node *commitgraph.Node - gci *graphCommitNodeIndex -} - -// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit -// graph files and the object store. -// -// graphCommitNodeIndex implements the CommitNodeIndex interface -type graphCommitNodeIndex struct { - commitGraph commitgraph.Index - s storer.EncodedObjectStorer -} - -// objectCommitNode is a representation of Commit as presented in the GIT object format. -// -// objectCommitNode implements the CommitNode interface. -type objectCommitNode struct { - nodeIndex CommitNodeIndex - commit *Commit -} - -// objectCommitNodeIndex is an index that can load CommitNode objects only from the -// object store. -// -// objectCommitNodeIndex implements the CommitNodeIndex interface -type objectCommitNodeIndex struct { - s storer.EncodedObjectStorer -} - -// ID returns the Commit object id referenced by the commit graph node. -func (c *graphCommitNode) ID() plumbing.Hash { - return c.hash -} - -// Tree returns the Tree referenced by the commit graph node. -func (c *graphCommitNode) Tree() (*Tree, error) { - return GetTree(c.gci.s, c.node.TreeHash) -} - -// CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. -func (c *graphCommitNode) CommitTime() time.Time { - return c.node.When -} - -// NumParents returns the number of parents in a commit. -func (c *graphCommitNode) NumParents() int { - return len(c.node.ParentIndexes) -} - -// ParentNodes return a CommitNodeIter for parents of specified node. -func (c *graphCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -// ParentNode returns the ith parent of a commit. -func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.node.ParentIndexes) { - return nil, ErrParentNotFound - } - - parent, err := c.gci.commitGraph.GetNodeByIndex(c.node.ParentIndexes[i]) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: c.node.ParentHashes[i], - index: c.node.ParentIndexes[i], - node: parent, - gci: c.gci, - }, nil -} - -// ParentHashes returns hashes of the parent commits for a specified node -func (c *graphCommitNode) ParentHashes() []plumbing.Hash { - return c.node.ParentHashes -} - -// Commit returns the full Commit object representing the commit graph node. -func (c *graphCommitNode) Commit() (*Commit, error) { - return GetCommit(c.gci.s, c.hash) -} - -func (c *graphCommitNode) String() string { - return fmt.Sprintf( - "%s %s\nDate: %s", - plumbing.CommitObject, c.ID(), - c.CommitTime().Format(DateFormat), - ) -} - -func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { - return &graphCommitNodeIndex{commitGraph, s} -} - -// NodeFromHash looks up a commit node by it's object hash -func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - // Check the commit graph first - parentIndex, err := gci.commitGraph.GetIndexByHash(hash) - if err == nil { - parent, err := gci.commitGraph.GetNodeByIndex(parentIndex) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: hash, - index: parentIndex, - node: parent, - gci: gci, - }, nil - } - - // Fallback to loading full commit object - commit, err := GetCommit(gci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: gci, - commit: commit, - }, nil -} - -// CommitTime returns the time when the commit was performed. -func (c *objectCommitNode) CommitTime() time.Time { - return c.commit.Committer.When -} - -// ID returns the Commit object id referenced by the node. -func (c *objectCommitNode) ID() plumbing.Hash { - return c.commit.ID() -} - -// Tree returns the Tree referenced by the node. -func (c *objectCommitNode) Tree() (*Tree, error) { - return c.commit.Tree() -} - -// NumParents returns the number of parents in a commit. -func (c *objectCommitNode) NumParents() int { - return c.commit.NumParents() -} - -// ParentNodes return a CommitNodeIter for parents of specified node. -func (c *objectCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -// ParentNode returns the ith parent of a commit. -func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.commit.ParentHashes) { - return nil, ErrParentNotFound - } - - return c.nodeIndex.Get(c.commit.ParentHashes[i]) -} - -// ParentHashes returns hashes of the parent commits for a specified node -func (c *objectCommitNode) ParentHashes() []plumbing.Hash { - return c.commit.ParentHashes -} - -// Commit returns the full Commit object representing the commit graph node. -func (c *objectCommitNode) Commit() (*Commit, error) { - return c.commit, nil -} - -func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { - return &objectCommitNodeIndex{s} -} - -// NodeFromHash looks up a commit node by it's object hash -func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - commit, err := GetCommit(oci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: oci, - commit: commit, - }, nil -} - -// Commit returns the full Commit object representing the commit graph node. -func (oci *objectCommitNodeIndex) Commit(node CommitNode) (*Commit, error) { - co := node.(*objectCommitNode) - return co.commit, nil -} - // parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. type parentCommitNodeIter struct { node CommitNode diff --git a/plumbing/object/commitnode_graph.go b/plumbing/object/commitnode_graph.go new file mode 100644 index 0000000..c57a258 --- /dev/null +++ b/plumbing/object/commitnode_graph.go @@ -0,0 +1,121 @@ +package object + +import ( + "fmt" + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// graphCommitNode is a reduced representation of Commit as presented in the commit +// graph file (commitgraph.Node). It is merely useful as an optimization for walking +// the commit graphs. +// +// graphCommitNode implements the CommitNode interface. +type graphCommitNode struct { + // Hash for the Commit object + hash plumbing.Hash + // Index of the node in the commit graph file + index int + + node *commitgraph.Node + gci *graphCommitNodeIndex +} + +// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit +// graph files and the object store. +// +// graphCommitNodeIndex implements the CommitNodeIndex interface +type graphCommitNodeIndex struct { + commitGraph commitgraph.Index + s storer.EncodedObjectStorer +} + +func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { + return &graphCommitNodeIndex{commitGraph, s} +} + +func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + // Check the commit graph first + parentIndex, err := gci.commitGraph.GetIndexByHash(hash) + if err == nil { + parent, err := gci.commitGraph.GetNodeByIndex(parentIndex) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: hash, + index: parentIndex, + node: parent, + gci: gci, + }, nil + } + + // Fallback to loading full commit object + commit, err := GetCommit(gci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: gci, + commit: commit, + }, nil +} + +func (c *graphCommitNode) ID() plumbing.Hash { + return c.hash +} + +func (c *graphCommitNode) Tree() (*Tree, error) { + return GetTree(c.gci.s, c.node.TreeHash) +} + +func (c *graphCommitNode) CommitTime() time.Time { + return c.node.When +} + +func (c *graphCommitNode) NumParents() int { + return len(c.node.ParentIndexes) +} + +func (c *graphCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.node.ParentIndexes) { + return nil, ErrParentNotFound + } + + parent, err := c.gci.commitGraph.GetNodeByIndex(c.node.ParentIndexes[i]) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: c.node.ParentHashes[i], + index: c.node.ParentIndexes[i], + node: parent, + gci: c.gci, + }, nil +} + +func (c *graphCommitNode) ParentHashes() []plumbing.Hash { + return c.node.ParentHashes +} + +func (c *graphCommitNode) Commit() (*Commit, error) { + return GetCommit(c.gci.s, c.hash) +} + +func (c *graphCommitNode) String() string { + return fmt.Sprintf( + "%s %s\nDate: %s", + plumbing.CommitObject, c.ID(), + c.CommitTime().Format(DateFormat), + ) +} diff --git a/plumbing/object/commitnode_object.go b/plumbing/object/commitnode_object.go new file mode 100644 index 0000000..08d8c0f --- /dev/null +++ b/plumbing/object/commitnode_object.go @@ -0,0 +1,79 @@ +package object + +import ( + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// objectCommitNode is a representation of Commit as presented in the GIT object format. +// +// objectCommitNode implements the CommitNode interface. +type objectCommitNode struct { + nodeIndex CommitNodeIndex + commit *Commit +} + +func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { + return &objectCommitNodeIndex{s} +} + +func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + commit, err := GetCommit(oci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: oci, + commit: commit, + }, nil +} + +// objectCommitNodeIndex is an index that can load CommitNode objects only from the +// object store. +// +// objectCommitNodeIndex implements the CommitNodeIndex interface +type objectCommitNodeIndex struct { + s storer.EncodedObjectStorer +} + +func (c *objectCommitNode) CommitTime() time.Time { + return c.commit.Committer.When +} + +func (c *objectCommitNode) ID() plumbing.Hash { + return c.commit.ID() +} + +func (c *objectCommitNode) Tree() (*Tree, error) { + return c.commit.Tree() +} + +func (c *objectCommitNode) NumParents() int { + return c.commit.NumParents() +} + +func (c *objectCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commit.ParentHashes) { + return nil, ErrParentNotFound + } + + // Note: It's necessary to go through CommitNodeIndex here to ensure + // that if the commit-graph file covers only part of the history we + // start using it when that part is reached. + return c.nodeIndex.Get(c.commit.ParentHashes[i]) +} + +func (c *objectCommitNode) ParentHashes() []plumbing.Hash { + return c.commit.ParentHashes +} + +func (c *objectCommitNode) Commit() (*Commit, error) { + return c.commit, nil +} -- cgit From 55dd4be4dfe8af030b5652782af2d4c37f51197f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 11:58:17 +0200 Subject: Add test for CommitNode.ParentNodes() Signed-off-by: Filip Navara --- plumbing/object/commitnode_test.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go index 883befc..1e5f2d9 100644 --- a/plumbing/object/commitnode_test.go +++ b/plumbing/object/commitnode_test.go @@ -53,7 +53,29 @@ func testWalker(c *C, nodeIndex CommitNodeIndex) { } } -func (s *CommitNodeSuite) TestWalkObject(c *C) { +func testParents(c *C, nodeIndex CommitNodeIndex) { + merge3, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + + var parents []CommitNode + merge3.ParentNodes().ForEach(func(c CommitNode) error { + parents = append(parents, c) + return nil + }) + + c.Assert(parents, HasLen, 3) + + expected := []string{ + "ce275064ad67d51e99f026084e20827901a8361c", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + } + for i, parent := range parents { + c.Assert(parent.ID().String(), Equals, expected[i]) + } +} + +func (s *CommitNodeSuite) TestObjectGraph(c *C) { f := fixtures.ByTag("commit-graph").One() storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) p := f.Packfile() @@ -63,9 +85,10 @@ func (s *CommitNodeSuite) TestWalkObject(c *C) { nodeIndex := NewObjectCommitNodeIndex(storer) testWalker(c, nodeIndex) + testParents(c, nodeIndex) } -func (s *CommitNodeSuite) TestWalkCommitGraph(c *C) { +func (s *CommitNodeSuite) TestCommitGraph(c *C) { f := fixtures.ByTag("commit-graph").One() dotgit := f.DotGit() storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()) @@ -77,4 +100,5 @@ func (s *CommitNodeSuite) TestWalkCommitGraph(c *C) { nodeIndex := NewGraphCommitNodeIndex(index, storer) testWalker(c, nodeIndex) + testParents(c, nodeIndex) } -- cgit From b48e4867d1aba5235132533006c8ed8be40344d8 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 12:14:53 +0200 Subject: Update comments Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 5 ++--- plumbing/object/commitnode_graph.go | 2 ++ plumbing/object/commitnode_object.go | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go index 62c0aef..22927f4 100644 --- a/plumbing/object/commitnode.go +++ b/plumbing/object/commitnode.go @@ -8,8 +8,8 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/storer" ) -// CommitNode is generic interface encapsulating either Commit object or -// graphCommitNode object +// CommitNode is generic interface encapsulating a lightweight commit object retrieved +// from CommitNodeIndex type CommitNode interface { // ID returns the Commit object id referenced by the commit graph node. ID() plumbing.Hash @@ -30,7 +30,6 @@ type CommitNode interface { } // CommitNodeIndex is generic interface encapsulating an index of CommitNode objects -// and accessor methods for walking it as a directed graph type CommitNodeIndex interface { // Get returns a commit node from a commit hash Get(hash plumbing.Hash) (CommitNode, error) diff --git a/plumbing/object/commitnode_graph.go b/plumbing/object/commitnode_graph.go index c57a258..9fc28a2 100644 --- a/plumbing/object/commitnode_graph.go +++ b/plumbing/object/commitnode_graph.go @@ -33,6 +33,8 @@ type graphCommitNodeIndex struct { s storer.EncodedObjectStorer } +// NewGraphCommitNodeIndex returns CommitNodeIndex implementation that uses commit-graph +// files as backing storage and falls back to object storage when necessary func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { return &graphCommitNodeIndex{commitGraph, s} } diff --git a/plumbing/object/commitnode_object.go b/plumbing/object/commitnode_object.go index 08d8c0f..52316f8 100644 --- a/plumbing/object/commitnode_object.go +++ b/plumbing/object/commitnode_object.go @@ -15,6 +15,8 @@ type objectCommitNode struct { commit *Commit } +// NewObjectCommitNodeIndex returns CommitNodeIndex implementation that uses +// only object storage to load the nodes func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { return &objectCommitNodeIndex{s} } -- cgit From cc48439674365fc8f216dd7df361872046f52f04 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 12:48:30 +0200 Subject: Add test for traversal on mixed object and commit-graph Signed-off-by: Filip Navara --- plumbing/object/commitnode_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go index 1e5f2d9..df307e3 100644 --- a/plumbing/object/commitnode_test.go +++ b/plumbing/object/commitnode_test.go @@ -102,3 +102,33 @@ func (s *CommitNodeSuite) TestCommitGraph(c *C) { testWalker(c, nodeIndex) testParents(c, nodeIndex) } + +func (s *CommitNodeSuite) TestMixedGraph(c *C) { + // Unpack the original repository with pack file + f := fixtures.ByTag("commit-graph").One() + dotgit := f.DotGit() + storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + err := packfile.UpdateObjectStorage(storer, p) + c.Assert(err, IsNil) + + // Take the commit-graph file and copy it to memory index without the last commit + reader, err := mmap.Open(path.Join(dotgit.Root(), "objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + fileIndex, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + memoryIndex := commitgraph.NewMemoryIndex() + for i, hash := range fileIndex.Hashes() { + if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { + node, err := fileIndex.GetNodeByIndex(i) + c.Assert(err, IsNil) + memoryIndex.Add(hash, node) + } + } + + nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) +} -- cgit From 34cb7a3aededd002881504dd729527628e923fc6 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 24 Apr 2019 13:14:17 +0200 Subject: Add test for CommitNode.Commit() and CommitNode.Tree() Signed-off-by: Filip Navara --- plumbing/object/commitnode_test.go | 44 +++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go index df307e3..3a70db3 100644 --- a/plumbing/object/commitnode_test.go +++ b/plumbing/object/commitnode_test.go @@ -3,7 +3,6 @@ package object import ( "path" - "golang.org/x/exp/mmap" . "gopkg.in/check.v1" "gopkg.in/src-d/go-git-fixtures.v3" "gopkg.in/src-d/go-git.v4/plumbing" @@ -19,6 +18,14 @@ type CommitNodeSuite struct { var _ = Suite(&CommitNodeSuite{}) +func unpackRepositry(f *fixtures.Fixture) *filesystem.Storage { + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + packfile.UpdateObjectStorage(storer, p) + return storer +} + func testWalker(c *C, nodeIndex CommitNodeIndex) { head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) c.Assert(err, IsNil) @@ -75,24 +82,31 @@ func testParents(c *C, nodeIndex CommitNodeIndex) { } } +func testCommitAndTree(c *C, nodeIndex CommitNodeIndex) { + merge3node, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + merge3commit, err := merge3node.Commit() + c.Assert(err, IsNil) + c.Assert(merge3node.ID().String(), Equals, merge3commit.ID().String()) + tree, err := merge3node.Tree() + c.Assert(err, IsNil) + c.Assert(tree.ID().String(), Equals, merge3commit.TreeHash.String()) +} + func (s *CommitNodeSuite) TestObjectGraph(c *C) { f := fixtures.ByTag("commit-graph").One() - storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) - p := f.Packfile() - defer p.Close() - err := packfile.UpdateObjectStorage(storer, p) - c.Assert(err, IsNil) + storer := unpackRepositry(f) nodeIndex := NewObjectCommitNodeIndex(storer) testWalker(c, nodeIndex) testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) } func (s *CommitNodeSuite) TestCommitGraph(c *C) { f := fixtures.ByTag("commit-graph").One() - dotgit := f.DotGit() - storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()) - reader, err := mmap.Open(path.Join(dotgit.Root(), "objects", "info", "commit-graph")) + storer := unpackRepositry(f) + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) c.Assert(err, IsNil) defer reader.Close() index, err := commitgraph.OpenFileIndex(reader) @@ -101,20 +115,15 @@ func (s *CommitNodeSuite) TestCommitGraph(c *C) { nodeIndex := NewGraphCommitNodeIndex(index, storer) testWalker(c, nodeIndex) testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) } func (s *CommitNodeSuite) TestMixedGraph(c *C) { - // Unpack the original repository with pack file f := fixtures.ByTag("commit-graph").One() - dotgit := f.DotGit() - storer := filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()) - p := f.Packfile() - defer p.Close() - err := packfile.UpdateObjectStorage(storer, p) - c.Assert(err, IsNil) + storer := unpackRepositry(f) // Take the commit-graph file and copy it to memory index without the last commit - reader, err := mmap.Open(path.Join(dotgit.Root(), "objects", "info", "commit-graph")) + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) c.Assert(err, IsNil) defer reader.Close() fileIndex, err := commitgraph.OpenFileIndex(reader) @@ -131,4 +140,5 @@ func (s *CommitNodeSuite) TestMixedGraph(c *C) { nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) testWalker(c, nodeIndex) testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) } -- cgit From a47126b1ae5020dbdd268b304fef45a59d63d99b Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 26 Apr 2019 10:11:07 +0200 Subject: pluming: object, adjust to new API names in format/commitgraph Signed-off-by: Filip Navara --- plumbing/object/commitnode_graph.go | 34 +++++++++++++++++----------------- plumbing/object/commitnode_test.go | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode_graph.go b/plumbing/object/commitnode_graph.go index 9fc28a2..b2a6f57 100644 --- a/plumbing/object/commitnode_graph.go +++ b/plumbing/object/commitnode_graph.go @@ -20,8 +20,8 @@ type graphCommitNode struct { // Index of the node in the commit graph file index int - node *commitgraph.Node - gci *graphCommitNodeIndex + commitData *commitgraph.CommitData + gci *graphCommitNodeIndex } // graphCommitNodeIndex is an index that can load CommitNode objects from both the commit @@ -43,16 +43,16 @@ func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { // Check the commit graph first parentIndex, err := gci.commitGraph.GetIndexByHash(hash) if err == nil { - parent, err := gci.commitGraph.GetNodeByIndex(parentIndex) + parent, err := gci.commitGraph.GetCommitDataByIndex(parentIndex) if err != nil { return nil, err } return &graphCommitNode{ - hash: hash, - index: parentIndex, - node: parent, - gci: gci, + hash: hash, + index: parentIndex, + commitData: parent, + gci: gci, }, nil } @@ -73,15 +73,15 @@ func (c *graphCommitNode) ID() plumbing.Hash { } func (c *graphCommitNode) Tree() (*Tree, error) { - return GetTree(c.gci.s, c.node.TreeHash) + return GetTree(c.gci.s, c.commitData.TreeHash) } func (c *graphCommitNode) CommitTime() time.Time { - return c.node.When + return c.commitData.When } func (c *graphCommitNode) NumParents() int { - return len(c.node.ParentIndexes) + return len(c.commitData.ParentIndexes) } func (c *graphCommitNode) ParentNodes() CommitNodeIter { @@ -89,25 +89,25 @@ func (c *graphCommitNode) ParentNodes() CommitNodeIter { } func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.node.ParentIndexes) { + if i < 0 || i >= len(c.commitData.ParentIndexes) { return nil, ErrParentNotFound } - parent, err := c.gci.commitGraph.GetNodeByIndex(c.node.ParentIndexes[i]) + parent, err := c.gci.commitGraph.GetCommitDataByIndex(c.commitData.ParentIndexes[i]) if err != nil { return nil, err } return &graphCommitNode{ - hash: c.node.ParentHashes[i], - index: c.node.ParentIndexes[i], - node: parent, - gci: c.gci, + hash: c.commitData.ParentHashes[i], + index: c.commitData.ParentIndexes[i], + commitData: parent, + gci: c.gci, }, nil } func (c *graphCommitNode) ParentHashes() []plumbing.Hash { - return c.node.ParentHashes + return c.commitData.ParentHashes } func (c *graphCommitNode) Commit() (*Commit, error) { diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go index 3a70db3..a295b8b 100644 --- a/plumbing/object/commitnode_test.go +++ b/plumbing/object/commitnode_test.go @@ -4,7 +4,7 @@ import ( "path" . "gopkg.in/check.v1" - "gopkg.in/src-d/go-git-fixtures.v3" + fixtures "gopkg.in/src-d/go-git-fixtures.v3" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/cache" "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" @@ -131,7 +131,7 @@ func (s *CommitNodeSuite) TestMixedGraph(c *C) { memoryIndex := commitgraph.NewMemoryIndex() for i, hash := range fileIndex.Hashes() { if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { - node, err := fileIndex.GetNodeByIndex(i) + node, err := fileIndex.GetCommitDataByIndex(i) c.Assert(err, IsNil) memoryIndex.Add(hash, node) } -- cgit From 5f53b23103a04f97220f325772646b603c4dc25f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 29 Apr 2019 13:23:06 +0200 Subject: Expose Generation property on CommitNode Signed-off-by: Filip Navara --- plumbing/object/commitnode.go | 3 +++ plumbing/object/commitnode_graph.go | 7 +++++++ plumbing/object/commitnode_object.go | 8 ++++++++ 3 files changed, 18 insertions(+) (limited to 'plumbing/object') diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go index 22927f4..ce25487 100644 --- a/plumbing/object/commitnode.go +++ b/plumbing/object/commitnode.go @@ -25,6 +25,9 @@ type CommitNode interface { ParentNode(i int) (CommitNode, error) // ParentHashes returns hashes of the parent commits for a specified node ParentHashes() []plumbing.Hash + // Generation returns the generation of the commit for reachability analysis. + // Objects with newer generation are not reachable from objects of older generation. + Generation() uint64 // Commit returns the full commit object from the node Commit() (*Commit, error) } diff --git a/plumbing/object/commitnode_graph.go b/plumbing/object/commitnode_graph.go index b2a6f57..ac790ab 100644 --- a/plumbing/object/commitnode_graph.go +++ b/plumbing/object/commitnode_graph.go @@ -110,6 +110,13 @@ func (c *graphCommitNode) ParentHashes() []plumbing.Hash { return c.commitData.ParentHashes } +func (c *graphCommitNode) Generation() uint64 { + // If the commit-graph file was generated with older Git version that + // set the generation to zero for every commit the generation assumption + // is still valid. It is just less useful. + return uint64(c.commitData.Generation) +} + func (c *graphCommitNode) Commit() (*Commit, error) { return GetCommit(c.gci.s, c.hash) } diff --git a/plumbing/object/commitnode_object.go b/plumbing/object/commitnode_object.go index 52316f8..9ac42d2 100644 --- a/plumbing/object/commitnode_object.go +++ b/plumbing/object/commitnode_object.go @@ -1,6 +1,7 @@ package object import ( + "math" "time" "gopkg.in/src-d/go-git.v4/plumbing" @@ -76,6 +77,13 @@ func (c *objectCommitNode) ParentHashes() []plumbing.Hash { return c.commit.ParentHashes } +func (c *objectCommitNode) Generation() uint64 { + // Commit nodes representing objects outside of the commit graph can never + // be reached by objects from the commit-graph thus we return the highest + // possible value. + return math.MaxUint64 +} + func (c *objectCommitNode) Commit() (*Commit, error) { return c.commit, nil } -- cgit From a661bca784d305e9df581302b725dc20fad8b995 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 3 May 2019 11:52:53 +0200 Subject: Move CommitNode/CommitNodeIndex into separate object/commitgraph package Signed-off-by: Filip Navara --- plumbing/object/commitgraph/commitnode.go | 98 ++++++++++++++ plumbing/object/commitgraph/commitnode_graph.go | 131 +++++++++++++++++++ plumbing/object/commitgraph/commitnode_object.go | 90 +++++++++++++ plumbing/object/commitgraph/commitnode_test.go | 144 +++++++++++++++++++++ .../object/commitgraph/commitnode_walker_ctime.go | 105 +++++++++++++++ plumbing/object/commitnode.go | 97 -------------- plumbing/object/commitnode_graph.go | 130 ------------------- plumbing/object/commitnode_object.go | 89 ------------- plumbing/object/commitnode_test.go | 144 --------------------- plumbing/object/commitnode_walker_ctime.go | 105 --------------- 10 files changed, 568 insertions(+), 565 deletions(-) create mode 100644 plumbing/object/commitgraph/commitnode.go create mode 100644 plumbing/object/commitgraph/commitnode_graph.go create mode 100644 plumbing/object/commitgraph/commitnode_object.go create mode 100644 plumbing/object/commitgraph/commitnode_test.go create mode 100644 plumbing/object/commitgraph/commitnode_walker_ctime.go delete mode 100644 plumbing/object/commitnode.go delete mode 100644 plumbing/object/commitnode_graph.go delete mode 100644 plumbing/object/commitnode_object.go delete mode 100644 plumbing/object/commitnode_test.go delete mode 100644 plumbing/object/commitnode_walker_ctime.go (limited to 'plumbing/object') diff --git a/plumbing/object/commitgraph/commitnode.go b/plumbing/object/commitgraph/commitnode.go new file mode 100644 index 0000000..e218d32 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode.go @@ -0,0 +1,98 @@ +package commitgraph + +import ( + "io" + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// CommitNode is generic interface encapsulating a lightweight commit object retrieved +// from CommitNodeIndex +type CommitNode interface { + // ID returns the Commit object id referenced by the commit graph node. + ID() plumbing.Hash + // Tree returns the Tree referenced by the commit graph node. + Tree() (*object.Tree, error) + // CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. + CommitTime() time.Time + // NumParents returns the number of parents in a commit. + NumParents() int + // ParentNodes return a CommitNodeIter for parents of specified node. + ParentNodes() CommitNodeIter + // ParentNode returns the ith parent of a commit. + ParentNode(i int) (CommitNode, error) + // ParentHashes returns hashes of the parent commits for a specified node + ParentHashes() []plumbing.Hash + // Generation returns the generation of the commit for reachability analysis. + // Objects with newer generation are not reachable from objects of older generation. + Generation() uint64 + // Commit returns the full commit object from the node + Commit() (*object.Commit, error) +} + +// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects +type CommitNodeIndex interface { + // Get returns a commit node from a commit hash + Get(hash plumbing.Hash) (CommitNode, error) +} + +// CommitNodeIter is a generic closable interface for iterating over commit nodes. +type CommitNodeIter interface { + Next() (CommitNode, error) + ForEach(func(CommitNode) error) error + Close() +} + +// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. +type parentCommitNodeIter struct { + node CommitNode + i int +} + +func newParentgraphCommitNodeIter(node CommitNode) CommitNodeIter { + return &parentCommitNodeIter{node, 0} +} + +// Next moves the iterator to the next commit and returns a pointer to it. If +// there are no more commits, it returns io.EOF. +func (iter *parentCommitNodeIter) Next() (CommitNode, error) { + obj, err := iter.node.ParentNode(iter.i) + if err == object.ErrParentNotFound { + return nil, io.EOF + } + if err == nil { + iter.i++ + } + + return obj, err +} + +// ForEach call the cb function for each commit contained on this iter until +// an error appends or the end of the iter is reached. If ErrStop is sent +// the iteration is stopped but no error is returned. The iterator is closed. +func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error { + for { + obj, err := iter.Next() + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + + if err := cb(obj); err != nil { + if err == storer.ErrStop { + return nil + } + + return err + } + } +} + +func (iter *parentCommitNodeIter) Close() { +} diff --git a/plumbing/object/commitgraph/commitnode_graph.go b/plumbing/object/commitgraph/commitnode_graph.go new file mode 100644 index 0000000..bd54e18 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_graph.go @@ -0,0 +1,131 @@ +package commitgraph + +import ( + "fmt" + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// graphCommitNode is a reduced representation of Commit as presented in the commit +// graph file (commitgraph.Node). It is merely useful as an optimization for walking +// the commit graphs. +// +// graphCommitNode implements the CommitNode interface. +type graphCommitNode struct { + // Hash for the Commit object + hash plumbing.Hash + // Index of the node in the commit graph file + index int + + commitData *commitgraph.CommitData + gci *graphCommitNodeIndex +} + +// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit +// graph files and the object store. +// +// graphCommitNodeIndex implements the CommitNodeIndex interface +type graphCommitNodeIndex struct { + commitGraph commitgraph.Index + s storer.EncodedObjectStorer +} + +// NewGraphCommitNodeIndex returns CommitNodeIndex implementation that uses commit-graph +// files as backing storage and falls back to object storage when necessary +func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { + return &graphCommitNodeIndex{commitGraph, s} +} + +func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + // Check the commit graph first + parentIndex, err := gci.commitGraph.GetIndexByHash(hash) + if err == nil { + parent, err := gci.commitGraph.GetCommitDataByIndex(parentIndex) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: hash, + index: parentIndex, + commitData: parent, + gci: gci, + }, nil + } + + // Fallback to loading full commit object + commit, err := object.GetCommit(gci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: gci, + commit: commit, + }, nil +} + +func (c *graphCommitNode) ID() plumbing.Hash { + return c.hash +} + +func (c *graphCommitNode) Tree() (*object.Tree, error) { + return object.GetTree(c.gci.s, c.commitData.TreeHash) +} + +func (c *graphCommitNode) CommitTime() time.Time { + return c.commitData.When +} + +func (c *graphCommitNode) NumParents() int { + return len(c.commitData.ParentIndexes) +} + +func (c *graphCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commitData.ParentIndexes) { + return nil, object.ErrParentNotFound + } + + parent, err := c.gci.commitGraph.GetCommitDataByIndex(c.commitData.ParentIndexes[i]) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: c.commitData.ParentHashes[i], + index: c.commitData.ParentIndexes[i], + commitData: parent, + gci: c.gci, + }, nil +} + +func (c *graphCommitNode) ParentHashes() []plumbing.Hash { + return c.commitData.ParentHashes +} + +func (c *graphCommitNode) Generation() uint64 { + // If the commit-graph file was generated with older Git version that + // set the generation to zero for every commit the generation assumption + // is still valid. It is just less useful. + return uint64(c.commitData.Generation) +} + +func (c *graphCommitNode) Commit() (*object.Commit, error) { + return object.GetCommit(c.gci.s, c.hash) +} + +func (c *graphCommitNode) String() string { + return fmt.Sprintf( + "%s %s\nDate: %s", + plumbing.CommitObject, c.ID(), + c.CommitTime().Format(object.DateFormat), + ) +} diff --git a/plumbing/object/commitgraph/commitnode_object.go b/plumbing/object/commitgraph/commitnode_object.go new file mode 100644 index 0000000..2779a54 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_object.go @@ -0,0 +1,90 @@ +package commitgraph + +import ( + "math" + "time" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// objectCommitNode is a representation of Commit as presented in the GIT object format. +// +// objectCommitNode implements the CommitNode interface. +type objectCommitNode struct { + nodeIndex CommitNodeIndex + commit *object.Commit +} + +// NewObjectCommitNodeIndex returns CommitNodeIndex implementation that uses +// only object storage to load the nodes +func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { + return &objectCommitNodeIndex{s} +} + +func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + commit, err := object.GetCommit(oci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: oci, + commit: commit, + }, nil +} + +// objectCommitNodeIndex is an index that can load CommitNode objects only from the +// object store. +// +// objectCommitNodeIndex implements the CommitNodeIndex interface +type objectCommitNodeIndex struct { + s storer.EncodedObjectStorer +} + +func (c *objectCommitNode) CommitTime() time.Time { + return c.commit.Committer.When +} + +func (c *objectCommitNode) ID() plumbing.Hash { + return c.commit.ID() +} + +func (c *objectCommitNode) Tree() (*object.Tree, error) { + return c.commit.Tree() +} + +func (c *objectCommitNode) NumParents() int { + return c.commit.NumParents() +} + +func (c *objectCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commit.ParentHashes) { + return nil, object.ErrParentNotFound + } + + // Note: It's necessary to go through CommitNodeIndex here to ensure + // that if the commit-graph file covers only part of the history we + // start using it when that part is reached. + return c.nodeIndex.Get(c.commit.ParentHashes[i]) +} + +func (c *objectCommitNode) ParentHashes() []plumbing.Hash { + return c.commit.ParentHashes +} + +func (c *objectCommitNode) Generation() uint64 { + // Commit nodes representing objects outside of the commit graph can never + // be reached by objects from the commit-graph thus we return the highest + // possible value. + return math.MaxUint64 +} + +func (c *objectCommitNode) Commit() (*object.Commit, error) { + return c.commit, nil +} diff --git a/plumbing/object/commitgraph/commitnode_test.go b/plumbing/object/commitgraph/commitnode_test.go new file mode 100644 index 0000000..2a339c8 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_test.go @@ -0,0 +1,144 @@ +package commitgraph + +import ( + "path" + + . "gopkg.in/check.v1" + fixtures "gopkg.in/src-d/go-git-fixtures.v3" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/cache" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" + "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" + "gopkg.in/src-d/go-git.v4/storage/filesystem" +) + +type CommitNodeSuite struct { + fixtures.Suite +} + +var _ = Suite(&CommitNodeSuite{}) + +func unpackRepositry(f *fixtures.Fixture) *filesystem.Storage { + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + packfile.UpdateObjectStorage(storer, p) + return storer +} + +func testWalker(c *C, nodeIndex CommitNodeIndex) { + head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) + c.Assert(err, IsNil) + + iter := NewCommitNodeIterCTime( + head, + nil, + nil, + ) + + var commits []CommitNode + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c) + return nil + }) + + c.Assert(commits, HasLen, 9) + + expected := []string{ + "b9d69064b190e7aedccf84731ca1d917871f8a1c", + "6f6c5d2be7852c782be1dd13e36496dd7ad39560", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + "c0edf780dd0da6a65a7a49a86032fcf8a0c2d467", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "03d2c021ff68954cf3ef0a36825e194a4b98f981", + "ce275064ad67d51e99f026084e20827901a8361c", + "e713b52d7e13807e87a002e812041f248db3f643", + "347c91919944a68e9413581a1bc15519550a3afe", + } + for i, commit := range commits { + c.Assert(commit.ID().String(), Equals, expected[i]) + } +} + +func testParents(c *C, nodeIndex CommitNodeIndex) { + merge3, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + + var parents []CommitNode + merge3.ParentNodes().ForEach(func(c CommitNode) error { + parents = append(parents, c) + return nil + }) + + c.Assert(parents, HasLen, 3) + + expected := []string{ + "ce275064ad67d51e99f026084e20827901a8361c", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + } + for i, parent := range parents { + c.Assert(parent.ID().String(), Equals, expected[i]) + } +} + +func testCommitAndTree(c *C, nodeIndex CommitNodeIndex) { + merge3node, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + merge3commit, err := merge3node.Commit() + c.Assert(err, IsNil) + c.Assert(merge3node.ID().String(), Equals, merge3commit.ID().String()) + tree, err := merge3node.Tree() + c.Assert(err, IsNil) + c.Assert(tree.ID().String(), Equals, merge3commit.TreeHash.String()) +} + +func (s *CommitNodeSuite) TestObjectGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepositry(f) + + nodeIndex := NewObjectCommitNodeIndex(storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} + +func (s *CommitNodeSuite) TestCommitGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepositry(f) + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + + nodeIndex := NewGraphCommitNodeIndex(index, storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} + +func (s *CommitNodeSuite) TestMixedGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepositry(f) + + // Take the commit-graph file and copy it to memory index without the last commit + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + fileIndex, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + memoryIndex := commitgraph.NewMemoryIndex() + for i, hash := range fileIndex.Hashes() { + if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { + node, err := fileIndex.GetCommitDataByIndex(i) + c.Assert(err, IsNil) + memoryIndex.Add(hash, node) + } + } + + nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} diff --git a/plumbing/object/commitgraph/commitnode_walker_ctime.go b/plumbing/object/commitgraph/commitnode_walker_ctime.go new file mode 100644 index 0000000..f6a1b6a --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_ctime.go @@ -0,0 +1,105 @@ +package commitgraph + +import ( + "io" + + "github.com/emirpasic/gods/trees/binaryheap" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +type commitNodeIteratorByCTime struct { + heap *binaryheap.Heap + seenExternal map[plumbing.Hash]bool + seen map[plumbing.Hash]bool +} + +// NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents while preserving Committer Time order. +// this appears to be the closest order to `git log` +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitNodeIterCTime( + c CommitNode, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := make(map[plumbing.Hash]bool) + for _, h := range ignore { + seen[h] = true + } + + heap := binaryheap.NewWith(func(a, b interface{}) int { + if a.(CommitNode).CommitTime().Before(b.(CommitNode).CommitTime()) { + return 1 + } + return -1 + }) + + heap.Push(c) + + return &commitNodeIteratorByCTime{ + heap: heap, + seenExternal: seenExternal, + seen: seen, + } +} + +func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { + var c CommitNode + for { + cIn, ok := w.heap.Pop() + if !ok { + return nil, io.EOF + } + c = cIn.(CommitNode) + cID := c.ID() + + if w.seen[cID] || w.seenExternal[cID] { + continue + } + + w.seen[cID] = true + + for i, h := range c.ParentHashes() { + if w.seen[h] || w.seenExternal[h] { + continue + } + pc, err := c.ParentNode(i) + if err != nil { + return nil, err + } + w.heap.Push(pc) + } + + return c, nil + } +} + +func (w *commitNodeIteratorByCTime) ForEach(cb func(CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +func (w *commitNodeIteratorByCTime) Close() {} diff --git a/plumbing/object/commitnode.go b/plumbing/object/commitnode.go deleted file mode 100644 index ce25487..0000000 --- a/plumbing/object/commitnode.go +++ /dev/null @@ -1,97 +0,0 @@ -package object - -import ( - "io" - "time" - - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/storer" -) - -// CommitNode is generic interface encapsulating a lightweight commit object retrieved -// from CommitNodeIndex -type CommitNode interface { - // ID returns the Commit object id referenced by the commit graph node. - ID() plumbing.Hash - // Tree returns the Tree referenced by the commit graph node. - Tree() (*Tree, error) - // CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. - CommitTime() time.Time - // NumParents returns the number of parents in a commit. - NumParents() int - // ParentNodes return a CommitNodeIter for parents of specified node. - ParentNodes() CommitNodeIter - // ParentNode returns the ith parent of a commit. - ParentNode(i int) (CommitNode, error) - // ParentHashes returns hashes of the parent commits for a specified node - ParentHashes() []plumbing.Hash - // Generation returns the generation of the commit for reachability analysis. - // Objects with newer generation are not reachable from objects of older generation. - Generation() uint64 - // Commit returns the full commit object from the node - Commit() (*Commit, error) -} - -// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects -type CommitNodeIndex interface { - // Get returns a commit node from a commit hash - Get(hash plumbing.Hash) (CommitNode, error) -} - -// CommitNodeIter is a generic closable interface for iterating over commit nodes. -type CommitNodeIter interface { - Next() (CommitNode, error) - ForEach(func(CommitNode) error) error - Close() -} - -// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. -type parentCommitNodeIter struct { - node CommitNode - i int -} - -func newParentgraphCommitNodeIter(node CommitNode) CommitNodeIter { - return &parentCommitNodeIter{node, 0} -} - -// Next moves the iterator to the next commit and returns a pointer to it. If -// there are no more commits, it returns io.EOF. -func (iter *parentCommitNodeIter) Next() (CommitNode, error) { - obj, err := iter.node.ParentNode(iter.i) - if err == ErrParentNotFound { - return nil, io.EOF - } - if err == nil { - iter.i++ - } - - return obj, err -} - -// ForEach call the cb function for each commit contained on this iter until -// an error appends or the end of the iter is reached. If ErrStop is sent -// the iteration is stopped but no error is returned. The iterator is closed. -func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error { - for { - obj, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if err := cb(obj); err != nil { - if err == storer.ErrStop { - return nil - } - - return err - } - } -} - -func (iter *parentCommitNodeIter) Close() { -} diff --git a/plumbing/object/commitnode_graph.go b/plumbing/object/commitnode_graph.go deleted file mode 100644 index ac790ab..0000000 --- a/plumbing/object/commitnode_graph.go +++ /dev/null @@ -1,130 +0,0 @@ -package object - -import ( - "fmt" - "time" - - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" - "gopkg.in/src-d/go-git.v4/plumbing/storer" -) - -// graphCommitNode is a reduced representation of Commit as presented in the commit -// graph file (commitgraph.Node). It is merely useful as an optimization for walking -// the commit graphs. -// -// graphCommitNode implements the CommitNode interface. -type graphCommitNode struct { - // Hash for the Commit object - hash plumbing.Hash - // Index of the node in the commit graph file - index int - - commitData *commitgraph.CommitData - gci *graphCommitNodeIndex -} - -// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit -// graph files and the object store. -// -// graphCommitNodeIndex implements the CommitNodeIndex interface -type graphCommitNodeIndex struct { - commitGraph commitgraph.Index - s storer.EncodedObjectStorer -} - -// NewGraphCommitNodeIndex returns CommitNodeIndex implementation that uses commit-graph -// files as backing storage and falls back to object storage when necessary -func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { - return &graphCommitNodeIndex{commitGraph, s} -} - -func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - // Check the commit graph first - parentIndex, err := gci.commitGraph.GetIndexByHash(hash) - if err == nil { - parent, err := gci.commitGraph.GetCommitDataByIndex(parentIndex) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: hash, - index: parentIndex, - commitData: parent, - gci: gci, - }, nil - } - - // Fallback to loading full commit object - commit, err := GetCommit(gci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: gci, - commit: commit, - }, nil -} - -func (c *graphCommitNode) ID() plumbing.Hash { - return c.hash -} - -func (c *graphCommitNode) Tree() (*Tree, error) { - return GetTree(c.gci.s, c.commitData.TreeHash) -} - -func (c *graphCommitNode) CommitTime() time.Time { - return c.commitData.When -} - -func (c *graphCommitNode) NumParents() int { - return len(c.commitData.ParentIndexes) -} - -func (c *graphCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.commitData.ParentIndexes) { - return nil, ErrParentNotFound - } - - parent, err := c.gci.commitGraph.GetCommitDataByIndex(c.commitData.ParentIndexes[i]) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: c.commitData.ParentHashes[i], - index: c.commitData.ParentIndexes[i], - commitData: parent, - gci: c.gci, - }, nil -} - -func (c *graphCommitNode) ParentHashes() []plumbing.Hash { - return c.commitData.ParentHashes -} - -func (c *graphCommitNode) Generation() uint64 { - // If the commit-graph file was generated with older Git version that - // set the generation to zero for every commit the generation assumption - // is still valid. It is just less useful. - return uint64(c.commitData.Generation) -} - -func (c *graphCommitNode) Commit() (*Commit, error) { - return GetCommit(c.gci.s, c.hash) -} - -func (c *graphCommitNode) String() string { - return fmt.Sprintf( - "%s %s\nDate: %s", - plumbing.CommitObject, c.ID(), - c.CommitTime().Format(DateFormat), - ) -} diff --git a/plumbing/object/commitnode_object.go b/plumbing/object/commitnode_object.go deleted file mode 100644 index 9ac42d2..0000000 --- a/plumbing/object/commitnode_object.go +++ /dev/null @@ -1,89 +0,0 @@ -package object - -import ( - "math" - "time" - - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/storer" -) - -// objectCommitNode is a representation of Commit as presented in the GIT object format. -// -// objectCommitNode implements the CommitNode interface. -type objectCommitNode struct { - nodeIndex CommitNodeIndex - commit *Commit -} - -// NewObjectCommitNodeIndex returns CommitNodeIndex implementation that uses -// only object storage to load the nodes -func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { - return &objectCommitNodeIndex{s} -} - -func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - commit, err := GetCommit(oci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: oci, - commit: commit, - }, nil -} - -// objectCommitNodeIndex is an index that can load CommitNode objects only from the -// object store. -// -// objectCommitNodeIndex implements the CommitNodeIndex interface -type objectCommitNodeIndex struct { - s storer.EncodedObjectStorer -} - -func (c *objectCommitNode) CommitTime() time.Time { - return c.commit.Committer.When -} - -func (c *objectCommitNode) ID() plumbing.Hash { - return c.commit.ID() -} - -func (c *objectCommitNode) Tree() (*Tree, error) { - return c.commit.Tree() -} - -func (c *objectCommitNode) NumParents() int { - return c.commit.NumParents() -} - -func (c *objectCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.commit.ParentHashes) { - return nil, ErrParentNotFound - } - - // Note: It's necessary to go through CommitNodeIndex here to ensure - // that if the commit-graph file covers only part of the history we - // start using it when that part is reached. - return c.nodeIndex.Get(c.commit.ParentHashes[i]) -} - -func (c *objectCommitNode) ParentHashes() []plumbing.Hash { - return c.commit.ParentHashes -} - -func (c *objectCommitNode) Generation() uint64 { - // Commit nodes representing objects outside of the commit graph can never - // be reached by objects from the commit-graph thus we return the highest - // possible value. - return math.MaxUint64 -} - -func (c *objectCommitNode) Commit() (*Commit, error) { - return c.commit, nil -} diff --git a/plumbing/object/commitnode_test.go b/plumbing/object/commitnode_test.go deleted file mode 100644 index a295b8b..0000000 --- a/plumbing/object/commitnode_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package object - -import ( - "path" - - . "gopkg.in/check.v1" - fixtures "gopkg.in/src-d/go-git-fixtures.v3" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/cache" - "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" - "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" - "gopkg.in/src-d/go-git.v4/storage/filesystem" -) - -type CommitNodeSuite struct { - fixtures.Suite -} - -var _ = Suite(&CommitNodeSuite{}) - -func unpackRepositry(f *fixtures.Fixture) *filesystem.Storage { - storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) - p := f.Packfile() - defer p.Close() - packfile.UpdateObjectStorage(storer, p) - return storer -} - -func testWalker(c *C, nodeIndex CommitNodeIndex) { - head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) - c.Assert(err, IsNil) - - iter := NewCommitNodeIterCTime( - head, - nil, - nil, - ) - - var commits []CommitNode - iter.ForEach(func(c CommitNode) error { - commits = append(commits, c) - return nil - }) - - c.Assert(commits, HasLen, 9) - - expected := []string{ - "b9d69064b190e7aedccf84731ca1d917871f8a1c", - "6f6c5d2be7852c782be1dd13e36496dd7ad39560", - "a45273fe2d63300e1962a9e26a6b15c276cd7082", - "c0edf780dd0da6a65a7a49a86032fcf8a0c2d467", - "bb13916df33ed23004c3ce9ed3b8487528e655c1", - "03d2c021ff68954cf3ef0a36825e194a4b98f981", - "ce275064ad67d51e99f026084e20827901a8361c", - "e713b52d7e13807e87a002e812041f248db3f643", - "347c91919944a68e9413581a1bc15519550a3afe", - } - for i, commit := range commits { - c.Assert(commit.ID().String(), Equals, expected[i]) - } -} - -func testParents(c *C, nodeIndex CommitNodeIndex) { - merge3, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) - c.Assert(err, IsNil) - - var parents []CommitNode - merge3.ParentNodes().ForEach(func(c CommitNode) error { - parents = append(parents, c) - return nil - }) - - c.Assert(parents, HasLen, 3) - - expected := []string{ - "ce275064ad67d51e99f026084e20827901a8361c", - "bb13916df33ed23004c3ce9ed3b8487528e655c1", - "a45273fe2d63300e1962a9e26a6b15c276cd7082", - } - for i, parent := range parents { - c.Assert(parent.ID().String(), Equals, expected[i]) - } -} - -func testCommitAndTree(c *C, nodeIndex CommitNodeIndex) { - merge3node, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) - c.Assert(err, IsNil) - merge3commit, err := merge3node.Commit() - c.Assert(err, IsNil) - c.Assert(merge3node.ID().String(), Equals, merge3commit.ID().String()) - tree, err := merge3node.Tree() - c.Assert(err, IsNil) - c.Assert(tree.ID().String(), Equals, merge3commit.TreeHash.String()) -} - -func (s *CommitNodeSuite) TestObjectGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - - nodeIndex := NewObjectCommitNodeIndex(storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} - -func (s *CommitNodeSuite) TestCommitGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) - c.Assert(err, IsNil) - defer reader.Close() - index, err := commitgraph.OpenFileIndex(reader) - c.Assert(err, IsNil) - - nodeIndex := NewGraphCommitNodeIndex(index, storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} - -func (s *CommitNodeSuite) TestMixedGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - - // Take the commit-graph file and copy it to memory index without the last commit - reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) - c.Assert(err, IsNil) - defer reader.Close() - fileIndex, err := commitgraph.OpenFileIndex(reader) - c.Assert(err, IsNil) - memoryIndex := commitgraph.NewMemoryIndex() - for i, hash := range fileIndex.Hashes() { - if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { - node, err := fileIndex.GetCommitDataByIndex(i) - c.Assert(err, IsNil) - memoryIndex.Add(hash, node) - } - } - - nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} diff --git a/plumbing/object/commitnode_walker_ctime.go b/plumbing/object/commitnode_walker_ctime.go deleted file mode 100644 index e55b4ad..0000000 --- a/plumbing/object/commitnode_walker_ctime.go +++ /dev/null @@ -1,105 +0,0 @@ -package object - -import ( - "io" - - "github.com/emirpasic/gods/trees/binaryheap" - - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/storer" -) - -type commitNodeIteratorByCTime struct { - heap *binaryheap.Heap - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool -} - -// NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, -// starting at the given commit and visiting its parents while preserving Committer Time order. -// this appears to be the closest order to `git log` -// The given callback will be called for each visited commit. Each commit will -// be visited only once. If the callback returns an error, walking will stop -// and will return the error. Other errors might be returned if the history -// cannot be traversed (e.g. missing objects). Ignore allows to skip some -// commits from being iterated. -func NewCommitNodeIterCTime( - c CommitNode, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, -) CommitNodeIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(CommitNode).CommitTime().Before(b.(CommitNode).CommitTime()) { - return 1 - } - return -1 - }) - - heap.Push(c) - - return &commitNodeIteratorByCTime{ - heap: heap, - seenExternal: seenExternal, - seen: seen, - } -} - -func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { - var c CommitNode - for { - cIn, ok := w.heap.Pop() - if !ok { - return nil, io.EOF - } - c = cIn.(CommitNode) - cID := c.ID() - - if w.seen[cID] || w.seenExternal[cID] { - continue - } - - w.seen[cID] = true - - for i, h := range c.ParentHashes() { - if w.seen[h] || w.seenExternal[h] { - continue - } - pc, err := c.ParentNode(i) - if err != nil { - return nil, err - } - w.heap.Push(pc) - } - - return c, nil - } -} - -func (w *commitNodeIteratorByCTime) ForEach(cb func(CommitNode) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *commitNodeIteratorByCTime) Close() {} -- cgit From 940460f5422b02f01351396af703e9b63e8596ae Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 3 May 2019 12:13:15 +0200 Subject: Fix object/commitgraph tests Signed-off-by: Filip Navara --- plumbing/object/commitgraph/commitnode_test.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plumbing/object') diff --git a/plumbing/object/commitgraph/commitnode_test.go b/plumbing/object/commitgraph/commitnode_test.go index 2a339c8..954f873 100644 --- a/plumbing/object/commitgraph/commitnode_test.go +++ b/plumbing/object/commitgraph/commitnode_test.go @@ -2,6 +2,7 @@ package commitgraph import ( "path" + "testing" . "gopkg.in/check.v1" fixtures "gopkg.in/src-d/go-git-fixtures.v3" @@ -12,6 +13,8 @@ import ( "gopkg.in/src-d/go-git.v4/storage/filesystem" ) +func Test(t *testing.T) { TestingT(t) } + type CommitNodeSuite struct { fixtures.Suite } -- cgit From 7d2695741f4d1f572a36f7225b6bbb2f569d59d7 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 7 May 2019 10:49:39 +0200 Subject: Add doc.go for commitgraph packages Signed-off-by: Filip Navara --- plumbing/object/commitgraph/doc.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 plumbing/object/commitgraph/doc.go (limited to 'plumbing/object') diff --git a/plumbing/object/commitgraph/doc.go b/plumbing/object/commitgraph/doc.go new file mode 100644 index 0000000..0a55ad5 --- /dev/null +++ b/plumbing/object/commitgraph/doc.go @@ -0,0 +1,7 @@ +// Package commitgraph provides an interface for efficient traversal over Git +// commit graph either through the regular object storage, or optionally with +// the index stored in commit-graph file (Git 2.18+). +// +// The API and functionality of this package are considered EXPERIMENTAL and is +// not considered stable nor production ready. +package commitgraph -- cgit