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; | |