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}