|
| 1 | +use crate::commit::topo::iter::gen_and_commit_time; |
| 2 | +use crate::commit::topo::{Error, Sorting, Walk, WalkFlags}; |
| 3 | +use crate::commit::{find, Info, Parents}; |
| 4 | +use gix_hash::{oid, ObjectId}; |
| 5 | +use gix_revwalk::graph::IdMap; |
| 6 | +use gix_revwalk::PriorityQueue; |
| 7 | + |
| 8 | +/// Builder for [`Walk`]. |
| 9 | +pub struct Builder<Find, Predicate> { |
| 10 | + commit_graph: Option<gix_commitgraph::Graph>, |
| 11 | + find: Find, |
| 12 | + predicate: Predicate, |
| 13 | + sorting: Sorting, |
| 14 | + parents: Parents, |
| 15 | + tips: Vec<ObjectId>, |
| 16 | + ends: Vec<ObjectId>, |
| 17 | +} |
| 18 | + |
| 19 | +impl<Find> Builder<Find, fn(&oid) -> bool> |
| 20 | +where |
| 21 | + Find: gix_object::Find, |
| 22 | +{ |
| 23 | + /// Create a new `Builder` for a [`Walk`] that reads commits from a repository with `find`. |
| 24 | + /// starting at the `tips` and ending at the `ends`. Like `git rev-list |
| 25 | + /// --topo-order ^ends... tips...`. |
| 26 | + pub fn from_iters( |
| 27 | + find: Find, |
| 28 | + tips: impl IntoIterator<Item = impl Into<ObjectId>>, |
| 29 | + ends: Option<impl IntoIterator<Item = impl Into<ObjectId>>>, |
| 30 | + ) -> Self { |
| 31 | + let tips = tips.into_iter().map(Into::into).collect::<Vec<_>>(); |
| 32 | + let ends = ends |
| 33 | + .map(|e| e.into_iter().map(Into::into).collect::<Vec<_>>()) |
| 34 | + .unwrap_or_default(); |
| 35 | + |
| 36 | + Self { |
| 37 | + commit_graph: Default::default(), |
| 38 | + find, |
| 39 | + sorting: Default::default(), |
| 40 | + parents: Default::default(), |
| 41 | + tips, |
| 42 | + ends, |
| 43 | + predicate: |_| true, |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// Set a `predicate` to filter out revisions from the walk. Can be used to |
| 48 | + /// implement e.g. filtering on paths or time. This does *not* exclude the |
| 49 | + /// parent(s) of a revision that is excluded. Specify a revision as an 'end' |
| 50 | + /// if you want that behavior. |
| 51 | + pub fn with_predicate<Predicate>(self, predicate: Predicate) -> Builder<Find, Predicate> |
| 52 | + where |
| 53 | + Predicate: FnMut(&oid) -> bool, |
| 54 | + { |
| 55 | + Builder { |
| 56 | + commit_graph: self.commit_graph, |
| 57 | + find: self.find, |
| 58 | + sorting: self.sorting, |
| 59 | + parents: self.parents, |
| 60 | + tips: self.tips, |
| 61 | + ends: self.ends, |
| 62 | + predicate, |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +impl<Find, Predicate> Builder<Find, Predicate> |
| 68 | +where |
| 69 | + Find: gix_object::Find, |
| 70 | + Predicate: FnMut(&oid) -> bool, |
| 71 | +{ |
| 72 | + /// Set the `sorting` to use for the topological walk. |
| 73 | + pub fn sorting(mut self, sorting: Sorting) -> Self { |
| 74 | + self.sorting = sorting; |
| 75 | + self |
| 76 | + } |
| 77 | + |
| 78 | + /// Specify how to handle commit `parents` during traversal. |
| 79 | + pub fn parents(mut self, parents: Parents) -> Self { |
| 80 | + self.parents = parents; |
| 81 | + self |
| 82 | + } |
| 83 | + |
| 84 | + /// Set or unset the `commit_graph` to use for the iteration. |
| 85 | + pub fn with_commit_graph(mut self, commit_graph: Option<gix_commitgraph::Graph>) -> Self { |
| 86 | + self.commit_graph = commit_graph; |
| 87 | + self |
| 88 | + } |
| 89 | + |
| 90 | + /// Build a new [`Walk`] instance. |
| 91 | + /// |
| 92 | + /// Note that merely building an instance is currently expensive. |
| 93 | + pub fn build(self) -> Result<Walk<Find, Predicate>, Error> { |
| 94 | + let mut w = Walk { |
| 95 | + commit_graph: self.commit_graph, |
| 96 | + find: self.find, |
| 97 | + predicate: self.predicate, |
| 98 | + indegrees: IdMap::default(), |
| 99 | + states: IdMap::default(), |
| 100 | + explore_queue: PriorityQueue::new(), |
| 101 | + indegree_queue: PriorityQueue::new(), |
| 102 | + topo_queue: super::iter::Queue::new(self.sorting), |
| 103 | + parents: self.parents, |
| 104 | + min_gen: gix_commitgraph::GENERATION_NUMBER_INFINITY, |
| 105 | + buf: vec![], |
| 106 | + }; |
| 107 | + |
| 108 | + // Initial flags for the states of the tips and ends. All of them are |
| 109 | + // seen and added to the explore and indegree queues. The ends are by |
| 110 | + // definition (?) uninteresting and bottom. |
| 111 | + let tip_flags = WalkFlags::Seen | WalkFlags::Explored | WalkFlags::InDegree; |
| 112 | + let end_flags = tip_flags | WalkFlags::Uninteresting | WalkFlags::Bottom; |
| 113 | + |
| 114 | + for (id, flags) in self |
| 115 | + .tips |
| 116 | + .iter() |
| 117 | + .map(|id| (id, tip_flags)) |
| 118 | + .chain(self.ends.iter().map(|id| (id, end_flags))) |
| 119 | + { |
| 120 | + *w.indegrees.entry(*id).or_default() = 1; |
| 121 | + let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; |
| 122 | + let (gen, time) = gen_and_commit_time(commit)?; |
| 123 | + |
| 124 | + if gen < w.min_gen { |
| 125 | + w.min_gen = gen; |
| 126 | + } |
| 127 | + |
| 128 | + w.states.insert(*id, flags); |
| 129 | + w.explore_queue.insert((gen, time), *id); |
| 130 | + w.indegree_queue.insert((gen, time), *id); |
| 131 | + } |
| 132 | + |
| 133 | + // NOTE: Parents of the ends must also be marked uninteresting for some |
| 134 | + // reason. See handle_commit() |
| 135 | + for id in &self.ends { |
| 136 | + let parents = w.collect_all_parents(id)?; |
| 137 | + for (id, _) in parents { |
| 138 | + w.states |
| 139 | + .entry(id) |
| 140 | + .and_modify(|s| *s |= WalkFlags::Uninteresting) |
| 141 | + .or_insert(WalkFlags::Uninteresting | WalkFlags::Seen); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + w.compute_indegrees_to_depth(w.min_gen)?; |
| 146 | + |
| 147 | + // NOTE: in Git the ends are also added to the topo_queue in addition to |
| 148 | + // the tips, but then in simplify_commit() Git is told to ignore it. For |
| 149 | + // now the tests pass. |
| 150 | + for id in self.tips.iter() { |
| 151 | + let i = w.indegrees.get(id).ok_or(Error::MissingIndegreeUnexpected)?; |
| 152 | + |
| 153 | + if *i != 1 { |
| 154 | + continue; |
| 155 | + } |
| 156 | + |
| 157 | + let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; |
| 158 | + let (_, time) = gen_and_commit_time(commit)?; |
| 159 | + let parent_ids = w.collect_all_parents(id)?.into_iter().map(|e| e.0).collect(); |
| 160 | + |
| 161 | + w.topo_queue.push( |
| 162 | + time, |
| 163 | + Info { |
| 164 | + id: *id, |
| 165 | + parent_ids, |
| 166 | + commit_time: Some(time), |
| 167 | + }, |
| 168 | + ); |
| 169 | + } |
| 170 | + |
| 171 | + w.topo_queue.initial_sort(); |
| 172 | + Ok(w) |
| 173 | + } |
| 174 | +} |
0 commit comments