1package object 2 3import ( 4 "io" 5 6 "github.com/go-git/go-git/v5/plumbing" 7 "github.com/go-git/go-git/v5/plumbing/storer" 8) 9 10// NewFilterCommitIter returns a CommitIter that walks the commit history, 11// starting at the passed commit and visiting its parents in Breadth-first order. 12// The commits returned by the CommitIter will validate the passed CommitFilter. 13// The history won't be transversed beyond a commit if isLimit is true for it. 14// Each commit will be visited only once. 15// If the commit history can not be traversed, or the Close() method is called, 16// the CommitIter won't return more commits. 17// If no isValid is passed, all ancestors of from commit will be valid. 18// If no isLimit is limit, all ancestors of all commits will be visited. 19func NewFilterCommitIter( 20 from *Commit, 21 isValid *CommitFilter, 22 isLimit *CommitFilter, 23) CommitIter { 24 var validFilter CommitFilter 25 if isValid == nil { 26 validFilter = func(_ *Commit) bool { 27 return true 28 } 29 } else { 30 validFilter = *isValid 31 } 32 33 var limitFilter CommitFilter 34 if isLimit == nil { 35 limitFilter = func(_ *Commit) bool { 36 return false 37 } 38 } else { 39 limitFilter = *isLimit 40 } 41 42 return &filterCommitIter{ 43 isValid: validFilter, 44 isLimit: limitFilter, 45 visited: map[plumbing.Hash]struct{}{}, 46 queue: []*Commit{from}, 47 } 48} 49 50// CommitFilter returns a boolean for the passed Commit 51type CommitFilter func(*Commit) bool 52 53// filterCommitIter implements CommitIter 54type filterCommitIter struct { 55 isValid CommitFilter 56 isLimit CommitFilter 57 visited map[plumbing.Hash]struct{} 58 queue []*Commit 59 lastErr error 60} 61 62// Next returns the next commit of the CommitIter. 63// It will return io.EOF if there are no more commits to visit, 64// or an error if the history could not be traversed. 65func (w *filterCommitIter) Next() (*Commit, error) { 66 var commit *Commit 67 var err error 68 for { 69 commit, err = w.popNewFromQueue() 70 if err != nil { 71 return nil, w.close(err) 72 } 73 74 w.visited[commit.Hash] = struct{}{} 75 76 if !w.isLimit(commit) { 77 err = w.addToQueue(commit.s, commit.ParentHashes...) 78 if err != nil { 79 return nil, w.close(err) 80 } 81 } 82 83 if w.isValid(commit) { 84 return commit, nil 85 } 86 } 87} 88 89// ForEach runs the passed callback over each Commit returned by the CommitIter 90// until the callback returns an error or there is no more commits to traverse. 91func (w *filterCommitIter) ForEach(cb func(*Commit) error) error { 92 for { 93 commit, err := w.Next() 94 if err == io.EOF { 95 break 96 } 97 98 if err != nil { 99 return err 100 } 101 102 if err := cb(commit); err == storer.ErrStop { 103 break 104 } else if err != nil { 105 return err 106 } 107 } 108 109 return nil 110} 111 112// Error returns the error that caused that the CommitIter is no longer returning commits 113func (w *filterCommitIter) Error() error { 114 return w.lastErr 115} 116 117// Close closes the CommitIter 118func (w *filterCommitIter) Close() { 119 w.visited = map[plumbing.Hash]struct{}{} 120 w.queue = []*Commit{} 121 w.isLimit = nil 122 w.isValid = nil 123} 124 125// close closes the CommitIter with an error 126func (w *filterCommitIter) close(err error) error { 127 w.Close() 128 w.lastErr = err 129 return err 130} 131 132// popNewFromQueue returns the first new commit from the internal fifo queue, 133// or an io.EOF error if the queue is empty 134func (w *filterCommitIter) popNewFromQueue() (*Commit, error) { 135 var first *Commit 136 for { 137 if len(w.queue) == 0 { 138 if w.lastErr != nil { 139 return nil, w.lastErr 140 } 141 142 return nil, io.EOF 143 } 144 145 first = w.queue[0] 146 w.queue = w.queue[1:] 147 if _, ok := w.visited[first.Hash]; ok { 148 continue 149 } 150 151 return first, nil 152 } 153} 154 155// addToQueue adds the passed commits to the internal fifo queue if they weren't seen 156// or returns an error if the passed hashes could not be used to get valid commits 157func (w *filterCommitIter) addToQueue( 158 store storer.EncodedObjectStorer, 159 hashes ...plumbing.Hash, 160) error { 161 for _, hash := range hashes { 162 if _, ok := w.visited[hash]; ok { 163 continue 164 } 165 166 commit, err := GetCommit(store, hash) 167 if err != nil { 168 return err 169 } 170 171 w.queue = append(w.queue, commit) 172 } 173 174 return nil 175} 176