Skip to content

Commit b736ace

Browse files
committed
Replace todos!() with assertions or remove them.
1 parent 845d96a commit b736ace

File tree

3 files changed

+76
-115
lines changed

3 files changed

+76
-115
lines changed

crate-status.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ Check out the [performance discussion][gix-diff-performance] as well.
371371
- [ ] shallow-history support
372372
- [ ] rename tracking (track different paths through history)
373373
- [ ] commits to ignore
374+
- [ ] pass all blame-cornercases (from Git)
374375
* **Performance-Improvements**
375376
- [ ] use commit-graph bloom filter for performance
376377
- [ ] traverse input-commits in correct order without `compute_indegrees_to_depth()`

gix-blame/src/file/function.rs

Lines changed: 64 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -91,51 +91,48 @@ where
9191

9292
let mut out = Vec::new();
9393
let mut diff_state = gix_diff::tree::State::default();
94-
'outer: for item in traverse {
95-
let item = item.map_err(|err| Error::Traverse(err.into()))?;
96-
let suspect = item.id;
94+
'outer: while let Some(item) = traverse.next() {
95+
let commit = item.map_err(|err| Error::Traverse(err.into()))?;
96+
let suspect = commit.id;
9797
stats.commits_traversed += 1;
9898

99-
let mut parent_ids = item.parent_ids;
99+
let parent_ids = commit.parent_ids;
100100
if parent_ids.is_empty() {
101-
// I’m not entirely sure if this is correct yet. `suspect`, at this point, is the `id` of
102-
// the last `item` that was yielded by `traverse`, so it makes sense to assign the
103-
// remaining lines to it, even though we don’t explicitly check whether that is true
104-
// here. We could perhaps use `needed_to_obtain` to compare `suspect` against an empty
105-
// tree to validate this assumption.
106-
out.extend(
107-
hunks_to_blame
108-
.iter()
109-
.map(|hunk| BlameEntry::from_unblamed_hunk(hunk, suspect)),
110-
);
111-
112-
hunks_to_blame.clear();
113-
break;
101+
if traverse.peek().is_none() {
102+
// I’m not entirely sure if this is correct yet. `suspect`, at this point, is the `id` of
103+
// the last `item` that was yielded by `traverse`, so it makes sense to assign the
104+
// remaining lines to it, even though we don’t explicitly check whether that is true
105+
// here. We could perhaps use diff-tree-to-tree to compare `suspect`
106+
// against an empty tree to validate this assumption.
107+
unblamed_to_out(&mut hunks_to_blame, &mut out, suspect);
108+
break;
109+
} else {
110+
// There is more, keep looking.
111+
continue;
112+
}
114113
}
115114

116115
let Some(entry) = find_path_entry_in_commit(&odb, &suspect, file_path, &mut buf, &mut buf2, &mut stats)? else {
117116
continue;
118117
};
119118

120-
if parent_ids.len() == 1 {
121-
let parent_id = parent_ids.pop().expect("just validated there is exactly one");
119+
for parent_id in &parent_ids {
122120
if let Some(parent_entry) =
123-
find_path_entry_in_commit(&odb, &parent_id, file_path, &mut buf, &mut buf2, &mut stats)?
121+
find_path_entry_in_commit(&odb, parent_id, file_path, &mut buf, &mut buf2, &mut stats)?
124122
{
125123
if entry.oid == parent_entry.oid {
126-
// The blobs storing the blamed file in `entry` and `parent_entry` are identical
127-
// which is why we can pass blame to the parent without further checks.
128-
for unblamed_hunk in &mut hunks_to_blame {
129-
unblamed_hunk.pass_blame(suspect, parent_id);
130-
}
131-
continue;
124+
pass_blame_from_to(suspect, *parent_id, &mut hunks_to_blame);
125+
continue 'outer;
132126
}
133127
}
128+
}
134129

130+
let more_than_one_parent = parent_ids.len() > 1;
131+
for parent_id in parent_ids {
135132
let changes_for_file_path = tree_diff_at_file_path(
136133
&odb,
137134
file_path,
138-
item.id,
135+
commit.id,
139136
parent_id,
140137
&mut stats,
141138
&mut diff_state,
@@ -144,92 +141,42 @@ where
144141
&mut buf3,
145142
)?;
146143
let Some(modification) = changes_for_file_path else {
147-
// None of the changes affected the file we’re currently blaming. Pass blame to parent.
148-
for unblamed_hunk in &mut hunks_to_blame {
149-
unblamed_hunk.pass_blame(suspect, parent_id);
150-
}
151-
continue;
152-
};
153-
154-
match modification {
155-
gix_diff::tree::recorder::Change::Addition { .. } => {
156-
// Every line that has not been blamed yet on a commit, is expected to have been
157-
// added when the file was added to the repository.
158-
out.extend(
159-
hunks_to_blame
160-
.iter()
161-
.map(|hunk| BlameEntry::from_unblamed_hunk(hunk, suspect)),
162-
);
163-
164-
hunks_to_blame.clear();
165-
break;
166-
}
167-
gix_diff::tree::recorder::Change::Deletion { .. } => todo!(),
168-
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
169-
let changes = blob_changes(&odb, resource_cache, oid, previous_oid, file_path, &mut stats)?;
170-
hunks_to_blame = process_changes(&mut out, hunks_to_blame, changes, suspect);
171-
for unblamed_hunk in &mut hunks_to_blame {
172-
unblamed_hunk.pass_blame(suspect, parent_id);
173-
}
174-
}
175-
}
176-
} else {
177-
for parent_id in &parent_ids {
178-
if let Some(parent_entry) =
179-
find_path_entry_in_commit(&odb, parent_id, file_path, &mut buf, &mut buf2, &mut stats)?
180-
{
181-
if entry.oid == parent_entry.oid {
182-
// The blobs storing the blamed file in `entry` and `parent_entry` are
183-
// identical which is why we can pass blame to the parent without further
184-
// checks.
185-
for unblamed_hunk in &mut hunks_to_blame {
186-
unblamed_hunk.pass_blame(suspect, *parent_id);
187-
}
188-
continue 'outer;
189-
}
190-
}
191-
}
192-
193-
for parent_id in parent_ids {
194-
let changes_for_file_path = tree_diff_at_file_path(
195-
&odb,
196-
file_path,
197-
item.id,
198-
parent_id,
199-
&mut stats,
200-
&mut diff_state,
201-
&mut buf,
202-
&mut buf2,
203-
&mut buf3,
204-
)?;
205-
let Some(modification) = changes_for_file_path else {
144+
if more_than_one_parent {
206145
// None of the changes affected the file we’re currently blaming. Pass blame
207146
// to parent.
208147
for unblamed_hunk in &mut hunks_to_blame {
209148
unblamed_hunk.clone_blame(suspect, parent_id);
210149
}
150+
} else {
151+
pass_blame_from_to(suspect, parent_id, &mut hunks_to_blame);
152+
}
153+
continue;
154+
};
211155

212-
continue;
213-
};
214-
215-
match modification {
216-
gix_diff::tree::recorder::Change::Addition { .. } => {
156+
match modification {
157+
gix_diff::tree::recorder::Change::Addition { .. } => {
158+
if more_than_one_parent {
217159
// Do nothing under the assumption that this always (or almost always)
218160
// implies that the file comes from a different parent, compared to which
219161
// it was modified, not added.
220162
//
221163
// TODO: I still have to figure out whether this is correct in all cases.
164+
} else {
165+
unblamed_to_out(&mut hunks_to_blame, &mut out, suspect);
166+
break;
222167
}
223-
gix_diff::tree::recorder::Change::Deletion { .. } => todo!(),
224-
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
225-
let changes = blob_changes(&odb, resource_cache, oid, previous_oid, file_path, &mut stats)?;
226-
hunks_to_blame = process_changes(&mut out, hunks_to_blame, changes, suspect);
227-
for unblamed_hunk in &mut hunks_to_blame {
228-
unblamed_hunk.pass_blame(suspect, parent_id);
229-
}
230-
}
168+
}
169+
gix_diff::tree::recorder::Change::Deletion { .. } => {
170+
unreachable!("We already found file_path in suspect^{{tree}}, so it can't be deleted")
171+
}
172+
gix_diff::tree::recorder::Change::Modification { previous_oid, oid, .. } => {
173+
let changes = blob_changes(&odb, resource_cache, oid, previous_oid, file_path, &mut stats)?;
174+
hunks_to_blame = process_changes(&mut out, hunks_to_blame, changes, suspect);
175+
pass_blame_from_to(suspect, parent_id, &mut hunks_to_blame);
231176
}
232177
}
178+
}
179+
if more_than_one_parent {
233180
for unblamed_hunk in &mut hunks_to_blame {
234181
unblamed_hunk.remove_blame(suspect);
235182
}
@@ -252,6 +199,24 @@ where
252199
})
253200
}
254201

202+
/// The blobs storing the blamed file in `entry` and `parent_entry` are identical which is why
203+
/// we can pass blame to the parent without further checks.
204+
fn pass_blame_from_to(from: ObjectId, to: ObjectId, hunks_to_blame: &mut Vec<UnblamedHunk>) {
205+
for unblamed_hunk in hunks_to_blame {
206+
unblamed_hunk.pass_blame(from, to);
207+
}
208+
}
209+
210+
fn unblamed_to_out(hunks_to_blame: &mut Vec<UnblamedHunk>, out: &mut Vec<BlameEntry>, suspect: ObjectId) {
211+
// Every line that has not been blamed yet on a commit, is expected to have been
212+
// added when the file was added to the repository.
213+
out.extend(
214+
hunks_to_blame
215+
.drain(..)
216+
.map(|hunk| BlameEntry::from_unblamed_hunk(hunk, suspect)),
217+
);
218+
}
219+
255220
/// This function merges adjacent blame entries. It merges entries that are adjacent both in the
256221
/// blamed file and in the original file that introduced them. This follows `git`’s
257222
/// behaviour. `libgit2`, as of 2024-09-19, only checks whether two entries are adjacent in the

gix-blame/src/file/mod.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,12 @@ fn process_change(
9090
};
9191

9292
let range_in_suspect = range_in_suspect.clone();
93-
94-
match (
95-
range_in_suspect.contains(&added.start),
96-
// Since `added` is a range that is not inclusive at the end, `added.end` is
97-
// not part of `added`. The first line that is `added.end - 1`.
98-
(added.end - 1) >= range_in_suspect.start && added.end <= range_in_suspect.end,
99-
) {
93+
let range_contains_added_start = range_in_suspect.contains(&added.start);
94+
// Since `added` is a range that is not inclusive at the end, `added.end` is
95+
// not part of `added`. The first line that is `added.end - 1`.
96+
let range_contains_added_end =
97+
(added.end - 1) >= range_in_suspect.start && added.end <= range_in_suspect.end;
98+
match (range_contains_added_start, range_contains_added_end) {
10099
(true, true) => {
101100
// <----------> (hunk)
102101
// <---> (added)
@@ -147,11 +146,7 @@ fn process_change(
147146
new_hunk.offset_for(suspect),
148147
));
149148

150-
if added.end > range_in_suspect.end {
151-
(None, Some(Change::Added(added, number_of_lines_deleted)))
152-
} else {
153-
todo!();
154-
}
149+
(None, Some(Change::Added(added, number_of_lines_deleted)))
155150
}
156151
(false, true) => {
157152
// <-------> (hunk)
@@ -442,15 +437,15 @@ impl BlameEntry {
442437
}
443438

444439
/// Create an offset from a portion of the *Original File*.
445-
fn from_unblamed_hunk(unblamed_hunk: &UnblamedHunk, commit_id: ObjectId) -> Self {
440+
fn from_unblamed_hunk(mut unblamed_hunk: UnblamedHunk, commit_id: ObjectId) -> Self {
446441
let range_in_original_file = unblamed_hunk
447442
.suspects
448-
.get(&commit_id)
443+
.remove(&commit_id)
449444
.expect("Private and only called when we now `commit_id` is in the suspect list");
450445

451446
Self {
452-
range_in_blamed_file: unblamed_hunk.range_in_blamed_file.clone(),
453-
range_in_original_file: range_in_original_file.clone(),
447+
range_in_blamed_file: unblamed_hunk.range_in_blamed_file,
448+
range_in_original_file,
454449
commit_id,
455450
}
456451
}

0 commit comments

Comments
 (0)