Showing the changes to git-diff/src/tree/changes.rs (1 of 7 files). Show all
Unified · Split
| @@ -0,0 +1,364 @@ | |||
| 1 | use crate::{ | ||
| 2 | tree, | ||
| 3 | tree::{changes::Error::Cancelled, visit::Change, TreeInfo, TreeInfoPair}, | ||
| 4 | }; | ||
| 5 | use git_hash::{oid, ObjectId}; | ||
| 6 | use git_object::immutable; | ||
| 7 | use quick_error::quick_error; | ||
| 8 | use std::{borrow::BorrowMut, collections::VecDeque}; | ||
| 9 | |||
| 10 | quick_error! { | ||
| 11 | #[derive(Debug)] | ||
| 12 | pub enum Error { | ||
| 13 | NotFound(oid: ObjectId) { | ||
| 14 | display("The object {} referenced by the tree was not found in the database", oid) | ||
| 15 | } | ||
| 16 | Cancelled { | ||
| 17 | display("The delegate cancelled the operation") | ||
| 18 | } | ||
| 19 | EntriesDecode(err: immutable::object::decode::Error) { | ||
| 20 | display("tree entries could not be decoded.") | ||
| 21 | from() | ||
| 22 | source(err) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | impl<'a> tree::Changes<'a> { | ||
| 28 | /// that need to be applied to `self` to get `other`. | ||
| 29 | /// TODO: Talk about choices in error handling and why Option<T> is enough, as opposed to the usual Result<Option<T>> | ||
| 30 | /// Talk about progress (done via delegate if needed) | ||
| 31 | /// | ||
| 32 | /// # Notes | ||
| 33 | /// | ||
| 34 | /// * Tree entries are expected to be ordered using [`tree-entry-comparison`][git_cmp_c] (the same [in Rust][git_cmp_rs]) | ||
| 35 | /// | ||
| 36 | /// [git_cmp_c]: https://github.com/git/git/blob/311531c9de557d25ac087c1637818bd2aad6eb3a/tree-diff.c#L49:L65 | ||
| 37 | /// [git_cmp_rs]: https://github.com/Byron/gitoxide/blob/a4d5f99c8dc99bf814790928a3bf9649cd99486b/git-object/src/mutable/tree.rs#L52-L55 | ||
| 38 | /// | ||
| 39 | /// * it does a breadth first iteration as buffer space only fits two trees, the current one on the one we compare with. | ||
| 40 | /// * does not do rename tracking but attempts to reduce allocations to zero (so performance is mostly determined | ||
| 41 | /// by the delegate implementation which should be as specific as possible. | ||
| 42 | /// * cycle checking is not performed, but can be performed in the delegate | ||
| 43 | /// * [std::mem::ManuallyDrop] is used because `Peekable` is needed. When using it as wrapper around our no-drop iterators, all of the sudden | ||
| 44 | /// borrowcheck complains as Drop is present (even though it's not) | ||
| 45 | pub fn needed_to_obtain<LocateFn, R, StateMut>( | ||
| 46 | mut self, | ||
| 47 | other: immutable::TreeIter<'a>, | ||
| 48 | mut state: StateMut, | ||
| 49 | mut locate: LocateFn, | ||
| 50 | delegate: &mut R, | ||
| 51 | ) -> Result<(), Error> | ||
| 52 | where | ||
| 53 | LocateFn: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Option<immutable::tree::TreeIter<'b>>, | ||
| 54 | R: tree::Visit, | ||
| 55 | StateMut: BorrowMut<tree::State<R::PathId>>, | ||
| 56 | { | ||
| 57 | let state = state.borrow_mut(); | ||
| 58 | state.clear(); | ||
| 59 | let mut lhs_entries = peekable(self.0.take().unwrap_or_default()); | ||
| 60 | let mut rhs_entries = peekable(other); | ||
| 61 | let mut avoid_popping_path: Option<()> = None; | ||
| 62 | |||
| 63 | loop { | ||
| 64 | if avoid_popping_path.take().is_none() { | ||
| 65 | delegate.pop_path_component(); | ||
| 66 | } | ||
| 67 | match (lhs_entries.next(), rhs_entries.next()) { | ||
| 68 | (None, None) => { | ||
| 69 | match state.trees.pop_front() { | ||
| 70 | Some((None, Some(rhs))) => { | ||
| 71 | delegate.set_current_path(rhs.parent_path_id.clone()); | ||
| 72 | rhs_entries = | ||
| 73 | peekable(locate(&rhs.tree_id, &mut state.buf2).ok_or(Error::NotFound(rhs.tree_id))?); | ||
| 74 | } | ||
| 75 | Some((Some(lhs), Some(rhs))) => { | ||
| 76 | delegate.set_current_path(lhs.parent_path_id.clone()); | ||
| 77 | lhs_entries = | ||
| 78 | peekable(locate(&lhs.tree_id, &mut state.buf1).ok_or(Error::NotFound(lhs.tree_id))?); | ||
| 79 | rhs_entries = | ||
| 80 | peekable(locate(&rhs.tree_id, &mut state.buf2).ok_or(Error::NotFound(rhs.tree_id))?); | ||
| 81 | } | ||
| 82 | Some((Some(lhs), None)) => { | ||
| 83 | delegate.set_current_path(lhs.parent_path_id.clone()); | ||
| 84 | lhs_entries = | ||
| 85 | peekable(locate(&lhs.tree_id, &mut state.buf1).ok_or(Error::NotFound(lhs.tree_id))?); | ||
| 86 | } | ||
| 87 | Some((None, None)) => unreachable!("BUG: it makes no sense to fill the stack with empties"), | ||
| 88 | None => return Ok(()), | ||
| 89 | }; | ||
| 90 | avoid_popping_path = Some(()); | ||
| 91 | } | ||
| 92 | (Some(lhs), Some(rhs)) => { | ||
| 93 | use std::cmp::Ordering::*; | ||
| 94 | let (lhs, rhs) = (lhs?, rhs?); | ||
| 95 | match lhs.filename.cmp(rhs.filename) { | ||
| 96 | Equal => handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, &mut state.trees, delegate)?, | ||
| 97 | Less => catchup_lhs_with_rhs(&mut lhs_entries, lhs, rhs, &mut state.trees, delegate)?, | ||
| 98 | Greater => catchup_rhs_with_lhs(&mut rhs_entries, lhs, rhs, &mut state.trees, delegate)?, | ||
| 99 | } | ||
| 100 | } | ||
| 101 | (Some(lhs), None) => { | ||
| 102 | let lhs = lhs?; | ||
| 103 | delete_entry_schedule_recursion(lhs, &mut state.trees, delegate)?; | ||
| 104 | } | ||
| 105 | (None, Some(rhs)) => { | ||
| 106 | let rhs = rhs?; | ||
| 107 | add_entry_schedule_recursion(rhs, &mut state.trees, delegate)?; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | fn delete_entry_schedule_recursion<R: tree::Visit>( | ||
| 115 | entry: immutable::tree::Entry<'_>, | ||
| 116 | queue: &mut VecDeque<TreeInfoPair<R::PathId>>, | ||
| 117 | delegate: &mut R, | ||
| 118 | ) -> Result<(), Error> { | ||
| 119 | delegate.push_path_component(entry.filename); | ||
| 120 | if delegate | ||
| 121 | .visit(Change::Deletion { | ||
| 122 | entry_mode: entry.mode, | ||
| 123 | oid: entry.oid.to_owned(), | ||
| 124 | }) | ||
| 125 | .cancelled() | ||
| 126 | { | ||
| 127 | return Err(Error::Cancelled); | ||
| 128 | } | ||
| 129 | if entry.mode.is_tree() { | ||
| 130 | delegate.pop_path_component(); | ||
| 131 | let path_id = delegate.push_tracked_path_component(entry.filename); | ||
| 132 | queue.push_back(( | ||
| 133 | Some(TreeInfo { | ||
| 134 | tree_id: entry.oid.to_owned(), | ||
| 135 | parent_path_id: path_id, | ||
| 136 | }), | ||
| 137 | None, | ||
| 138 | )); | ||
| 139 | } | ||
| 140 | Ok(()) | ||
| 141 | } | ||
| 142 | |||
| 143 | fn add_entry_schedule_recursion<R: tree::Visit>( | ||
| 144 | entry: immutable::tree::Entry<'_>, | ||
| 145 | queue: &mut VecDeque<TreeInfoPair<R::PathId>>, | ||
| 146 | delegate: &mut R, | ||
| 147 | ) -> Result<(), Error> { | ||
| 148 | delegate.push_path_component(entry.filename); | ||
| 149 | if delegate | ||
| 150 | .visit(Change::Addition { | ||
| 151 | entry_mode: entry.mode, | ||
| 152 | oid: entry.oid.to_owned(), | ||
| 153 | }) | ||
| 154 | .cancelled() | ||
| 155 | { | ||
| 156 | return Err(Error::Cancelled); | ||
| 157 | } | ||
| 158 | if entry.mode.is_tree() { | ||
| 159 | delegate.pop_path_component(); | ||
| 160 | let path_id = delegate.push_tracked_path_component(entry.filename); | ||
| 161 | queue.push_back(( | ||
| 162 | None, | ||
| 163 | Some(TreeInfo { | ||
| 164 | tree_id: entry.oid.to_owned(), | ||
| 165 | parent_path_id: path_id, | ||
| 166 | }), | ||
| 167 | )) | ||
| 168 | } | ||
| 169 | Ok(()) | ||
| 170 | } | ||
| 171 | fn catchup_rhs_with_lhs<R: tree::Visit>( | ||
| 172 | rhs_entries: &mut IteratorType<immutable::TreeIter<'_>>, | ||
| 173 | lhs: immutable::tree::Entry<'_>, | ||
| 174 | rhs: immutable::tree::Entry<'_>, | ||
| 175 | queue: &mut VecDeque<TreeInfoPair<R::PathId>>, | ||
| 176 | delegate: &mut R, | ||
| 177 | ) -> Result<(), Error> { | ||
| 178 | use std::cmp::Ordering::*; | ||
| 179 | add_entry_schedule_recursion(rhs, queue, delegate)?; | ||
| 180 | loop { | ||
| 181 | match rhs_entries.peek() { | ||
| 182 | Some(Ok(rhs)) => match lhs.filename.cmp(rhs.filename) { | ||
| 183 | Equal => { | ||
| 184 | let rhs = rhs_entries.next().transpose()?.expect("the peeked item tobe present"); | ||
| 185 | delegate.pop_path_component(); | ||
| 186 | handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, queue, delegate)?; | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | Greater => { | ||
| 190 | let rhs = rhs_entries.next().transpose()?.expect("the peeked item tobe present"); | ||
| 191 | delegate.pop_path_component(); | ||
| 192 | add_entry_schedule_recursion(rhs, queue, delegate)?; | ||
| 193 | } | ||
| 194 | Less => { | ||
| 195 | delegate.pop_path_component(); | ||
| 196 | delete_entry_schedule_recursion(lhs, queue, delegate)?; | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | }, | ||
| 200 | Some(Err(err)) => return Err(Error::EntriesDecode(err.to_owned())), | ||
| 201 | None => { | ||
| 202 | delegate.pop_path_component(); | ||
| 203 | delete_entry_schedule_recursion(lhs, queue, delegate)?; | ||
| 204 | break; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | Ok(()) | ||
| 209 | } | ||
| 210 | |||
| 211 | fn catchup_lhs_with_rhs<R: tree::Visit>( | ||
| 212 | lhs_entries: &mut IteratorType<immutable::TreeIter<'_>>, | ||
| 213 | lhs: immutable::tree::Entry<'_>, | ||
| 214 | rhs: immutable::tree::Entry<'_>, | ||
| 215 | queue: &mut VecDeque<TreeInfoPair<R::PathId>>, | ||
| 216 | delegate: &mut R, | ||
| 217 | ) -> Result<(), Error> { | ||
| 218 | use std::cmp::Ordering::*; | ||
| 219 | delete_entry_schedule_recursion(lhs, queue, delegate)?; | ||
| 220 | loop { | ||
| 221 | match lhs_entries.peek() { | ||
| 222 | Some(Ok(lhs)) => match lhs.filename.cmp(rhs.filename) { | ||
| 223 | Equal => { | ||
| 224 | let lhs = lhs_entries.next().expect("the peeked item to be present")?; | ||
| 225 | delegate.pop_path_component(); | ||
| 226 | handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, queue, delegate)?; | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | Less => { | ||
| 230 | let lhs = lhs_entries.next().expect("the peeked item to be present")?; | ||
| 231 | delegate.pop_path_component(); | ||
| 232 | delete_entry_schedule_recursion(lhs, queue, delegate)?; | ||
| 233 | } | ||
| 234 | Greater => { | ||
| 235 | delegate.pop_path_component(); | ||
| 236 | add_entry_schedule_recursion(rhs, queue, delegate)?; | ||
| 237 | break; | ||
| 238 | } | ||
| 239 | }, | ||
| 240 | Some(Err(err)) => return Err(Error::EntriesDecode(err.to_owned())), | ||
| 241 | None => { | ||
| 242 | delegate.pop_path_component(); | ||
| 243 | add_entry_schedule_recursion(rhs, queue, delegate)?; | ||
| 244 | break; | ||
| 245 | } | ||
| 246 | } | ||
| 247 | } | ||
| 248 | Ok(()) | ||
| 249 | } | ||
| 250 | |||
| 251 | fn handle_lhs_and_rhs_with_equal_filenames<R: tree::Visit>( | ||
| 252 | lhs: immutable::tree::Entry<'_>, | ||
| 253 | rhs: immutable::tree::Entry<'_>, | ||
| 254 | queue: &mut VecDeque<TreeInfoPair<R::PathId>>, | ||
| 255 | delegate: &mut R, | ||
| 256 | ) -> Result<(), Error> { | ||
| 257 | use git_object::tree::EntryMode::*; | ||
| 258 | match (lhs.mode, rhs.mode) { | ||
| 259 | (Tree, Tree) => { | ||
| 260 | let path_id = delegate.push_tracked_path_component(lhs.filename); | ||
| 261 | if lhs.oid != rhs.oid | ||
| 262 | && delegate | ||
| 263 | .visit(Change::Modification { | ||
| 264 | previous_entry_mode: lhs.mode, | ||
| 265 | previous_oid: lhs.oid.to_owned(), | ||
| 266 | entry_mode: rhs.mode, | ||
| 267 | oid: rhs.oid.to_owned(), | ||
| 268 | }) | ||
| 269 | .cancelled() | ||
| 270 | { | ||
| 271 | return Err(Cancelled); | ||
| 272 | } | ||
| 273 | queue.push_back(( | ||
| 274 | Some(TreeInfo { | ||
| 275 | tree_id: lhs.oid.to_owned(), | ||
| 276 | parent_path_id: path_id.clone(), | ||
| 277 | }), | ||
| 278 | Some(TreeInfo { | ||
| 279 | tree_id: rhs.oid.to_owned(), | ||
| 280 | parent_path_id: path_id, | ||
| 281 | }), | ||
| 282 | )); | ||
| 283 | } | ||
| 284 | (lhs_mode, Tree) if lhs_mode.is_no_tree() => { | ||
| 285 | let path_id = delegate.push_tracked_path_component(lhs.filename); | ||
| 286 | if delegate | ||
| 287 | .visit(Change::Deletion { | ||
| 288 | entry_mode: lhs.mode, | ||
| 289 | oid: lhs.oid.to_owned(), | ||
| 290 | }) | ||
| 291 | .cancelled() | ||
| 292 | { | ||
| 293 | return Err(Cancelled); | ||
| 294 | }; | ||
| 295 | if delegate | ||
| 296 | .visit(Change::Addition { | ||
| 297 | entry_mode: rhs.mode, | ||
| 298 | oid: rhs.oid.to_owned(), | ||
| 299 | }) | ||
| 300 | .cancelled() | ||
| 301 | { | ||
| 302 | return Err(Cancelled); | ||
| 303 | }; | ||
| 304 | queue.push_back(( | ||
| 305 | None, | ||
| 306 | Some(TreeInfo { | ||
| 307 | tree_id: rhs.oid.to_owned(), | ||
| 308 | parent_path_id: path_id, | ||
| 309 | }), | ||
| 310 | )); | ||
| 311 | } | ||
| 312 | (Tree, rhs_mode) if rhs_mode.is_no_tree() => { | ||
| 313 | let path_id = delegate.push_tracked_path_component(lhs.filename); | ||
| 314 | if delegate | ||
| 315 | .visit(Change::Deletion { | ||
| 316 | entry_mode: lhs.mode, | ||
| 317 | oid: lhs.oid.to_owned(), | ||
| 318 | }) | ||
| 319 | .cancelled() | ||
| 320 | { | ||
| 321 | return Err(Error::Cancelled); | ||
| 322 | } | ||
| 323 | if delegate | ||
| 324 | .visit(Change::Addition { | ||
| 325 | entry_mode: rhs.mode, | ||
| 326 | oid: rhs.oid.to_owned(), | ||
| 327 | }) | ||
| 328 | .cancelled() | ||
| 329 | { | ||
| 330 | return Err(Cancelled); | ||
| 331 | }; | ||
| 332 | queue.push_back(( | ||
| 333 | Some(TreeInfo { | ||
| 334 | tree_id: lhs.oid.to_owned(), | ||
| 335 | parent_path_id: path_id, | ||
| 336 | }), | ||
| 337 | None, | ||
| 338 | )); | ||
| 339 | } | ||
| 340 | (lhs_non_tree, rhs_non_tree) => { | ||
| 341 | delegate.push_path_component(lhs.filename); | ||
| 342 | debug_assert!(lhs_non_tree.is_no_tree() && rhs_non_tree.is_no_tree()); | ||
| 343 | if lhs.oid != rhs.oid | ||
| 344 | && delegate | ||
| 345 | .visit(Change::Modification { | ||
| 346 | previous_entry_mode: lhs.mode, | ||
| 347 | previous_oid: lhs.oid.to_owned(), | ||
| 348 | entry_mode: rhs.mode, | ||
| 349 | oid: rhs.oid.to_owned(), | ||
| 350 | }) | ||
| 351 | .cancelled() | ||
| 352 | { | ||
| 353 | return Err(Error::Cancelled); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | }; | ||
| 357 | Ok(()) | ||
| 358 | } | ||
| 359 | |||
| 360 | type IteratorType<I> = std::mem::ManuallyDrop<std::iter::Peekable<I>>; | ||
| 361 | |||
| 362 | fn peekable<I: Iterator>(iter: I) -> IteratorType<I> { | ||
| 363 | std::mem::ManuallyDrop::new(iter.peekable()) | ||
| 364 | } | ||