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}