Skip to main content

gix_diff/tree_with_rewrites/
change.rs

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