|
1 |
| -// TODO: make this the actual bitflags |
2 |
| -pub(crate) type Flags = u32; |
| 1 | +use crate::{Error, Negotiator}; |
| 2 | +use gix_hash::ObjectId; |
| 3 | +use gix_revision::graph::CommitterTimestamp; |
| 4 | +bitflags::bitflags! { |
| 5 | + /// Whether something can be read or written. |
| 6 | + #[derive(Debug, Default, Copy, Clone)] |
| 7 | + pub struct Flags: u8 { |
| 8 | + /// The revision is known to be in common with the remote |
| 9 | + const COMMON = 1 << 0; |
| 10 | + /// The revision is common and was set by merit of a remote tracking ref (e.g. `refs/heads/origin/main`). |
| 11 | + const COMMON_REF = 1 << 1; |
| 12 | + /// The revision was processed by us and used to avoid processing it again. |
| 13 | + const SEEN = 1 << 2; |
| 14 | + /// The revision was popped off our primary priority queue, used to avoid double-counting of `non_common_revs` |
| 15 | + const POPPED = 1 << 3; |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +pub(crate) struct Algorithm<'find> { |
| 20 | + graph: gix_revision::Graph<'find, Flags>, |
| 21 | + revs: gix_revision::PriorityQueue<CommitterTimestamp, ObjectId>, |
| 22 | + non_common_revs: usize, |
| 23 | +} |
| 24 | + |
| 25 | +impl<'a> Algorithm<'a> { |
| 26 | + pub fn new(graph: gix_revision::Graph<'a, Flags>) -> Self { |
| 27 | + Self { |
| 28 | + graph, |
| 29 | + revs: gix_revision::PriorityQueue::new(), |
| 30 | + non_common_revs: 0, |
| 31 | + } |
| 32 | + } |
| 33 | + |
| 34 | + /// Add `id` to our priority queue and *add* `flags` to it. |
| 35 | + fn add_to_queue(&mut self, id: ObjectId, flags: Flags) -> Result<(), Error> { |
| 36 | + let mut is_common = false; |
| 37 | + let commit = self.graph.try_lookup_and_insert(id, |current| { |
| 38 | + *current |= flags; |
| 39 | + is_common = current.contains(Flags::COMMON); |
| 40 | + })?; |
| 41 | + if let Some(timestamp) = commit.map(|c| c.committer_timestamp()).transpose()? { |
| 42 | + self.revs.insert(timestamp, id); |
| 43 | + if !is_common { |
| 44 | + self.non_common_revs += 1; |
| 45 | + } |
| 46 | + } |
| 47 | + Ok(()) |
| 48 | + } |
| 49 | + |
| 50 | + fn mark_common(&mut self, id: ObjectId, mode: Mark, ancestors: Ancestors) -> Result<(), Error> { |
| 51 | + let mut is_common = false; |
| 52 | + if let Some(commit) = self |
| 53 | + .graph |
| 54 | + .try_lookup_and_insert(id, |current| is_common = current.contains(Flags::COMMON))? |
| 55 | + .filter(|_| !is_common) |
| 56 | + { |
| 57 | + let mut queue = |
| 58 | + gix_revision::PriorityQueue::from_iter(Some((commit.committer_timestamp()?, (id, 0_usize)))); |
| 59 | + if let Mark::ThisCommitAndAncestors = mode { |
| 60 | + let current = self.graph.get_mut(&id).expect("just inserted"); |
| 61 | + *current |= Flags::COMMON; |
| 62 | + if current.contains(Flags::SEEN) && !current.contains(Flags::POPPED) { |
| 63 | + self.non_common_revs -= 1; |
| 64 | + } |
| 65 | + } |
| 66 | + let mut parents = Vec::with_capacity(2); |
| 67 | + while let Some((id, generation)) = queue.pop() { |
| 68 | + if self.graph.get(&id).map_or(true, |d| !d.contains(Flags::SEEN)) { |
| 69 | + self.add_to_queue(id, Flags::SEEN)?; |
| 70 | + } else if matches!(ancestors, Ancestors::AllUnseen) || generation == 0 { |
| 71 | + if let Some(commit) = self.graph.try_lookup_and_insert(id, |_| {})? { |
| 72 | + collect_parents(commit.iter_parents(), &mut parents)?; |
| 73 | + for parent_id in parents.drain(..) { |
| 74 | + let mut prev_flags = Flags::default(); |
| 75 | + if let Some(parent) = self |
| 76 | + .graph |
| 77 | + .try_lookup_and_insert(parent_id, |d| { |
| 78 | + prev_flags = *d; |
| 79 | + *d |= Flags::COMMON; |
| 80 | + })? |
| 81 | + .filter(|_| !prev_flags.contains(Flags::COMMON)) |
| 82 | + { |
| 83 | + if prev_flags.contains(Flags::SEEN) && !prev_flags.contains(Flags::POPPED) { |
| 84 | + self.non_common_revs -= 1; |
| 85 | + } |
| 86 | + queue.insert(parent.committer_timestamp()?, (parent_id, generation + 1)) |
| 87 | + } |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + Ok(()) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +fn collect_parents(parents: gix_revision::graph::commit::Parents<'_>, out: &mut Vec<ObjectId>) -> Result<(), Error> { |
| 98 | + out.clear(); |
| 99 | + for parent in parents { |
| 100 | + out.push(parent.map_err(|err| match err { |
| 101 | + gix_revision::graph::commit::iter_parents::Error::DecodeCommit(err) => Error::DecodeCommit(err), |
| 102 | + gix_revision::graph::commit::iter_parents::Error::DecodeCommitGraph(err) => Error::DecodeCommitInGraph(err), |
| 103 | + })?); |
| 104 | + } |
| 105 | + Ok(()) |
| 106 | +} |
| 107 | + |
| 108 | +impl<'a> Negotiator for Algorithm<'a> { |
| 109 | + fn known_common(&mut self, id: ObjectId) -> Result<(), Error> { |
| 110 | + if self.graph.get(&id).map_or(true, |d| !d.contains(Flags::SEEN)) { |
| 111 | + self.add_to_queue(id, Flags::COMMON_REF | Flags::SEEN)?; |
| 112 | + self.mark_common(id, Mark::AncestorsOnly, Ancestors::DirectUnseen)?; |
| 113 | + } |
| 114 | + Ok(()) |
| 115 | + } |
| 116 | + |
| 117 | + fn add_tip(&mut self, id: ObjectId) -> Result<(), Error> { |
| 118 | + self.add_to_queue(id, Flags::SEEN) |
| 119 | + } |
| 120 | + |
| 121 | + fn next_have(&mut self) -> Option<Result<ObjectId, Error>> { |
| 122 | + let mut parents = Vec::new(); |
| 123 | + loop { |
| 124 | + let id = self.revs.pop().filter(|_| self.non_common_revs != 0)?; |
| 125 | + let flags = self.graph.get_mut(&id).expect("it was added to the graph by now"); |
| 126 | + *flags |= Flags::POPPED; |
| 127 | + |
| 128 | + if !flags.contains(Flags::COMMON) { |
| 129 | + self.non_common_revs -= 1; |
| 130 | + } |
| 131 | + |
| 132 | + let (res, mark) = if flags.contains(Flags::COMMON) { |
| 133 | + (None, Flags::COMMON | Flags::SEEN) |
| 134 | + } else if flags.contains(Flags::COMMON_REF) { |
| 135 | + (Some(id), Flags::COMMON | Flags::SEEN) |
| 136 | + } else { |
| 137 | + (Some(id), Flags::SEEN) |
| 138 | + }; |
| 139 | + |
| 140 | + let commit = match self.graph.try_lookup(&id) { |
| 141 | + Ok(c) => c.expect("it was found before, must still be there"), |
| 142 | + Err(err) => return Some(Err(err.into())), |
| 143 | + }; |
| 144 | + if let Err(err) = collect_parents(commit.iter_parents(), &mut parents) { |
| 145 | + return Some(Err(err)); |
| 146 | + } |
| 147 | + for parent_id in parents.drain(..) { |
| 148 | + if self.graph.get(&parent_id).map_or(true, |d| !d.contains(Flags::SEEN)) { |
| 149 | + if let Err(err) = self.add_to_queue(parent_id, mark) { |
| 150 | + return Some(Err(err)); |
| 151 | + } |
| 152 | + } |
| 153 | + if mark.contains(Flags::COMMON) { |
| 154 | + if let Err(err) = self.mark_common(parent_id, Mark::AncestorsOnly, Ancestors::AllUnseen) { |
| 155 | + return Some(Err(err)); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + if let Some(id) = res { |
| 161 | + return Some(Ok(id)); |
| 162 | + } |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + fn in_common_with_remote(&mut self, id: ObjectId) -> Result<bool, Error> { |
| 167 | + let known_to_be_common = self.graph.get(&id).map_or(false, |d| d.contains(Flags::COMMON)); |
| 168 | + self.mark_common(id, Mark::ThisCommitAndAncestors, Ancestors::DirectUnseen)?; |
| 169 | + Ok(known_to_be_common) |
| 170 | + } |
| 171 | +} |
| 172 | + |
| 173 | +enum Mark { |
| 174 | + AncestorsOnly, |
| 175 | + ThisCommitAndAncestors, |
| 176 | +} |
| 177 | + |
| 178 | +enum Ancestors { |
| 179 | + /// Traverse only the parents of a commit. |
| 180 | + DirectUnseen, |
| 181 | + /// Traverse all ancestors that weren't yet seen. |
| 182 | + AllUnseen, |
| 183 | +} |
0 commit comments