aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/object/commit_walker_bfs_filtered.go
blob: b12523d4896db6d53b9c04d7c128be953b4c3c89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package object

import (
	"io"

	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

// NewFilterCommitIter returns a CommitIter that walks the commit history,
// starting at the passed commit and visiting its parents in Breadth-first order.
// The commits returned by the CommitIter will validate the passed CommitFilter.
// The history won't be transversed beyond a commit if isLimit is true for it.
// Each commit will be visited only once.
// If the commit history can not be traversed, or the Close() method is called,
// the CommitIter won't return more commits.
// If no isValid is passed, all ancestors of from commit will be valid.
// If no isLimit is limmit, all ancestors of all commits will be visited.
func NewFilterCommitIter(
	from *Commit,
	isValid *CommitFilter,
	isLimit *CommitFilter,
) CommitIter {
	var validFilter CommitFilter
	if isValid == nil {
		validFilter = func(_ *Commit) bool {
			return true
		}
	} else {
		validFilter = *isValid
	}

	var limitFilter CommitFilter
	if isLimit == nil {
		limitFilter = func(_ *Commit) bool {
			return false
		}
	} else {
		limitFilter = *isLimit
	}

	return &filterCommitIter{
		isValid: validFilter,
		isLimit: limitFilter,
		visited: map[plumbing.Hash]struct{}{},
		queue:   []*Commit{from},
	}
}

// CommitFilter returns a boolean for the passed Commit
type CommitFilter func(*Commit) bool

// filterCommitIter implments CommitIter
type filterCommitIter struct {
	isValid CommitFilter
	isLimit CommitFilter
	visited map[plumbing.Hash]struct{}
	queue   []*Commit
	lastErr error
}

// Next returns the next commit of the CommitIter.
// It will return io.EOF if there are no more commits to visit,
// or an error if the history could not be traversed.
func (w *filterCommitIter) Next() (*Commit, error) {
	var commit *Commit
	var err error
	for {
		commit, err = w.popNewFromQueue()
		if err != nil {
			return nil, w.close(err)
		}

		w.visited[commit.Hash] = struct{}{}

		if !w.isLimit(commit) {
			err = w.addToQueue(commit.s, commit.ParentHashes...)
			if err != nil {
				return nil, w.close(err)
			}
		}

		if w.isValid(commit) {
			return commit, nil
		}
	}
}

// ForEach runs the passed callback over each Commit returned by the CommitIter
// until the callback returns an error or there is no more commits to traverse.
func (w *filterCommitIter) ForEach(cb func(*Commit) error) error {
	for {
		commit, err := w.Next()
		if err == io.EOF {
			break
		}

		if err != nil {
			return err
		}

		if err := cb(commit); err == storer.ErrStop {
			break
		} else if err != nil {
			return err
		}
	}

	return nil
}

// Error returns the error that caused that the CommitIter is no longer returning commits
func (w *filterCommitIter) Error() error {
	return w.lastErr
}

// Close closes the CommitIter
func (w *filterCommitIter) Close() {
	w.visited = map[plumbing.Hash]struct{}{}
	w.queue = []*Commit{}
	w.isLimit = nil
	w.isValid = nil
}

// close closes the CommitIter with an error
func (w *filterCommitIter) close(err error) error {
	w.Close()
	w.lastErr = err
	return err
}

// popNewFromQueue returns the first new commit from the internal fifo queue,
// or an io.EOF error if the queue is empty
func (w *filterCommitIter) popNewFromQueue() (*Commit, error) {
	var first *Commit
	for {
		if len(w.queue) == 0 {
			if w.lastErr != nil {
				return nil, w.lastErr
			}

			return nil, io.EOF
		}

		first = w.queue[0]
		w.queue = w.queue[1:]
		if _, ok := w.visited[first.Hash]; ok {
			continue
		}

		return first, nil
	}
}

// addToQueue adds the passed commits to the internal fifo queue if they weren't seen
// or returns an error if the passed hashes could not be used to get valid commits
func (w *filterCommitIter) addToQueue(
	store storer.EncodedObjectStorer,
	hashes ...plumbing.Hash,
) error {
	for _, hash := range hashes {
		if _, ok := w.visited[hash]; ok {
			continue
		}

		commit, err := GetCommit(store, hash)
		if err != nil {
			return err
		}

		w.queue = append(w.queue, commit)
	}

	return nil
}