Unified · Split
| @@ -1,336 +0,0 @@ | |||
| 1 | use crate::{bstr::BStr, Repository, Tree}; | ||
| 2 | |||
| 3 | /// Returned by the `for_each` function to control flow. | ||
| 4 | #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] | ||
| 5 | pub enum Action { | ||
| 6 | /// Continue the traversal of changes. | ||
| 7 | Continue, | ||
| 8 | /// Stop the traversal of changes and stop calling this function. | ||
| 9 | Cancel, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Default for Action { | ||
| 13 | fn default() -> Self { | ||
| 14 | Action::Continue | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Represents any possible change in order to turn one tree into another. | ||
| 19 | #[derive(Debug, Clone, Copy)] | ||
| 20 | pub struct Change<'a, 'old, 'new> { | ||
| 21 | /// The location of the file or directory described by `event`, if tracking was enabled. | ||
| 22 | /// | ||
| 23 | /// Otherwise this value is always an empty path. | ||
| 24 | pub location: &'a BStr, | ||
| 25 | /// The diff event itself to provide information about what would need to change. | ||
| 26 | pub event: change::Event<'old, 'new>, | ||
| 27 | } | ||
| 28 | |||
| 29 | /// | ||
| 30 | pub mod change { | ||
| 31 | use git_object::tree::EntryMode; | ||
| 32 | |||
| 33 | use crate::{bstr::ByteSlice, Id, Repository}; | ||
| 34 | |||
| 35 | /// An event emitted when finding differences between two trees. | ||
| 36 | #[derive(Debug, Clone, Copy)] | ||
| 37 | pub enum Event<'old, 'new> { | ||
| 38 | /// An entry was added, like the addition of a file or directory. | ||
| 39 | Addition { | ||
| 40 | /// The mode of the added entry. | ||
| 41 | entry_mode: git_object::tree::EntryMode, | ||
| 42 | /// The object id of the added entry. | ||
| 43 | id: Id<'new>, | ||
| 44 | }, | ||
| 45 | /// An entry was deleted, like the deletion of a file or directory. | ||
| 46 | Deletion { | ||
| 47 | /// The mode of the deleted entry. | ||
| 48 | entry_mode: git_object::tree::EntryMode, | ||
| 49 | /// The object id of the deleted entry. | ||
| 50 | id: Id<'old>, | ||
| 51 | }, | ||
| 52 | /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning | ||
| 53 | /// a file into a symbolic link adjusts its mode. | ||
| 54 | Modification { | ||
| 55 | /// The mode of the entry before the modification. | ||
| 56 | previous_entry_mode: git_object::tree::EntryMode, | ||
| 57 | /// The object id of the entry before the modification. | ||
| 58 | previous_id: Id<'old>, | ||
| 59 | |||
| 60 | /// The mode of the entry after the modification. | ||
| 61 | entry_mode: git_object::tree::EntryMode, | ||
| 62 | /// The object id after the modification. | ||
| 63 | id: Id<'new>, | ||
| 64 | }, | ||
| 65 | } | ||
| 66 | |||
| 67 | /// A platform to keep temporary information to perform line diffs. | ||
| 68 | pub struct DiffPlatform<'old, 'new> { | ||
| 69 | old: crate::Object<'old>, | ||
| 70 | new: crate::Object<'new>, | ||
| 71 | algo: git_diff::text::Algorithm, | ||
| 72 | } | ||
| 73 | |||
| 74 | impl<'old, 'new> Event<'old, 'new> { | ||
| 75 | fn repo(&self) -> &Repository { | ||
| 76 | match self { | ||
| 77 | Event::Addition { id, .. } => id.repo, | ||
| 78 | Event::Deletion { id, .. } => id.repo, | ||
| 79 | Event::Modification { id, .. } => id.repo, | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | impl<'old, 'new> Event<'old, 'new> { | ||
| 85 | /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification] | ||
| 86 | /// or one of the entries to compare is not a blob. | ||
| 87 | pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, crate::object::find::existing::Error>> { | ||
| 88 | // let algo = self.repo().config.diff_algorithm()?; | ||
| 89 | match self { | ||
| 90 | Event::Modification { | ||
| 91 | previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, | ||
| 92 | previous_id, | ||
| 93 | entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, | ||
| 94 | id, | ||
| 95 | } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) { | ||
| 96 | Ok((old, new)) => Some(Ok(DiffPlatform { | ||
| 97 | old, | ||
| 98 | new, | ||
| 99 | algo: git_diff::text::Algorithm::Myers, | ||
| 100 | })), | ||
| 101 | Err(err) => Some(Err(err)), | ||
| 102 | }, | ||
| 103 | _ => None, | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | impl<'old, 'new> DiffPlatform<'old, 'new> { | ||
| 109 | /// Perform a diff on lines between the old and the new version of a blob. | ||
| 110 | /// The algorithm is determined by the `diff.algorithm` configuration. | ||
| 111 | /// Note that the [`Sink`][git_diff::text::imara::Sink] implementation is | ||
| 112 | /// what makes the diff usable and relies heavily on what the caller requires, as created by `make_sink`. | ||
| 113 | pub fn lines<FnS, S>(&self, new_sink: FnS) -> S::Out | ||
| 114 | where | ||
| 115 | FnS: for<'a> FnOnce(&git_diff::text::imara::intern::InternedInput<&'a [u8]>) -> S, | ||
| 116 | S: git_diff::text::imara::Sink, | ||
| 117 | { | ||
| 118 | git_diff::text::with( | ||
| 119 | self.old.data.as_bstr(), | ||
| 120 | self.new.data.as_bstr(), | ||
| 121 | git_diff::text::Algorithm::Myers, // TODO: use diff.algorithm | ||
| 122 | // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably | ||
| 123 | // OK to just know how these objects are saved to know what constitutes a line. | ||
| 124 | git_diff::text::imara::intern::InternedInput::new, | ||
| 125 | new_sink, | ||
| 126 | ) | ||
| 127 | .1 | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Diffing | ||
| 133 | impl<'repo> Tree<'repo> { | ||
| 134 | /// Return a platform to see the changes needed to create other trees, for instance. | ||
| 135 | /// | ||
| 136 | /// # Performance | ||
| 137 | /// | ||
| 138 | /// It's highly recommended to set an object cache to avoid extracting the same object multiple times. | ||
| 139 | pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { | ||
| 140 | Platform { | ||
| 141 | state: Default::default(), | ||
| 142 | lhs: self, | ||
| 143 | tracking: None, | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | /// The diffing platform returned by [`Tree::changes()`]. | ||
| 149 | #[derive(Clone)] | ||
| 150 | pub struct Platform<'a, 'repo> { | ||
| 151 | state: git_diff::tree::State, | ||
| 152 | lhs: &'a Tree<'repo>, | ||
| 153 | tracking: Option<Tracking>, | ||
| 154 | } | ||
| 155 | |||
| 156 | #[derive(Clone, Copy)] | ||
| 157 | enum Tracking { | ||
| 158 | FileName, | ||
| 159 | Path, | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Configuration | ||
| 163 | impl<'a, 'repo> Platform<'a, 'repo> { | ||
| 164 | /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. | ||
| 165 | pub fn track_filename(&mut self) -> &mut Self { | ||
| 166 | self.tracking = Some(Tracking::FileName); | ||
| 167 | self | ||
| 168 | } | ||
| 169 | |||
| 170 | /// Keep track of the entire path of a change, relative to the repository. | ||
| 171 | /// | ||
| 172 | /// This makes the [`location`][Change::location] field usable. | ||
| 173 | pub fn track_path(&mut self) -> &mut Self { | ||
| 174 | self.tracking = Some(Tracking::Path); | ||
| 175 | self | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | /// | ||
| 180 | pub mod for_each { | ||
| 181 | use super::{change, Action, Change, Platform, Tracking}; | ||
| 182 | use crate::bstr::{BStr, BString, ByteSlice, ByteVec}; | ||
| 183 | use crate::ext::ObjectIdExt; | ||
| 184 | use crate::{Repository, Tree}; | ||
| 185 | use git_object::TreeRefIter; | ||
| 186 | use git_odb::FindExt; | ||
| 187 | use std::collections::VecDeque; | ||
| 188 | |||
| 189 | /// The error return by methods on the [diff platform][Platform]. | ||
| 190 | #[derive(Debug, thiserror::Error)] | ||
| 191 | #[allow(missing_docs)] | ||
| 192 | pub enum Error { | ||
| 193 | #[error(transparent)] | ||
| 194 | Diff(#[from] git_diff::tree::changes::Error), | ||
| 195 | #[error("The user-provided callback failed")] | ||
| 196 | ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>), | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Add the item to compare to. | ||
| 200 | impl<'a, 'old> Platform<'a, 'old> { | ||
| 201 | /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. | ||
| 202 | pub fn for_each_to_obtain_tree<'new, E>( | ||
| 203 | &mut self, | ||
| 204 | other: &Tree<'new>, | ||
| 205 | for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>, | ||
| 206 | ) -> Result<(), Error> | ||
| 207 | where | ||
| 208 | E: std::error::Error + Sync + Send + 'static, | ||
| 209 | { | ||
| 210 | let repo = self.lhs.repo; | ||
| 211 | let mut delegate = Delegate { | ||
| 212 | repo: self.lhs.repo, | ||
| 213 | other_repo: other.repo, | ||
| 214 | tracking: self.tracking, | ||
| 215 | location: BString::default(), | ||
| 216 | path_deque: Default::default(), | ||
| 217 | visit: for_each, | ||
| 218 | err: None, | ||
| 219 | }; | ||
| 220 | git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( | ||
| 221 | TreeRefIter::from_bytes(&other.data), | ||
| 222 | &mut self.state, | ||
| 223 | |oid, buf| repo.objects.find_tree_iter(oid, buf), | ||
| 224 | &mut delegate, | ||
| 225 | )?; | ||
| 226 | match delegate.err { | ||
| 227 | Some(err) => Err(Error::ForEach(Box::new(err))), | ||
| 228 | None => Ok(()), | ||
| 229 | } | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | struct Delegate<'old, 'new, VisitFn, E> { | ||
| 234 | repo: &'old Repository, | ||
| 235 | other_repo: &'new Repository, | ||
| 236 | tracking: Option<Tracking>, | ||
| 237 | location: BString, | ||
| 238 | path_deque: VecDeque<BString>, | ||
| 239 | visit: VisitFn, | ||
| 240 | err: Option<E>, | ||
| 241 | } | ||
| 242 | |||
| 243 | impl<A, B> Delegate<'_, '_, A, B> { | ||
| 244 | fn pop_element(&mut self) { | ||
| 245 | if let Some(pos) = self.location.rfind_byte(b'/') { | ||
| 246 | self.location.resize(pos, 0); | ||
| 247 | } else { | ||
| 248 | self.location.clear(); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | fn push_element(&mut self, name: &BStr) { | ||
| 253 | if !self.location.is_empty() { | ||
| 254 | self.location.push(b'/'); | ||
| 255 | } | ||
| 256 | self.location.push_str(name); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E> | ||
| 261 | where | ||
| 262 | VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>, | ||
| 263 | E: std::error::Error + Sync + Send + 'static, | ||
| 264 | { | ||
| 265 | fn pop_front_tracked_path_and_set_current(&mut self) { | ||
| 266 | if let Some(Tracking::Path) = self.tracking { | ||
| 267 | self.location = self | ||
| 268 | .path_deque | ||
| 269 | .pop_front() | ||
| 270 | .expect("every call is matched with push_tracked_path_component"); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | fn push_back_tracked_path_component(&mut self, component: &BStr) { | ||
| 275 | if let Some(Tracking::Path) = self.tracking { | ||
| 276 | self.push_element(component); | ||
| 277 | self.path_deque.push_back(self.location.clone()); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | fn push_path_component(&mut self, component: &BStr) { | ||
| 282 | match self.tracking { | ||
| 283 | Some(Tracking::FileName) => { | ||
| 284 | self.location.clear(); | ||
| 285 | self.location.push_str(component); | ||
| 286 | } | ||
| 287 | Some(Tracking::Path) => { | ||
| 288 | self.push_element(component); | ||
| 289 | } | ||
| 290 | None => {} | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | fn pop_path_component(&mut self) { | ||
| 295 | if let Some(Tracking::Path) = self.tracking { | ||
| 296 | self.pop_element(); | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { | ||
| 301 | use git_diff::tree::visit::Change::*; | ||
| 302 | let event = match change { | ||
| 303 | Addition { entry_mode, oid } => change::Event::Addition { | ||
| 304 | entry_mode, | ||
| 305 | id: oid.attach(self.other_repo), | ||
| 306 | }, | ||
| 307 | Deletion { entry_mode, oid } => change::Event::Deletion { | ||
| 308 | entry_mode, | ||
| 309 | id: oid.attach(self.repo), | ||
| 310 | }, | ||
| 311 | Modification { | ||
| 312 | previous_entry_mode, | ||
| 313 | previous_oid, | ||
| 314 | entry_mode, | ||
| 315 | oid, | ||
| 316 | } => change::Event::Modification { | ||
| 317 | previous_entry_mode, | ||
| 318 | entry_mode, | ||
| 319 | previous_id: previous_oid.attach(self.repo), | ||
| 320 | id: oid.attach(self.other_repo), | ||
| 321 | }, | ||
| 322 | }; | ||
| 323 | match (self.visit)(Change { | ||
| 324 | event, | ||
| 325 | location: self.location.as_ref(), | ||
| 326 | }) { | ||
| 327 | Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, | ||
| 328 | Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, | ||
| 329 | Err(err) => { | ||
| 330 | self.err = Some(err); | ||
| 331 | git_diff::tree::visit::Action::Cancel | ||
| 332 | } | ||
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| @@ -0,0 +1,99 @@ | |||
| 1 | use git_object::tree::EntryMode; | ||
| 2 | |||
| 3 | use crate::{bstr::ByteSlice, Id, Repository}; | ||
| 4 | |||
| 5 | /// An event emitted when finding differences between two trees. | ||
| 6 | #[derive(Debug, Clone, Copy)] | ||
| 7 | pub enum Event<'old, 'new> { | ||
| 8 | /// An entry was added, like the addition of a file or directory. | ||
| 9 | Addition { | ||
| 10 | /// The mode of the added entry. | ||
| 11 | entry_mode: git_object::tree::EntryMode, | ||
| 12 | /// The object id of the added entry. | ||
| 13 | id: Id<'new>, | ||
| 14 | }, | ||
| 15 | /// An entry was deleted, like the deletion of a file or directory. | ||
| 16 | Deletion { | ||
| 17 | /// The mode of the deleted entry. | ||
| 18 | entry_mode: git_object::tree::EntryMode, | ||
| 19 | /// The object id of the deleted entry. | ||
| 20 | id: Id<'old>, | ||
| 21 | }, | ||
| 22 | /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning | ||
| 23 | /// a file into a symbolic link adjusts its mode. | ||
| 24 | Modification { | ||
| 25 | /// The mode of the entry before the modification. | ||
| 26 | previous_entry_mode: git_object::tree::EntryMode, | ||
| 27 | /// The object id of the entry before the modification. | ||
| 28 | previous_id: Id<'old>, | ||
| 29 | |||
| 30 | /// The mode of the entry after the modification. | ||
| 31 | entry_mode: git_object::tree::EntryMode, | ||
| 32 | /// The object id after the modification. | ||
| 33 | id: Id<'new>, | ||
| 34 | }, | ||
| 35 | } | ||
| 36 | |||
| 37 | /// A platform to keep temporary information to perform line diffs. | ||
| 38 | pub struct DiffPlatform<'old, 'new> { | ||
| 39 | old: crate::Object<'old>, | ||
| 40 | new: crate::Object<'new>, | ||
| 41 | algo: git_diff::text::Algorithm, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<'old, 'new> Event<'old, 'new> { | ||
| 45 | fn repo(&self) -> &Repository { | ||
| 46 | match self { | ||
| 47 | Event::Addition { id, .. } => id.repo, | ||
| 48 | Event::Deletion { id, .. } => id.repo, | ||
| 49 | Event::Modification { id, .. } => id.repo, | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | impl<'old, 'new> Event<'old, 'new> { | ||
| 55 | /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification] | ||
| 56 | /// or one of the entries to compare is not a blob. | ||
| 57 | pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, crate::object::find::existing::Error>> { | ||
| 58 | // let algo = self.repo().config.diff_algorithm()?; | ||
| 59 | match self { | ||
| 60 | Event::Modification { | ||
| 61 | previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, | ||
| 62 | previous_id, | ||
| 63 | entry_mode: EntryMode::BlobExecutable | EntryMode::Blob, | ||
| 64 | id, | ||
| 65 | } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) { | ||
| 66 | Ok((old, new)) => Some(Ok(DiffPlatform { | ||
| 67 | old, | ||
| 68 | new, | ||
| 69 | algo: git_diff::text::Algorithm::Myers, | ||
| 70 | })), | ||
| 71 | Err(err) => Some(Err(err)), | ||
| 72 | }, | ||
| 73 | _ => None, | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | impl<'old, 'new> DiffPlatform<'old, 'new> { | ||
| 79 | /// Perform a diff on lines between the old and the new version of a blob. | ||
| 80 | /// The algorithm is determined by the `diff.algorithm` configuration. | ||
| 81 | /// Note that the [`Sink`][git_diff::text::imara::Sink] implementation is | ||
| 82 | /// what makes the diff usable and relies heavily on what the caller requires, as created by `make_sink`. | ||
| 83 | pub fn lines<FnS, S>(&self, new_sink: FnS) -> S::Out | ||
| 84 | where | ||
| 85 | FnS: for<'a> FnOnce(&git_diff::text::imara::intern::InternedInput<&'a [u8]>) -> S, | ||
| 86 | S: git_diff::text::imara::Sink, | ||
| 87 | { | ||
| 88 | git_diff::text::with( | ||
| 89 | self.old.data.as_bstr(), | ||
| 90 | self.new.data.as_bstr(), | ||
| 91 | git_diff::text::Algorithm::Myers, // TODO: use diff.algorithm | ||
| 92 | // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably | ||
| 93 | // OK to just know how these objects are saved to know what constitutes a line. | ||
| 94 | git_diff::text::imara::intern::InternedInput::new, | ||
| 95 | new_sink, | ||
| 96 | ) | ||
| 97 | .1 | ||
| 98 | } | ||
| 99 | } | ||
| @@ -0,0 +1,155 @@ | |||
| 1 | use super::{change, Action, Change, Platform, Tracking}; | ||
| 2 | use crate::bstr::{BStr, BString, ByteSlice, ByteVec}; | ||
| 3 | use crate::ext::ObjectIdExt; | ||
| 4 | use crate::{Repository, Tree}; | ||
| 5 | use git_object::TreeRefIter; | ||
| 6 | use git_odb::FindExt; | ||
| 7 | use std::collections::VecDeque; | ||
| 8 | |||
| 9 | /// The error return by methods on the [diff platform][Platform]. | ||
| 10 | #[derive(Debug, thiserror::Error)] | ||
| 11 | #[allow(missing_docs)] | ||
| 12 | pub enum Error { | ||
| 13 | #[error(transparent)] | ||
| 14 | Diff(#[from] git_diff::tree::changes::Error), | ||
| 15 | #[error("The user-provided callback failed")] | ||
| 16 | ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>), | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Add the item to compare to. | ||
| 20 | impl<'a, 'old> Platform<'a, 'old> { | ||
| 21 | /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. | ||
| 22 | pub fn for_each_to_obtain_tree<'new, E>( | ||
| 23 | &mut self, | ||
| 24 | other: &Tree<'new>, | ||
| 25 | for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>, | ||
| 26 | ) -> Result<(), Error> | ||
| 27 | where | ||
| 28 | E: std::error::Error + Sync + Send + 'static, | ||
| 29 | { | ||
| 30 | let repo = self.lhs.repo; | ||
| 31 | let mut delegate = Delegate { | ||
| 32 | repo: self.lhs.repo, | ||
| 33 | other_repo: other.repo, | ||
| 34 | tracking: self.tracking, | ||
| 35 | location: BString::default(), | ||
| 36 | path_deque: Default::default(), | ||
| 37 | visit: for_each, | ||
| 38 | err: None, | ||
| 39 | }; | ||
| 40 | git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( | ||
| 41 | TreeRefIter::from_bytes(&other.data), | ||
| 42 | &mut self.state, | ||
| 43 | |oid, buf| repo.objects.find_tree_iter(oid, buf), | ||
| 44 | &mut delegate, | ||
| 45 | )?; | ||
| 46 | match delegate.err { | ||
| 47 | Some(err) => Err(Error::ForEach(Box::new(err))), | ||
| 48 | None => Ok(()), | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | struct Delegate<'old, 'new, VisitFn, E> { | ||
| 54 | repo: &'old Repository, | ||
| 55 | other_repo: &'new Repository, | ||
| 56 | tracking: Option<Tracking>, | ||
| 57 | location: BString, | ||
| 58 | path_deque: VecDeque<BString>, | ||
| 59 | visit: VisitFn, | ||
| 60 | err: Option<E>, | ||
| 61 | } | ||
| 62 | |||
| 63 | impl<A, B> Delegate<'_, '_, A, B> { | ||
| 64 | fn pop_element(&mut self) { | ||
| 65 | if let Some(pos) = self.location.rfind_byte(b'/') { | ||
| 66 | self.location.resize(pos, 0); | ||
| 67 | } else { | ||
| 68 | self.location.clear(); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | fn push_element(&mut self, name: &BStr) { | ||
| 73 | if !self.location.is_empty() { | ||
| 74 | self.location.push(b'/'); | ||
| 75 | } | ||
| 76 | self.location.push_str(name); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E> | ||
| 81 | where | ||
| 82 | VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>, | ||
| 83 | E: std::error::Error + Sync + Send + 'static, | ||
| 84 | { | ||
| 85 | fn pop_front_tracked_path_and_set_current(&mut self) { | ||
| 86 | if let Some(Tracking::Path) = self.tracking { | ||
| 87 | self.location = self | ||
| 88 | .path_deque | ||
| 89 | .pop_front() | ||
| 90 | .expect("every call is matched with push_tracked_path_component"); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | fn push_back_tracked_path_component(&mut self, component: &BStr) { | ||
| 95 | if let Some(Tracking::Path) = self.tracking { | ||
| 96 | self.push_element(component); | ||
| 97 | self.path_deque.push_back(self.location.clone()); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | fn push_path_component(&mut self, component: &BStr) { | ||
| 102 | match self.tracking { | ||
| 103 | Some(Tracking::FileName) => { | ||
| 104 | self.location.clear(); | ||
| 105 | self.location.push_str(component); | ||
| 106 | } | ||
| 107 | Some(Tracking::Path) => { | ||
| 108 | self.push_element(component); | ||
| 109 | } | ||
| 110 | None => {} | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | fn pop_path_component(&mut self) { | ||
| 115 | if let Some(Tracking::Path) = self.tracking { | ||
| 116 | self.pop_element(); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { | ||
| 121 | use git_diff::tree::visit::Change::*; | ||
| 122 | let event = match change { | ||
| 123 | Addition { entry_mode, oid } => change::Event::Addition { | ||
| 124 | entry_mode, | ||
| 125 | id: oid.attach(self.other_repo), | ||
| 126 | }, | ||
| 127 | Deletion { entry_mode, oid } => change::Event::Deletion { | ||
| 128 | entry_mode, | ||
| 129 | id: oid.attach(self.repo), | ||
| 130 | }, | ||
| 131 | Modification { | ||
| 132 | previous_entry_mode, | ||
| 133 | previous_oid, | ||
| 134 | entry_mode, | ||
| 135 | oid, | ||
| 136 | } => change::Event::Modification { | ||
| 137 | previous_entry_mode, | ||
| 138 | entry_mode, | ||
| 139 | previous_id: previous_oid.attach(self.repo), | ||
| 140 | id: oid.attach(self.other_repo), | ||
| 141 | }, | ||
| 142 | }; | ||
| 143 | match (self.visit)(Change { | ||
| 144 | event, | ||
| 145 | location: self.location.as_ref(), | ||
| 146 | }) { | ||
| 147 | Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, | ||
| 148 | Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, | ||
| 149 | Err(err) => { | ||
| 150 | self.err = Some(err); | ||
| 151 | git_diff::tree::visit::Action::Cancel | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| @@ -0,0 +1,80 @@ | |||
| 1 | use crate::{bstr::BStr, Tree}; | ||
| 2 | |||
| 3 | /// Returned by the `for_each` function to control flow. | ||
| 4 | #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] | ||
| 5 | pub enum Action { | ||
| 6 | /// Continue the traversal of changes. | ||
| 7 | Continue, | ||
| 8 | /// Stop the traversal of changes and stop calling this function. | ||
| 9 | Cancel, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Default for Action { | ||
| 13 | fn default() -> Self { | ||
| 14 | Action::Continue | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Represents any possible change in order to turn one tree into another. | ||
| 19 | #[derive(Debug, Clone, Copy)] | ||
| 20 | pub struct Change<'a, 'old, 'new> { | ||
| 21 | /// The location of the file or directory described by `event`, if tracking was enabled. | ||
| 22 | /// | ||
| 23 | /// Otherwise this value is always an empty path. | ||
| 24 | pub location: &'a BStr, | ||
| 25 | /// The diff event itself to provide information about what would need to change. | ||
| 26 | pub event: change::Event<'old, 'new>, | ||
| 27 | } | ||
| 28 | |||
| 29 | /// | ||
| 30 | pub mod change; | ||
| 31 | |||
| 32 | /// Diffing | ||
| 33 | impl<'repo> Tree<'repo> { | ||
| 34 | /// Return a platform to see the changes needed to create other trees, for instance. | ||
| 35 | /// | ||
| 36 | /// # Performance | ||
| 37 | /// | ||
| 38 | /// It's highly recommended to set an object cache to avoid extracting the same object multiple times. | ||
| 39 | pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { | ||
| 40 | Platform { | ||
| 41 | state: Default::default(), | ||
| 42 | lhs: self, | ||
| 43 | tracking: None, | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | /// The diffing platform returned by [`Tree::changes()`]. | ||
| 49 | #[derive(Clone)] | ||
| 50 | pub struct Platform<'a, 'repo> { | ||
| 51 | state: git_diff::tree::State, | ||
| 52 | lhs: &'a Tree<'repo>, | ||
| 53 | tracking: Option<Tracking>, | ||
| 54 | } | ||
| 55 | |||
| 56 | #[derive(Clone, Copy)] | ||
| 57 | enum Tracking { | ||
| 58 | FileName, | ||
| 59 | Path, | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Configuration | ||
| 63 | impl<'a, 'repo> Platform<'a, 'repo> { | ||
| 64 | /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. | ||
| 65 | pub fn track_filename(&mut self) -> &mut Self { | ||
| 66 | self.tracking = Some(Tracking::FileName); | ||
| 67 | self | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Keep track of the entire path of a change, relative to the repository. | ||
| 71 | /// | ||
| 72 | /// This makes the [`location`][Change::location] field usable. | ||
| 73 | pub fn track_path(&mut self) -> &mut Self { | ||
| 74 | self.tracking = Some(Tracking::Path); | ||
| 75 | self | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | /// | ||
| 80 | pub mod for_each; | ||