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() {
}