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}