gix_diff/tree_with_rewrites/
change.rs

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