gix_diff/tree_with_rewrites/
change.rs

1use crate::blob::{DiffLineStats, ResourceKind};
2use crate::tree;
3use bstr::BString;
4use bstr::{BStr, ByteSlice};
5
6/// Represents any possible change in order to turn one tree into another, which references data owned by its producer.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum ChangeRef<'a> {
9    /// An entry was added, like the addition of a file or directory.
10    Addition {
11        /// The location of the file or directory.
12        ///
13        /// It may be empty if [file names](super::Options::location) is `None`.
14        location: &'a BStr,
15        /// The mode of the added entry.
16        entry_mode: gix_object::tree::EntryMode,
17        /// Identifies a relationship between this instance and another one,
18        /// making it easy to reconstruct the top-level of directory changes.
19        relation: Option<tree::visit::Relation>,
20        /// The object id of the added entry.
21        id: gix_hash::ObjectId,
22    },
23    /// An entry was deleted, like the deletion of a file or directory.
24    Deletion {
25        /// The location of the file or directory.
26        ///
27        /// It may be empty if [file names](super::Options::location) is `None`.
28        /// are tracked.
29        location: &'a BStr,
30        /// The mode of the deleted entry.
31        entry_mode: gix_object::tree::EntryMode,
32        /// Identifies a relationship between this instance and another one,
33        /// making it easy to reconstruct the top-level of directory changes.
34        relation: Option<tree::visit::Relation>,
35        /// The object id of the deleted entry.
36        id: gix_hash::ObjectId,
37    },
38    /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
39    /// a file into a symbolic link adjusts its mode.
40    Modification {
41        /// The location of the file or directory.
42        ///
43        /// It may be empty if [file names](super::Options::location) is `None`.
44        /// are tracked.
45        location: &'a BStr,
46        /// The mode of the entry before the modification.
47        previous_entry_mode: gix_object::tree::EntryMode,
48        /// The object id of the entry before the modification.
49        previous_id: gix_hash::ObjectId,
50
51        /// The mode of the entry after the modification.
52        entry_mode: gix_object::tree::EntryMode,
53        /// The object id after the modification.
54        id: gix_hash::ObjectId,
55    },
56    /// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed
57    /// or copied.
58    /// In case of renames, this means they originally appeared as [`Deletion`](ChangeRef::Deletion) signalling their source as well as an
59    /// [`Addition`](ChangeRef::Addition) acting as destination.
60    ///
61    /// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made.
62    ///
63    /// This variant can only be encountered if [rewrite tracking](super::Options::rewrites) is enabled.
64    ///
65    /// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa.
66    Rewrite {
67        /// The location of the source of the rename or copy operation.
68        ///
69        /// It may be empty if [file names](super::Options::location) is `None`.
70        /// are tracked.
71        source_location: &'a BStr,
72        /// The mode of the entry before the rename.
73        source_entry_mode: gix_object::tree::EntryMode,
74        /// Identifies a relationship between the source and another source,
75        /// making it easy to reconstruct the top-level of directory changes.
76        source_relation: Option<tree::visit::Relation>,
77        /// The object id of the entry before the rename.
78        ///
79        /// Note that this is the same as `id` if we require the [similarity to be 100%](super::Rewrites::percentage), but may
80        /// be different otherwise.
81        source_id: gix_hash::ObjectId,
82        /// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`.
83        /// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary.
84        diff: Option<DiffLineStats>,
85        /// The mode of the entry after the rename.
86        /// It could differ but still be considered a rename as we are concerned only about content.
87        entry_mode: gix_object::tree::EntryMode,
88        /// The object id after the rename.
89        id: gix_hash::ObjectId,
90        /// The location after the rename or copy operation.
91        ///
92        /// It may be empty if [file names](super::Options::location) is `None`.
93        location: &'a BStr,
94        /// Identifies a relationship between this destination and another destination,
95        /// making it easy to reconstruct the top-level of directory changes.
96        relation: Option<tree::visit::Relation>,
97        /// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id`
98        /// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content.
99        copy: bool,
100    },
101}
102
103/// Represents any possible change in order to turn one tree into another, with fully-owned data.
104#[derive(Debug, Clone, PartialEq)]
105pub enum Change {
106    /// An entry was added, like the addition of a file or directory.
107    Addition {
108        /// The location of the file or directory.
109        ///
110        /// It may be empty if [file names](super::Options::location) is `None`.
111        location: BString,
112        /// Identifies a relationship between this instance and another one,
113        /// making it easy to reconstruct the top-level of directory changes.
114        relation: Option<tree::visit::Relation>,
115        /// The mode of the added entry.
116        entry_mode: gix_object::tree::EntryMode,
117        /// The object id of the added entry.
118        id: gix_hash::ObjectId,
119    },
120    /// An entry was deleted, like the deletion of a file or directory.
121    Deletion {
122        /// The location of the file or directory.
123        ///
124        /// It may be empty if [file names](super::Options::location) is `None`.
125        location: BString,
126        /// Identifies a relationship between this instance and another one,
127        /// making it easy to reconstruct the top-level of directory changes.
128        relation: Option<tree::visit::Relation>,
129        /// The mode of the deleted entry.
130        entry_mode: gix_object::tree::EntryMode,
131        /// The object id of the deleted entry.
132        id: gix_hash::ObjectId,
133    },
134    /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
135    /// a file into a symbolic link adjusts its mode.
136    Modification {
137        /// The location of the file or directory.
138        ///
139        /// It may be empty if [file names](super::Options::location) is `None`.
140        location: BString,
141        /// The mode of the entry before the modification.
142        previous_entry_mode: gix_object::tree::EntryMode,
143        /// The object id of the entry before the modification.
144        previous_id: gix_hash::ObjectId,
145
146        /// The mode of the entry after the modification.
147        entry_mode: gix_object::tree::EntryMode,
148        /// The object id after the modification.
149        id: gix_hash::ObjectId,
150    },
151    /// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed
152    /// or copied.
153    /// In case of renames, this means they originally appeared as [`Deletion`](ChangeRef::Deletion) signalling their source as well as an
154    /// [`Addition`](ChangeRef::Addition) acting as destination.
155    ///
156    /// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made.
157    ///
158    /// This variant can only be encountered if [rewrite tracking](super::Options::rewrites) is enabled.
159    ///
160    /// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa.
161    Rewrite {
162        /// The location of the source of the rename operation.
163        ///
164        /// It may be empty if [file names](super::Options::location) is `None`.
165        source_location: BString,
166        /// The mode of the entry before the rename.
167        source_entry_mode: gix_object::tree::EntryMode,
168        /// Identifies a relationship between the source and another source,
169        /// making it easy to reconstruct the top-level of directory changes.
170        source_relation: Option<tree::visit::Relation>,
171        /// The object id of the entry before the rename.
172        ///
173        /// Note that this is the same as `id` if we require the [similarity to be 100%](super::Rewrites::percentage), but may
174        /// be different otherwise.
175        source_id: gix_hash::ObjectId,
176        /// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`.
177        /// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary.
178        diff: Option<DiffLineStats>,
179        /// The mode of the entry after the rename.
180        /// It could differ but still be considered a rename as we are concerned only about content.
181        entry_mode: gix_object::tree::EntryMode,
182        /// The object id after the rename.
183        id: gix_hash::ObjectId,
184        /// The location after the rename or copy operation.
185        ///
186        /// It may be empty if [file names](super::Options::location) is `None`.
187        location: BString,
188        /// Identifies a relationship between this destination and another destination,
189        /// making it easy to reconstruct the top-level of directory changes.
190        relation: Option<tree::visit::Relation>,
191        /// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id`
192        /// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content.
193        copy: bool,
194    },
195}
196
197/// Lifecycle
198impl ChangeRef<'_> {
199    /// Copy this instance into a fully-owned version
200    pub fn into_owned(self) -> Change {
201        match self {
202            ChangeRef::Addition {
203                location,
204                entry_mode,
205                id,
206                relation,
207            } => Change::Addition {
208                location: location.to_owned(),
209                entry_mode,
210                id,
211                relation,
212            },
213            ChangeRef::Deletion {
214                location,
215                entry_mode,
216                id,
217                relation,
218            } => Change::Deletion {
219                location: location.to_owned(),
220                entry_mode,
221                id,
222                relation,
223            },
224            ChangeRef::Modification {
225                location,
226                previous_entry_mode,
227                previous_id,
228                entry_mode,
229                id,
230            } => Change::Modification {
231                location: location.to_owned(),
232                previous_entry_mode,
233                previous_id,
234                entry_mode,
235                id,
236            },
237            ChangeRef::Rewrite {
238                source_location,
239                source_relation,
240                source_entry_mode,
241                source_id,
242                diff,
243                entry_mode,
244                id,
245                location,
246                relation,
247                copy,
248            } => Change::Rewrite {
249                source_location: source_location.to_owned(),
250                source_relation,
251                source_entry_mode,
252                source_id,
253                diff,
254                entry_mode,
255                id,
256                location: location.to_owned(),
257                relation,
258                copy,
259            },
260        }
261    }
262}
263
264/// Lifecycle
265impl Change {
266    /// Return an attached version of this instance that uses `old_repo` for previous values and `new_repo` for current values.
267    pub fn to_ref(&self) -> ChangeRef<'_> {
268        match self {
269            Change::Addition {
270                location,
271                relation,
272                entry_mode,
273                id,
274            } => ChangeRef::Addition {
275                location: location.as_bstr(),
276                entry_mode: *entry_mode,
277                id: *id,
278                relation: *relation,
279            },
280            Change::Deletion {
281                location,
282                relation,
283                entry_mode,
284                id,
285            } => ChangeRef::Deletion {
286                location: location.as_bstr(),
287                entry_mode: *entry_mode,
288                id: *id,
289                relation: *relation,
290            },
291            Change::Modification {
292                location,
293                previous_entry_mode,
294                previous_id,
295                entry_mode,
296                id,
297            } => ChangeRef::Modification {
298                location: location.as_bstr(),
299                previous_entry_mode: *previous_entry_mode,
300                previous_id: *previous_id,
301                entry_mode: *entry_mode,
302                id: *id,
303            },
304            Change::Rewrite {
305                source_location,
306                source_relation,
307                source_entry_mode,
308                source_id,
309                diff,
310                entry_mode,
311                id,
312                location,
313                relation,
314                copy,
315            } => ChangeRef::Rewrite {
316                source_location: source_location.as_ref(),
317                source_relation: *source_relation,
318                source_entry_mode: *source_entry_mode,
319                source_id: *source_id,
320                diff: *diff,
321                entry_mode: *entry_mode,
322                id: *id,
323                location: location.as_bstr(),
324                relation: *relation,
325                copy: *copy,
326            },
327        }
328    }
329}
330
331impl crate::blob::Platform {
332    /// Set ourselves up to produces blob-diffs from `change`, so this platform can be used to produce diffs easily.
333    /// `objects` are used to fetch object data as needed.
334    ///
335    /// ### Warning about Memory Consumption
336    ///
337    /// This instance only grows, so one should call [`crate::blob::Platform::clear_resource_cache`] occasionally.
338    pub fn set_resource_by_change(
339        &mut self,
340        change: ChangeRef<'_>,
341        objects: &impl gix_object::FindObjectOrHeader,
342    ) -> Result<&mut Self, crate::blob::platform::set_resource::Error> {
343        match change {
344            ChangeRef::Addition {
345                location,
346                relation: _,
347                entry_mode,
348                id,
349            } => {
350                self.set_resource(
351                    id.kind().null(),
352                    entry_mode.kind(),
353                    location,
354                    ResourceKind::OldOrSource,
355                    objects,
356                )?;
357                self.set_resource(id, entry_mode.kind(), location, ResourceKind::NewOrDestination, objects)?;
358            }
359            ChangeRef::Deletion {
360                location,
361                relation: _,
362                entry_mode,
363                id,
364            } => {
365                self.set_resource(id, entry_mode.kind(), location, ResourceKind::OldOrSource, objects)?;
366                self.set_resource(
367                    id.kind().null(),
368                    entry_mode.kind(),
369                    location,
370                    ResourceKind::NewOrDestination,
371                    objects,
372                )?;
373            }
374            ChangeRef::Modification {
375                location,
376                previous_entry_mode,
377                previous_id,
378                entry_mode,
379                id,
380            } => {
381                self.set_resource(
382                    previous_id,
383                    previous_entry_mode.kind(),
384                    location,
385                    ResourceKind::OldOrSource,
386                    objects,
387                )?;
388                self.set_resource(id, entry_mode.kind(), location, ResourceKind::NewOrDestination, objects)?;
389            }
390            ChangeRef::Rewrite {
391                source_location,
392                source_relation: _,
393                source_entry_mode,
394                source_id,
395                entry_mode,
396                id,
397                location,
398                relation: _,
399                diff: _,
400                copy: _,
401            } => {
402                self.set_resource(
403                    source_id,
404                    source_entry_mode.kind(),
405                    source_location,
406                    ResourceKind::OldOrSource,
407                    objects,
408                )?;
409                self.set_resource(id, entry_mode.kind(), location, ResourceKind::NewOrDestination, objects)?;
410            }
411        }
412        Ok(self)
413    }
414}
415
416impl<'a> ChangeRef<'a> {
417    /// Return the relation this instance may have to other changes.
418    pub fn relation(&self) -> Option<tree::visit::Relation> {
419        match self {
420            ChangeRef::Addition { relation, .. }
421            | ChangeRef::Deletion { relation, .. }
422            | ChangeRef::Rewrite { relation, .. } => *relation,
423            ChangeRef::Modification { .. } => None,
424        }
425    }
426
427    /// Return the current mode of this instance.
428    pub fn entry_mode(&self) -> gix_object::tree::EntryMode {
429        match self {
430            ChangeRef::Addition { entry_mode, .. }
431            | ChangeRef::Deletion { entry_mode, .. }
432            | ChangeRef::Modification { entry_mode, .. }
433            | ChangeRef::Rewrite { entry_mode, .. } => *entry_mode,
434        }
435    }
436
437    /// Return the current mode of this instance, along with its object id.
438    pub fn entry_mode_and_id(&self) -> (gix_object::tree::EntryMode, &gix_hash::oid) {
439        match self {
440            ChangeRef::Addition { entry_mode, id, .. }
441            | ChangeRef::Deletion { entry_mode, id, .. }
442            | ChangeRef::Modification { entry_mode, id, .. }
443            | ChangeRef::Rewrite { entry_mode, id, .. } => (*entry_mode, id),
444        }
445    }
446
447    /// Return the *previous* mode and id of the resource where possible, i.e. the source of a rename or copy, or a modification.
448    pub fn source_entry_mode_and_id(&self) -> (gix_object::tree::EntryMode, &gix_hash::oid) {
449        match self {
450            ChangeRef::Addition { entry_mode, id, .. }
451            | ChangeRef::Deletion { entry_mode, id, .. }
452            | ChangeRef::Modification {
453                previous_entry_mode: entry_mode,
454                previous_id: id,
455                ..
456            }
457            | ChangeRef::Rewrite {
458                source_entry_mode: entry_mode,
459                source_id: id,
460                ..
461            } => (*entry_mode, id),
462        }
463    }
464
465    /// Return the *current* location of the resource, i.e. the destination of a rename or copy, or the
466    /// location at which an addition, deletion or modification took place.
467    pub fn location(&self) -> &'a BStr {
468        match self {
469            ChangeRef::Addition { location, .. }
470            | ChangeRef::Deletion { location, .. }
471            | ChangeRef::Modification { location, .. }
472            | ChangeRef::Rewrite { location, .. } => location,
473        }
474    }
475
476    /// Return the *previous* location of the resource where possible, i.e. the source of a rename or copy, or the
477    /// location at which an addition, deletion or modification took place.
478    pub fn source_location(&self) -> &BStr {
479        match self {
480            ChangeRef::Addition { location, .. }
481            | ChangeRef::Deletion { location, .. }
482            | ChangeRef::Modification { location, .. } => location,
483            ChangeRef::Rewrite { source_location, .. } => source_location,
484        }
485    }
486}
487
488impl Change {
489    /// Return the relation this instance may have to other changes.
490    pub fn relation(&self) -> Option<tree::visit::Relation> {
491        match self {
492            Change::Addition { relation, .. }
493            | Change::Deletion { relation, .. }
494            | Change::Rewrite { relation, .. } => *relation,
495            Change::Modification { .. } => None,
496        }
497    }
498
499    /// Return the current mode of this instance.
500    pub fn entry_mode(&self) -> gix_object::tree::EntryMode {
501        match self {
502            Change::Addition { entry_mode, .. }
503            | Change::Deletion { entry_mode, .. }
504            | Change::Modification { entry_mode, .. }
505            | Change::Rewrite { entry_mode, .. } => *entry_mode,
506        }
507    }
508
509    /// Return the current mode of this instance, along with its object id.
510    pub fn entry_mode_and_id(&self) -> (gix_object::tree::EntryMode, &gix_hash::oid) {
511        match self {
512            Change::Addition { entry_mode, id, .. }
513            | Change::Deletion { entry_mode, id, .. }
514            | Change::Modification { entry_mode, id, .. }
515            | Change::Rewrite { entry_mode, id, .. } => (*entry_mode, id),
516        }
517    }
518
519    /// Return the *previous* mode and id of the resource where possible, i.e. the source of a rename or copy, or a modification.
520    pub fn source_entry_mode_and_id(&self) -> (gix_object::tree::EntryMode, &gix_hash::oid) {
521        match self {
522            Change::Addition { entry_mode, id, .. }
523            | Change::Deletion { entry_mode, id, .. }
524            | Change::Modification {
525                previous_entry_mode: entry_mode,
526                previous_id: id,
527                ..
528            }
529            | Change::Rewrite {
530                source_entry_mode: entry_mode,
531                source_id: id,
532                ..
533            } => (*entry_mode, id),
534        }
535    }
536
537    /// Return the *current* location of the resource, i.e. the destination of a rename or copy, or the
538    /// location at which an addition, deletion or modification took place.
539    pub fn location(&self) -> &BStr {
540        match self {
541            Change::Addition { location, .. }
542            | Change::Deletion { location, .. }
543            | Change::Modification { location, .. }
544            | Change::Rewrite { location, .. } => location.as_bstr(),
545        }
546    }
547
548    /// Return the *previous* location of the resource where possible, i.e. the source of a rename or copy, or the
549    /// location at which an addition, deletion or modification took place.
550    pub fn source_location(&self) -> &BStr {
551        match self {
552            Change::Addition { location, .. }
553            | Change::Deletion { location, .. }
554            | Change::Modification { location, .. } => location.as_bstr(),
555            Change::Rewrite { source_location, .. } => source_location.as_bstr(),
556        }
557    }
558}