Skip to content

Commit c75bc44

Browse files
authored
Merge pull request #1945 from cruessler/replace-btreemap-by-smallvec
`gix-blame`: Replace `BTreeMap` by `SmallVec`
2 parents 5993dd5 + 75b842b commit c75bc44

File tree

3 files changed

+42
-21
lines changed

3 files changed

+42
-21
lines changed

gix-blame/src/file/function.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub fn file(
114114
break;
115115
}
116116

117-
let is_still_suspect = hunks_to_blame.iter().any(|hunk| hunk.suspects.contains_key(&suspect));
117+
let is_still_suspect = hunks_to_blame.iter().any(|hunk| hunk.has_suspect(&suspect));
118118
if !is_still_suspect {
119119
// There are no `UnblamedHunk`s associated with this `suspect`, so we can continue with
120120
// the next one.
@@ -189,7 +189,7 @@ pub fn file(
189189
.collect();
190190

191191
for hunk in hunks_to_blame.iter() {
192-
if let Some(range_in_suspect) = hunk.suspects.get(&suspect) {
192+
if let Some(range_in_suspect) = hunk.get_range(&suspect) {
193193
let range_in_blamed_file = hunk.range_in_blamed_file.clone();
194194

195195
for (blamed_line_number, source_line_number) in range_in_blamed_file.zip(range_in_suspect.clone()) {

gix-blame/src/file/mod.rs

+21-13
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ fn process_change(
3939
// 3. `change` *must* be returned if it is not fully included in `hunk`.
4040
match (hunk, change) {
4141
(Some(hunk), Some(Change::Unchanged(unchanged))) => {
42-
let Some(range_in_suspect) = hunk.suspects.get(&suspect) else {
42+
let Some(range_in_suspect) = hunk.get_range(&suspect) else {
4343
// We don’t clone blame to `parent` as `suspect` has nothing to do with this
4444
// `hunk`.
4545
new_hunks_to_blame.push(hunk);
@@ -102,7 +102,7 @@ fn process_change(
102102
}
103103
}
104104
(Some(hunk), Some(Change::AddedOrReplaced(added, number_of_lines_deleted))) => {
105-
let Some(range_in_suspect) = hunk.suspects.get(&suspect).cloned() else {
105+
let Some(range_in_suspect) = hunk.get_range(&suspect).cloned() else {
106106
new_hunks_to_blame.push(hunk);
107107
return (None, Some(Change::AddedOrReplaced(added, number_of_lines_deleted)));
108108
};
@@ -247,7 +247,7 @@ fn process_change(
247247
}
248248
}
249249
(Some(hunk), Some(Change::Deleted(line_number_in_destination, number_of_lines_deleted))) => {
250-
let Some(range_in_suspect) = hunk.suspects.get(&suspect) else {
250+
let Some(range_in_suspect) = hunk.get_range(&suspect) else {
251251
new_hunks_to_blame.push(hunk);
252252
return (
253253
None,
@@ -359,12 +359,16 @@ fn process_changes(
359359

360360
impl UnblamedHunk {
361361
fn shift_by(mut self, suspect: ObjectId, offset: Offset) -> Self {
362-
self.suspects.entry(suspect).and_modify(|e| *e = e.shift_by(offset));
362+
if let Some(position) = self.suspects.iter().position(|entry| entry.0 == suspect) {
363+
if let Some((_, ref mut range_in_suspect)) = self.suspects.get_mut(position) {
364+
*range_in_suspect = range_in_suspect.shift_by(offset);
365+
}
366+
}
363367
self
364368
}
365369

366370
fn split_at(self, suspect: ObjectId, line_number_in_destination: u32) -> Either<Self, (Self, Self)> {
367-
match self.suspects.get(&suspect) {
371+
match self.get_range(&suspect) {
368372
None => Either::Left(self),
369373
Some(range_in_suspect) => {
370374
if !range_in_suspect.contains(&line_number_in_destination) {
@@ -405,34 +409,38 @@ impl UnblamedHunk {
405409
/// This is like [`Self::pass_blame()`], but easier to use in places where the 'passing' is
406410
/// done 'inline'.
407411
fn passed_blame(mut self, from: ObjectId, to: ObjectId) -> Self {
408-
if let Some(range_in_suspect) = self.suspects.remove(&from) {
409-
self.suspects.insert(to, range_in_suspect);
412+
if let Some(position) = self.suspects.iter().position(|entry| entry.0 == from) {
413+
if let Some((ref mut commit_id, _)) = self.suspects.get_mut(position) {
414+
*commit_id = to;
415+
}
410416
}
411417
self
412418
}
413419

414420
/// Transfer all ranges from the commit at `from` to the commit at `to`.
415421
fn pass_blame(&mut self, from: ObjectId, to: ObjectId) {
416-
if let Some(range_in_suspect) = self.suspects.remove(&from) {
417-
self.suspects.insert(to, range_in_suspect);
422+
if let Some(position) = self.suspects.iter().position(|entry| entry.0 == from) {
423+
if let Some((ref mut commit_id, _)) = self.suspects.get_mut(position) {
424+
*commit_id = to;
425+
}
418426
}
419427
}
420428

421429
fn clone_blame(&mut self, from: ObjectId, to: ObjectId) {
422-
if let Some(range_in_suspect) = self.suspects.get(&from) {
423-
self.suspects.insert(to, range_in_suspect.clone());
430+
if let Some(range_in_suspect) = self.get_range(&from) {
431+
self.suspects.push((to, range_in_suspect.clone()));
424432
}
425433
}
426434

427435
fn remove_blame(&mut self, suspect: ObjectId) {
428-
self.suspects.remove(&suspect);
436+
self.suspects.retain(|entry| entry.0 != suspect);
429437
}
430438
}
431439

432440
impl BlameEntry {
433441
/// Create an offset from a portion of the *Blamed File*.
434442
fn from_unblamed_hunk(unblamed_hunk: &UnblamedHunk, commit_id: ObjectId) -> Option<Self> {
435-
let range_in_source_file = unblamed_hunk.suspects.get(&commit_id)?;
443+
let range_in_source_file = unblamed_hunk.get_range(&commit_id)?;
436444

437445
Some(Self {
438446
start_in_blamed_file: unblamed_hunk.range_in_blamed_file.start,

gix-blame/src/types.rs

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use crate::file::function::tokens_for_diffing;
22
use gix_hash::ObjectId;
33
use gix_object::bstr::BString;
4+
use smallvec::SmallVec;
45
use std::num::NonZeroU32;
5-
use std::{
6-
collections::BTreeMap,
7-
ops::{AddAssign, Range, SubAssign},
8-
};
6+
use std::ops::{AddAssign, Range, SubAssign};
97

108
/// Options to be passed to [`file()`](crate::file()).
119
#[derive(Default, Debug, Clone)]
@@ -198,8 +196,23 @@ impl LineRange for Range<u32> {
198196
pub struct UnblamedHunk {
199197
/// The range in the file that is being blamed that this hunk represents.
200198
pub range_in_blamed_file: Range<u32>,
201-
/// Maps a commit to the range in a source file (i.e. *Blamed File* at a revision) that is equal to `range_in_blamed_file`.
202-
pub suspects: BTreeMap<ObjectId, Range<u32>>,
199+
/// Maps a commit to the range in a source file (i.e. *Blamed File* at a revision) that is
200+
/// equal to `range_in_blamed_file`. Since `suspects` rarely contains more than 1 item, it can
201+
/// efficiently be stored as a `SmallVec`.
202+
pub suspects: SmallVec<[(ObjectId, Range<u32>); 1]>,
203+
}
204+
205+
impl UnblamedHunk {
206+
pub(crate) fn has_suspect(&self, suspect: &ObjectId) -> bool {
207+
self.suspects.iter().any(|entry| entry.0 == *suspect)
208+
}
209+
210+
pub(crate) fn get_range(&self, suspect: &ObjectId) -> Option<&Range<u32>> {
211+
self.suspects
212+
.iter()
213+
.find(|entry| entry.0 == *suspect)
214+
.map(|entry| &entry.1)
215+
}
203216
}
204217

205218
#[derive(Debug)]

0 commit comments

Comments
 (0)