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