forked from gitea/gitea
328 lines
6.8 KiB
Go
328 lines
6.8 KiB
Go
package object
|
|
|
|
import (
|
|
"container/list"
|
|
"io"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
"gopkg.in/src-d/go-git.v4/plumbing/storer"
|
|
"gopkg.in/src-d/go-git.v4/storage"
|
|
)
|
|
|
|
type commitPreIterator struct {
|
|
seenExternal map[plumbing.Hash]bool
|
|
seen map[plumbing.Hash]bool
|
|
stack []CommitIter
|
|
start *Commit
|
|
}
|
|
|
|
// NewCommitPreorderIter returns a CommitIter that walks the commit history,
|
|
// starting at the given commit and visiting its parents in pre-order.
|
|
// 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 NewCommitPreorderIter(
|
|
c *Commit,
|
|
seenExternal map[plumbing.Hash]bool,
|
|
ignore []plumbing.Hash,
|
|
) CommitIter {
|
|
seen := make(map[plumbing.Hash]bool)
|
|
for _, h := range ignore {
|
|
seen[h] = true
|
|
}
|
|
|
|
return &commitPreIterator{
|
|
seenExternal: seenExternal,
|
|
seen: seen,
|
|
stack: make([]CommitIter, 0),
|
|
start: c,
|
|
}
|
|
}
|
|
|
|
func (w *commitPreIterator) Next() (*Commit, error) {
|
|
var c *Commit
|
|
for {
|
|
if w.start != nil {
|
|
c = w.start
|
|
w.start = nil
|
|
} else {
|
|
current := len(w.stack) - 1
|
|
if current < 0 {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
var err error
|
|
c, err = w.stack[current].Next()
|
|
if err == io.EOF {
|
|
w.stack = w.stack[:current]
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if w.seen[c.Hash] || w.seenExternal[c.Hash] {
|
|
continue
|
|
}
|
|
|
|
w.seen[c.Hash] = true
|
|
|
|
if c.NumParents() > 0 {
|
|
w.stack = append(w.stack, filteredParentIter(c, w.seen))
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
}
|
|
|
|
func filteredParentIter(c *Commit, seen map[plumbing.Hash]bool) CommitIter {
|
|
var hashes []plumbing.Hash
|
|
for _, h := range c.ParentHashes {
|
|
if !seen[h] {
|
|
hashes = append(hashes, h)
|
|
}
|
|
}
|
|
|
|
return NewCommitIter(c.s,
|
|
storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, hashes),
|
|
)
|
|
}
|
|
|
|
func (w *commitPreIterator) ForEach(cb func(*Commit) 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 *commitPreIterator) Close() {}
|
|
|
|
type commitPostIterator struct {
|
|
stack []*Commit
|
|
seen map[plumbing.Hash]bool
|
|
}
|
|
|
|
// NewCommitPostorderIter returns a CommitIter that walks the commit
|
|
// history like WalkCommitHistory but in post-order. This means that after
|
|
// walking a merge commit, the merged commit will be walked before the base
|
|
// it was merged on. This can be useful if you wish to see the history in
|
|
// chronological order. Ignore allows to skip some commits from being iterated.
|
|
func NewCommitPostorderIter(c *Commit, ignore []plumbing.Hash) CommitIter {
|
|
seen := make(map[plumbing.Hash]bool)
|
|
for _, h := range ignore {
|
|
seen[h] = true
|
|
}
|
|
|
|
return &commitPostIterator{
|
|
stack: []*Commit{c},
|
|
seen: seen,
|
|
}
|
|
}
|
|
|
|
func (w *commitPostIterator) Next() (*Commit, error) {
|
|
for {
|
|
if len(w.stack) == 0 {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
c := w.stack[len(w.stack)-1]
|
|
w.stack = w.stack[:len(w.stack)-1]
|
|
|
|
if w.seen[c.Hash] {
|
|
continue
|
|
}
|
|
|
|
w.seen[c.Hash] = true
|
|
|
|
return c, c.Parents().ForEach(func(p *Commit) error {
|
|
w.stack = append(w.stack, p)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func (w *commitPostIterator) ForEach(cb func(*Commit) 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 *commitPostIterator) Close() {}
|
|
|
|
// commitAllIterator stands for commit iterator for all refs.
|
|
type commitAllIterator struct {
|
|
// currCommit points to the current commit.
|
|
currCommit *list.Element
|
|
}
|
|
|
|
// NewCommitAllIter returns a new commit iterator for all refs.
|
|
// repoStorer is a repo Storer used to get commits and references.
|
|
// commitIterFunc is a commit iterator function, used to iterate through ref commits in chosen order
|
|
func NewCommitAllIter(repoStorer storage.Storer, commitIterFunc func(*Commit) CommitIter) (CommitIter, error) {
|
|
commitsPath := list.New()
|
|
commitsLookup := make(map[plumbing.Hash]*list.Element)
|
|
head, err := storer.ResolveReference(repoStorer, plumbing.HEAD)
|
|
if err == nil {
|
|
err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup)
|
|
}
|
|
|
|
if err != nil && err != plumbing.ErrReferenceNotFound {
|
|
return nil, err
|
|
}
|
|
|
|
// add all references along with the HEAD
|
|
refIter, err := repoStorer.IterReferences()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer refIter.Close()
|
|
|
|
for {
|
|
ref, err := refIter.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err == plumbing.ErrReferenceNotFound {
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &commitAllIterator{commitsPath.Front()}, nil
|
|
}
|
|
|
|
func addReference(
|
|
repoStorer storage.Storer,
|
|
commitIterFunc func(*Commit) CommitIter,
|
|
ref *plumbing.Reference,
|
|
commitsPath *list.List,
|
|
commitsLookup map[plumbing.Hash]*list.Element) error {
|
|
|
|
_, exists := commitsLookup[ref.Hash()]
|
|
if exists {
|
|
// we already have it - skip the reference.
|
|
return nil
|
|
}
|
|
|
|
refCommit, _ := GetCommit(repoStorer, ref.Hash())
|
|
if refCommit == nil {
|
|
// if it's not a commit - skip it.
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
refCommits []*Commit
|
|
parent *list.Element
|
|
)
|
|
// collect all ref commits to add
|
|
commitIter := commitIterFunc(refCommit)
|
|
for c, e := commitIter.Next(); e == nil; {
|
|
parent, exists = commitsLookup[c.Hash]
|
|
if exists {
|
|
break
|
|
}
|
|
refCommits = append(refCommits, c)
|
|
c, e = commitIter.Next()
|
|
}
|
|
commitIter.Close()
|
|
|
|
if parent == nil {
|
|
// common parent - not found
|
|
// add all commits to the path from this ref (maybe it's a HEAD and we don't have anything, yet)
|
|
for _, c := range refCommits {
|
|
parent = commitsPath.PushBack(c)
|
|
commitsLookup[c.Hash] = parent
|
|
}
|
|
} else {
|
|
// add ref's commits to the path in reverse order (from the latest)
|
|
for i := len(refCommits) - 1; i >= 0; i-- {
|
|
c := refCommits[i]
|
|
// insert before found common parent
|
|
parent = commitsPath.InsertBefore(c, parent)
|
|
commitsLookup[c.Hash] = parent
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (it *commitAllIterator) Next() (*Commit, error) {
|
|
if it.currCommit == nil {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
c := it.currCommit.Value.(*Commit)
|
|
it.currCommit = it.currCommit.Next()
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (it *commitAllIterator) ForEach(cb func(*Commit) error) error {
|
|
for {
|
|
c, err := it.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 (it *commitAllIterator) Close() {
|
|
it.currCommit = nil
|
|
}
|