Skip to content

Commit eb6f420

Browse files
committed
Pass blame to more than one parent
1 parent 20710a1 commit eb6f420

File tree

3 files changed

+63
-32
lines changed

3 files changed

+63
-32
lines changed

gitoxide-core/src/repository/blame.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ pub fn blame_file(mut repo: gix::Repository, file: &OsStr, out: impl std::io::Wr
66
repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
77

88
let suspect = repo.head()?.peel_to_commit_in_place()?;
9-
let traverse: Vec<_> = gix::traverse::commit::Simple::new(Some(suspect.id), &repo.objects).collect();
9+
let traverse: Vec<_> = gix::traverse::commit::Simple::new(Some(suspect.id), &repo.objects)
10+
.sorting(gix::traverse::commit::simple::Sorting::ByCommitTime(
11+
gix::traverse::commit::simple::CommitTimeOrder::NewestFirst,
12+
))?
13+
.collect();
1014
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
1115

1216
let work_dir: PathBuf = repo.work_dir().expect("TODO").into();

gix-blame/src/lib.rs

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ impl UnblamedHunk {
262262
self.suspects.insert(to, range_in_suspect);
263263
}
264264
}
265+
266+
fn clone_blame(&mut self, from: ObjectId, to: ObjectId) {
267+
if let Some(range_in_suspect) = self.suspects.get(&from) {
268+
self.suspects.insert(to, range_in_suspect.clone());
269+
}
270+
}
271+
272+
fn remove_blame(&mut self, suspect: ObjectId) {
273+
let _ = self.suspects.remove(&suspect);
274+
}
265275
}
266276

267277
#[derive(Clone, Debug, PartialEq)]
@@ -336,7 +346,11 @@ pub fn process_change(
336346
) -> (Option<UnblamedHunk>, Option<Change>) {
337347
match (hunk, change) {
338348
(Some(hunk), Some(Change::Unchanged(unchanged))) => {
339-
let range_in_suspect = hunk.suspects.get(&suspect).expect("TODO");
349+
let Some(range_in_suspect) = hunk.suspects.get(&suspect) else {
350+
new_hunks_to_blame.push(hunk);
351+
352+
return (None, Some(Change::Unchanged(unchanged)));
353+
};
340354

341355
match (
342356
// Since `unchanged` is a range that is not inclusive at the end,
@@ -799,46 +813,55 @@ pub fn blame_file<E>(
799813
break;
800814
}
801815

802-
if parent_ids.len() > 1 {
803-
todo!("passing blame to more than one parent is not implemented yet");
804-
}
816+
if parent_ids.len() == 1 {
817+
let parent_id: ObjectId = *parent_ids.last().unwrap();
805818

806-
let last_parent_id: ObjectId = *parent_ids.last().unwrap();
819+
let changes_for_file_path = get_changes_for_file_path(&odb, file_path, item.id, parent_id);
807820

808-
let changes_for_file_path = get_changes_for_file_path(&odb, file_path, item.id, last_parent_id);
821+
let [ref modification]: [gix_diff::tree::recorder::Change] = changes_for_file_path[..] else {
822+
// None of the changes affected the file we’re currently blaming. Pass blame to parent.
823+
hunks_to_blame
824+
.iter_mut()
825+
.for_each(|unblamed_hunk| unblamed_hunk.pass_blame(suspect, parent_id));
809826

810-
let [ref modification]: [gix_diff::tree::recorder::Change] = changes_for_file_path[..] else {
811-
// None of the changes affected the file we’re currently blaming. Pass blame to parent.
812-
hunks_to_blame
813-
.iter_mut()
814-
.for_each(|unblamed_hunk| unblamed_hunk.pass_blame(suspect, last_parent_id));
827+
continue;
828+
};
815829

816-
continue;
817-
};
830+
match modification {
831+
gix_diff::tree::recorder::Change::Addition { .. } => {
832+
// Every line that has not been blamed yet on a commit, is expected to have been
833+
// added when the file was added to the repository.
834+
out.extend(
835+
hunks_to_blame
836+
.iter()
837+
.map(|hunk| BlameEntry::from_unblamed_hunk(hunk, suspect)),
838+
);
818839

819-
match modification {
820-
gix_diff::tree::recorder::Change::Addition { .. } => {
821-
// Every line that has not been blamed yet on a commit, is expected to have been
822-
// added when the file was added to the repository.
823-
out.extend(
824-
hunks_to_blame
825-
.iter()
826-
.map(|hunk| BlameEntry::from_unblamed_hunk(hunk, suspect)),
827-
);
840+
hunks_to_blame = vec![];
828841

829-
hunks_to_blame = vec![];
842+
break;
843+
}
844+
gix_diff::tree::recorder::Change::Deletion { .. } => todo!(),
845+
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
846+
let changes = get_changes(&odb, resource_cache, *oid, *previous_oid, file_path);
830847

831-
break;
848+
hunks_to_blame = process_changes(&mut out, &hunks_to_blame, &changes, suspect);
849+
hunks_to_blame
850+
.iter_mut()
851+
.for_each(|unblamed_hunk| unblamed_hunk.pass_blame(suspect, parent_id));
852+
}
832853
}
833-
gix_diff::tree::recorder::Change::Deletion { .. } => todo!(),
834-
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
835-
let changes = get_changes(&odb, resource_cache, *oid, *previous_oid, file_path);
836-
837-
hunks_to_blame = process_changes(&mut out, &hunks_to_blame, &changes, suspect);
854+
} else {
855+
for parent_id in parent_ids {
856+
// Pass blame to parent.
838857
hunks_to_blame
839858
.iter_mut()
840-
.for_each(|unblamed_hunk| unblamed_hunk.pass_blame(suspect, last_parent_id));
859+
.for_each(|unblamed_hunk| unblamed_hunk.clone_blame(suspect, parent_id));
841860
}
861+
862+
hunks_to_blame
863+
.iter_mut()
864+
.for_each(|unblamed_hunk| unblamed_hunk.remove_blame(suspect));
842865
}
843866
}
844867

gix-blame/tests/blame.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ impl Fixture {
139139

140140
let head_id = reference.peel_to_id_in_place(&store, &odb)?;
141141

142-
let commits: Vec<_> = gix_traverse::commit::Simple::new(Some(head_id), &odb).collect();
142+
let commits: Vec<_> = gix_traverse::commit::Simple::new(Some(head_id), &odb)
143+
.sorting(gix_traverse::commit::simple::Sorting::ByCommitTime(
144+
gix_traverse::commit::simple::CommitTimeOrder::NewestFirst,
145+
))?
146+
.collect();
143147

144148
let git_dir = worktree_path.join(".git");
145149
let index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default())?;

0 commit comments

Comments
 (0)