package commitgraph import ( "io" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/emirpasic/gods/trees/binaryheap" ) type commitNodeIteratorTopological struct { exploreStack commitNodeStackable visitStack commitNodeStackable inCounts map[plumbing.Hash]int ignore map[plumbing.Hash]struct{} } // NewCommitNodeIterTopoOrder returns a CommitNodeIter that walks the commit history, // starting at the given commit and visiting its parents in a topological order but // with the constraint that no parent is emitted before its children are emitted. // // This matches `git log --topo-order` func NewCommitNodeIterTopoOrder(c CommitNode, seenExternal map[plumbing.Hash]bool, ignore []plumbing.Hash, ) CommitNodeIter { seen := composeIgnores(ignore, seenExternal) inCounts := make(map[plumbing.Hash]int) heap := &commitNodeHeap{binaryheap.NewWith(generationAndDateOrderComparator)} heap.Push(c) lifo := &commitNodeLifo{make([]CommitNode, 0, 8)} lifo.Push(c) return &commitNodeIteratorTopological{ exploreStack: heap, visitStack: lifo, inCounts: inCounts, ignore: seen, } } func (iter *commitNodeIteratorTopological) Next() (CommitNode, error) { var next CommitNode for { var ok bool next, ok = iter.visitStack.Pop() if !ok { return nil, io.EOF } if iter.inCounts[next.ID()] == 0 { break } } minimumLevel, generationV2 := next.GenerationV2(), true if minimumLevel == 0 { minimumLevel, generationV2 = next.Generation(), false } parents := make([]CommitNode, 0, len(next.ParentHashes())) for i := range next.ParentHashes() { pc, err := next.ParentNode(i) if err != nil { return nil, err } parents = append(parents, pc) if generationV2 { if pc.GenerationV2() < minimumLevel { minimumLevel = pc.GenerationV2() } continue } if pc.Generation() < minimumLevel { minimumLevel = pc.Generation() } } // EXPLORE for { toExplore, ok := iter.exploreStack.Peek() if !ok { break } if toExplore.ID() != next.ID() && iter.exploreStack.Size() == 1 { break } if generationV2 { if toExplore.GenerationV2() < minimumLevel { break } } else { if toExplore.Generation() < minimumLevel { break } } iter.exploreStack.Pop() for i, h := range toExplore.ParentHashes() { if _, has := iter.ignore[h]; has { continue } iter.inCounts[h]++ if iter.inCounts[h] == 1 { pc, err := toExplore.ParentNode(i) if err != nil { return nil, err } iter.exploreStack.Push(pc) } } } // VISIT for i, h := range next.ParentHashes() { if _, has := iter.ignore[h]; has { continue } iter.inCounts[h]-- if iter.inCounts[h] == 0 { iter.visitStack.Push(parents[i]) } } delete(iter.inCounts, next.ID()) return next, nil } func (iter *commitNodeIteratorTopological) 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 *commitNodeIteratorTopological) Close() { }