@@ -102,6 +102,7 @@ pub fn file(
102
102
hunks_to_blame.push(UnblamedHunk {
103
103
range_in_blamed_file: range.clone(),
104
104
suspects: [(suspect, range)].into(),
105
+
source_file_name: None,
105
106
});
106
107
}
107
108
@@ -120,12 +121,19 @@ pub fn file(
120
121
break;
121
122
}
122
123
123
-
let is_still_suspect = hunks_to_blame.iter().any(|hunk| hunk.has_suspect(&suspect));
124
-
if !is_still_suspect {
124
+
let first_hunk_for_suspect = hunks_to_blame.iter().find(|hunk| hunk.has_suspect(&suspect));
125
+
let Some(first_hunk_for_suspect) = first_hunk_for_suspect else {
125
126
// There are no `UnblamedHunk`s associated with this `suspect`, so we can continue with
126
127
// the next one.
127
128
continue 'outer;
128
-
}
129
+
};
130
+
131
+
// We know `first_hunk_for_suspect` can’t be `None` here because we check `is_some()`
132
+
// above.
133
+
let current_file_path = first_hunk_for_suspect
134
+
.source_file_name
135
+
.clone()
136
+
.unwrap_or_else(|| file_path.to_owned());
129
137
130
138
let commit = find_commit(cache.as_ref(), &odb, &suspect, &mut buf)?;
131
139
let commit_time = commit.commit_time()?;
@@ -165,7 +173,7 @@ pub fn file(
165
173
entry = find_path_entry_in_commit(
166
174
&odb,
167
175
&suspect,
168
-
file_path,
176
+
current_file_path.as_ref(),
169
177
cache.as_ref(),
170
178
&mut buf,
171
179
&mut buf2,
@@ -216,7 +224,7 @@ pub fn file(
216
224
if let Some(parent_entry_id) = find_path_entry_in_commit(
217
225
&odb,
218
226
parent_id,
219
-
file_path,
227
+
current_file_path.as_ref(),
220
228
cache.as_ref(),
221
229
&mut buf,
222
230
&mut buf2,
@@ -239,15 +247,17 @@ pub fn file(
239
247
queue.insert(parent_commit_time, parent_id);
240
248
let changes_for_file_path = tree_diff_at_file_path(
241
249
&odb,
242
-
file_path,
250
+
current_file_path.as_ref(),
243
251
suspect,
244
252
parent_id,
245
253
cache.as_ref(),
246
254
&mut stats,
247
255
&mut diff_state,
256
+
resource_cache,
248
257
&mut buf,
249
258
&mut buf2,
250
259
&mut buf3,
260
+
options.rewrites,
251
261
)?;
252
262
let Some(modification) = changes_for_file_path else {
253
263
if more_than_one_parent {
@@ -263,7 +273,7 @@ pub fn file(
263
273
};
264
274
265
275
match modification {
266
-
gix_diff::tree::recorder::Change::Addition { .. } => {
276
+
TreeDiffChange::Addition => {
267
277
if more_than_one_parent {
268
278
// Do nothing under the assumption that this always (or almost always)
269
279
// implies that the file comes from a different parent, compared to which
@@ -272,20 +282,44 @@ pub fn file(
272
282
break 'outer;
273
283
}
274
284
}
275
-
gix_diff::tree::recorder::Change::Deletion { .. } => {
285
+
TreeDiffChange::Deletion => {
276
286
unreachable!("We already found file_path in suspect^{{tree}}, so it can't be deleted")
277
287
}
278
-
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
288
+
TreeDiffChange::Modification { previous_id, id } => {
279
289
let changes = blob_changes(
280
290
&odb,
281
291
resource_cache,
282
-
oid,
283
-
previous_oid,
292
+
id,
293
+
previous_id,
294
+
file_path,
295
+
file_path,
296
+
options.diff_algorithm,
297
+
&mut stats,
298
+
)?;
299
+
hunks_to_blame = process_changes(hunks_to_blame, changes, suspect, parent_id);
300
+
}
301
+
TreeDiffChange::Rewrite {
302
+
source_location,
303
+
source_id,
304
+
id,
305
+
} => {
306
+
let changes = blob_changes(
307
+
&odb,
308
+
resource_cache,
309
+
id,
310
+
source_id,
284
311
file_path,
312
+
source_location.as_ref(),
285
313
options.diff_algorithm,
286
314
&mut stats,
287
315
)?;
288
316
hunks_to_blame = process_changes(hunks_to_blame, changes, suspect, parent_id);
317
+
318
+
for hunk in hunks_to_blame.iter_mut() {
319
+
if hunk.has_suspect(&parent_id) {
320
+
hunk.source_file_name = Some(source_location.clone());
321
+
}
322
+
}
289
323
}
290
324
}
291
325
}
@@ -382,6 +416,7 @@ fn coalesce_blame_entries(lines_blamed: Vec<BlameEntry>) -> Vec<BlameEntry> {
382
416
len: NonZeroU32::new((current_source_range.end - previous_source_range.start) as u32)
383
417
.expect("BUG: hunks are never zero-sized"),
384
418
commit_id: previous_entry.commit_id,
419
+
source_file_name: previous_entry.source_file_name.clone(),
385
420
};
386
421
387
422
acc.pop();
@@ -399,6 +434,59 @@ fn coalesce_blame_entries(lines_blamed: Vec<BlameEntry>) -> Vec<BlameEntry> {
399
434
})
400
435
}
401
436
437
+
/// The union of [`gix_diff::tree::recorder::Change`] and [`gix_diff::tree_with_rewrites::Change`],
438
+
/// keeping only the blame-relevant information.
439
+
enum TreeDiffChange {
440
+
Addition,
441
+
Deletion,
442
+
Modification {
443
+
previous_id: ObjectId,
444
+
id: ObjectId,
445
+
},
446
+
Rewrite {
447
+
source_location: BString,
448
+
source_id: ObjectId,
449
+
id: ObjectId,
450
+
},
451
+
}
452
+
453
+
impl From<gix_diff::tree::recorder::Change> for TreeDiffChange {
454
+
fn from(value: gix_diff::tree::recorder::Change) -> Self {
455
+
use gix_diff::tree::recorder::Change;
456
+
457
+
match value {
458
+
Change::Addition { .. } => Self::Addition,
459
+
Change::Deletion { .. } => Self::Deletion,
460
+
Change::Modification { previous_oid, oid, .. } => Self::Modification {
461
+
previous_id: previous_oid,
462
+
id: oid,
463
+
},
464
+
}
465
+
}
466
+
}
467
+
468
+
impl From<gix_diff::tree_with_rewrites::Change> for TreeDiffChange {
469
+
fn from(value: gix_diff::tree_with_rewrites::Change) -> Self {
470
+
use gix_diff::tree_with_rewrites::Change;
471
+
472
+
match value {
473
+
Change::Addition { .. } => Self::Addition,
474
+
Change::Deletion { .. } => Self::Deletion,
475
+
Change::Modification { previous_id, id, .. } => Self::Modification { previous_id, id },
476
+
Change::Rewrite {
477
+
source_location,
478
+
source_id,
479
+
id,
480
+
..
481
+
} => Self::Rewrite {
482
+
source_location,
483
+
source_id,
484
+
id,
485
+
},
486
+
}
487
+
}
488
+
}
489
+
402
490
#[allow(clippy::too_many_arguments)]
403
491
fn tree_diff_at_file_path(
404
492
odb: impl gix_object::Find + gix_object::FindHeader,
@@ -408,10 +496,12 @@ fn tree_diff_at_file_path(
408
496
cache: Option<&gix_commitgraph::Graph>,
409
497
stats: &mut Statistics,
410
498
state: &mut gix_diff::tree::State,
499
+
resource_cache: &mut gix_diff::blob::Platform,
411
500
commit_buf: &mut Vec<u8>,
412
501
lhs_tree_buf: &mut Vec<u8>,
413
502
rhs_tree_buf: &mut Vec<u8>,
414
-
) -> Result<Option<gix_diff::tree::recorder::Change>, Error> {
503
+
rewrites: Option<gix_diff::Rewrites>,
504
+
) -> Result<Option<TreeDiffChange>, Error> {
415
505
let parent_tree_id = find_commit(cache, &odb, &parent_id, commit_buf)?.tree_id()?;
416
506
417
507
let parent_tree_iter = odb.find_tree_iter(&parent_tree_id, lhs_tree_buf)?;
@@ -422,6 +512,44 @@ fn tree_diff_at_file_path(
422
512
let tree_iter = odb.find_tree_iter(&tree_id, rhs_tree_buf)?;
423
513
stats.trees_decoded += 1;
424
514
515
+
let result = tree_diff_without_rewrites_at_file_path(&odb, file_path, stats, state, parent_tree_iter, tree_iter)?;
516
+
517
+
// Here, we follow git’s behaviour. We return when we’ve found a `Modification`. We try a
518
+
// second time with rename tracking when the change is either an `Addition` or a `Deletion`
519
+
// because those can turn out to have been a `Rewrite`.
520
+
// TODO(perf): renames are usually rare enough to not care about the work duplication done here.
521
+
// But in theory, a rename tracker could be used by us, on demand, and we could stuff the
522
+
// changes in there and have it find renames, without repeating the diff.
523
+
if matches!(result, Some(TreeDiffChange::Modification { .. })) {
524
+
return Ok(result);
525
+
}
526
+
let Some(rewrites) = rewrites else {
527
+
return Ok(result);
528
+
};
529
+
530
+
let result = tree_diff_with_rewrites_at_file_path(
531
+
&odb,
532
+
file_path,
533
+
stats,
534
+
state,
535
+
resource_cache,
536
+
parent_tree_iter,
537
+
tree_iter,
538
+
rewrites,
539
+
)?;
540
+
541
+
Ok(result)
542
+
}
543
+
544
+
#[allow(clippy::too_many_arguments)]
545
+
fn tree_diff_without_rewrites_at_file_path(
546
+
odb: impl gix_object::Find + gix_object::FindHeader,
547
+
file_path: &BStr,
548
+
stats: &mut Statistics,
549
+
state: &mut gix_diff::tree::State,
550
+
parent_tree_iter: gix_object::TreeRefIter<'_>,
551
+
tree_iter: gix_object::TreeRefIter<'_>,
552
+
) -> Result<Option<TreeDiffChange>, Error> {
425
553
struct FindChangeToPath {
426
554
inner: gix_diff::tree::Recorder,
427
555
interesting_path: BString,
@@ -509,17 +637,62 @@ fn tree_diff_at_file_path(
509
637
stats.trees_diffed += 1;
510
638
511
639
match result {
512
-
Ok(_) | Err(gix_diff::tree::Error::Cancelled) => Ok(recorder.change),
640
+
Ok(_) | Err(gix_diff::tree::Error::Cancelled) => Ok(recorder.change.map(Into::into)),
513
641
Err(error) => Err(Error::DiffTree(error)),
514
642
}
515
643
}
516
644
645
+
#[allow(clippy::too_many_arguments)]
646
+
fn tree_diff_with_rewrites_at_file_path(
647
+
odb: impl gix_object::Find + gix_object::FindHeader,
648
+
file_path: &BStr,
649
+
stats: &mut Statistics,
650
+
state: &mut gix_diff::tree::State,
651
+
resource_cache: &mut gix_diff::blob::Platform,
652
+
parent_tree_iter: gix_object::TreeRefIter<'_>,
653
+
tree_iter: gix_object::TreeRefIter<'_>,
654
+
rewrites: gix_diff::Rewrites,
655
+
) -> Result<Option<TreeDiffChange>, Error> {
656
+
let mut change: Option<gix_diff::tree_with_rewrites::Change> = None;
657
+
658
+
let options: gix_diff::tree_with_rewrites::Options = gix_diff::tree_with_rewrites::Options {
659
+
location: Some(gix_diff::tree::recorder::Location::Path),
660
+
rewrites: Some(rewrites),
661
+
};
662
+
let result = gix_diff::tree_with_rewrites(
663
+
parent_tree_iter,
664
+
tree_iter,
665
+
resource_cache,
666
+
state,
667
+
&odb,
668
+
|change_ref| -> Result<_, std::convert::Infallible> {
669
+
if change_ref.location() == file_path {
670
+
change = Some(change_ref.into_owned());
671
+
Ok(gix_diff::tree_with_rewrites::Action::Cancel)
672
+
} else {
673
+
Ok(gix_diff::tree_with_rewrites::Action::Continue)
674
+
}
675
+
},
676
+
options,
677
+
);
678
+
stats.trees_diffed_with_rewrites += 1;
679
+
680
+
match result {
681
+
Ok(_) | Err(gix_diff::tree_with_rewrites::Error::Diff(gix_diff::tree::Error::Cancelled)) => {
682
+
Ok(change.map(Into::into))
683
+
}
684
+
Err(error) => Err(Error::DiffTreeWithRewrites(error)),
685
+
}
686
+
}
687
+
688
+
#[allow(clippy::too_many_arguments)]
517
689
fn blob_changes(
518
690
odb: impl gix_object::Find + gix_object::FindHeader,
519
691
resource_cache: &mut gix_diff::blob::Platform,
520
692
oid: ObjectId,
521
693
previous_oid: ObjectId,
522
694
file_path: &BStr,
695
+
previous_file_path: &BStr,
523
696
diff_algorithm: gix_diff::blob::Algorithm,
524
697
stats: &mut Statistics,
525
698
) -> Result<Vec<Change>, Error> {
@@ -579,7 +752,7 @@ fn blob_changes(
579
752
resource_cache.set_resource(
580
753
previous_oid,
581
754
gix_object::tree::EntryKind::Blob,
582
-
file_path,
755
+
previous_file_path,
583
756
gix_diff::blob::ResourceKind::OldOrSource,
584
757
&odb,
585
758
)?;
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4