-
-
Notifications
You must be signed in to change notification settings - Fork 378
feat(gix-blame): library support for ignore-rev (#2064) #2123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
29c84a2
456639b
60c68bc
36e0a1c
bee12c4
26caa1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -12,6 +12,59 @@ use smallvec::SmallVec; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use super::{process_changes, Change, UnblamedHunk}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use crate::{types::BlamePathEntry, BlameEntry, Error, Options, Outcome, Statistics}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// Find the effective commit by walking back through ignored commits. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// Returns the first commit in the ancestry that is not ignored, or None if no such commit exists. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fn find_effective_commit( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
commit_id: gix_hash::ObjectId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ignored_revs: Option<&std::collections::HashSet<gix_hash::ObjectId>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
odb: &impl gix_object::Find, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cache: Option<&gix_commitgraph::Graph>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
buf: &mut Vec<u8>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> Result<Option<gix_hash::ObjectId>, Error> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let Some(ignored_set) = ignored_revs else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Ok(Some(commit_id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !ignored_set.contains(&commit_id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Ok(Some(commit_id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mut current = commit_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mut visited = std::collections::HashSet::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mut local_buf = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mut last_commit = commit_id; // Keep track of the last commit for fallback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
loop { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !ignored_set.contains(¤t) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Ok(Some(current)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Prevent infinite loops | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !visited.insert(current) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
last_commit = current; // Track the current commit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let commit = find_commit(cache, odb, ¤t, buf)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let parent_ids: ParentIds = collect_parents(commit, odb, cache, &mut local_buf)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if parent_ids.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Root commit - can't go further back | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// For ignored root commits, we have no choice but to attribute to the root itself | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Ok(Some(current)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// For simplicity, follow the first parent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// In a more complete implementation, we might need to handle multiple parents differently | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
current = parent_ids[0].0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment highlights incomplete merge commit handling. The current implementation only follows the first parent, which may not provide correct blame attribution for lines that originated from non-first parents in merge commits. Consider documenting this limitation more prominently or implementing proper multi-parent traversal.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is true that the current code only follows the first parent, but that’s deliberate here. This function’s job is to skip ignored commits and return the first non-ignored ancestor—not to do a full multi-parent walk. The proposed rewrite adds a lot of complexity, includes dead/commented code, introduces an early-return bug that short-circuits the stack walk, and can make results depend on parent order. In the blame context, simple and deterministic beats exhaustive, and Git’s first-parent precedence fits that. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// If we've walked back to the root and everything is ignored, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// return the root commit to ensure all hunks get attributed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Ok(Some(last_commit)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// Produce a list of consecutive [`BlameEntry`] instances to indicate in which commits the ranges of the file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// at `suspect:<file_path>` originated in. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -144,7 +197,15 @@ pub fn file( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(since) = options.since { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if commit_time < since.seconds { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if unblamed_to_out_is_done(&mut hunks_to_blame, &mut out, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if unblamed_to_out_is_done( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut hunks_to_blame, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut out, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suspect, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options.ignored_revs.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&odb, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cache.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut buf, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
break 'outer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -161,7 +222,15 @@ pub fn file( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// the remaining lines to it, even though we don’t explicitly check whether that is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// true here. We could perhaps use diff-tree-to-tree to compare `suspect` against | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// an empty tree to validate this assumption. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if unblamed_to_out_is_done(&mut hunks_to_blame, &mut out, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if unblamed_to_out_is_done( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut hunks_to_blame, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut out, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suspect, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options.ignored_revs.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&odb, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cache.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut buf, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(ref mut blame_path) = blame_path { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let entry = previous_entry | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.take() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -299,7 +368,15 @@ pub fn file( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Do nothing under the assumption that this always (or almost always) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// implies that the file comes from a different parent, compared to which | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// it was modified, not added. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if unblamed_to_out_is_done(&mut hunks_to_blame, &mut out, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if unblamed_to_out_is_done( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut hunks_to_blame, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut out, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suspect, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options.ignored_revs.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&odb, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cache.as_ref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
&mut buf, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(ref mut blame_path) = blame_path { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let blame_path_entry = BlamePathEntry { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
source_file_path: current_file_path.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -392,14 +469,27 @@ pub fn file( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hunks_to_blame.retain_mut(|unblamed_hunk| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if unblamed_hunk.suspects.len() == 1 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(entry) = BlameEntry::from_unblamed_hunk(unblamed_hunk, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// At this point, we have copied blame for every hunk to a parent. Hunks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// that have only `suspect` left in `suspects` have not passed blame to any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// parent, and so they can be converted to a `BlameEntry` and moved to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// `out`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
out.push(entry); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Find the effective commit (walk back through ignored commits) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Ok(Some(effective_commit)) = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
find_effective_commit(suspect, options.ignored_revs.as_ref(), &odb, cache.as_ref(), &mut buf2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(mut entry) = BlameEntry::from_unblamed_hunk(unblamed_hunk, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Replace the ignored commit with the effective commit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entry.commit_id = effective_commit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// At this point, we have copied blame for every hunk to a parent. Hunks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// that have only `suspect` left in `suspects` have not passed blame to any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// parent, and so they can be converted to a `BlameEntry` and moved to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// `out`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
out.push(entry); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// All reachable ancestors for these lines are in the ignored set. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Intentionally omit a blame entry to keep attribution deterministic at this layer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// instead of attributing to an ignored commit. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Covered by tests: ignore_revisions::consecutive_ignored_commits_transparent_walk | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// and ignore_revisions::merge_scenarios_with_ignored_parents. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// A future change may switch to 'attribute-to-nearest-ignored' to mirror `git blame --ignore-rev`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
unblamed_hunk.remove_blame(suspect); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -412,9 +502,10 @@ pub fn file( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"only if there is no portion of the file left we have completed the blame" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// I don’t know yet whether it would make sense to use a data structure instead that preserves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// I don't know yet whether it would make sense to use a data structure instead that preserves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// order on insertion. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
out.sort_by(|a, b| a.start_in_blamed_file.cmp(&b.start_in_blamed_file)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Ok(Outcome { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entries: coalesce_blame_entries(out), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
blob: blamed_file_blob, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -439,13 +530,32 @@ fn unblamed_to_out_is_done( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hunks_to_blame: &mut Vec<UnblamedHunk>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
out: &mut Vec<BlameEntry>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suspect: ObjectId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ignored_revs: Option<&std::collections::HashSet<gix_hash::ObjectId>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
odb: &impl gix_object::Find, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cache: Option<&gix_commitgraph::Graph>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
buf: &mut Vec<u8>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mut without_suspect = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
out.extend(hunks_to_blame.drain(..).filter_map(|hunk| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BlameEntry::from_unblamed_hunk(&hunk, suspect).or_else(|| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Some(mut entry) = BlameEntry::from_unblamed_hunk(&hunk, suspect) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Find the effective commit (walk back through ignored commits) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if let Ok(Some(effective_commit)) = find_effective_commit(suspect, ignored_revs, odb, cache, buf) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entry.commit_id = effective_commit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Some(entry) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// All reachable ancestors for these lines are in the ignored set. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Intentionally omit a blame entry to keep attribution deterministic at this layer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// instead of attributing to an ignored commit. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Covered by tests: ignore_revisions::consecutive_ignored_commits_transparent_walk | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// and ignore_revisions::merge_scenarios_with_ignored_parents. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// A future change may switch to 'attribute-to-nearest-ignored' to mirror `git blame --ignore-rev`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
without_suspect.push(hunk); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
without_suspect.push(hunk); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*hunks_to_blame = without_suspect; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hunks_to_blame.is_empty() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a
SmallVec
or similar optimized collection instead ofHashSet
for cycle detection. Most commit ancestry chains are relatively short, and a simple vector with linear search might be more efficient for small sets.Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally used a HashSet here.
The hot operation is membership checks during the DAG walk; HashSet keeps them ~O(1), while SmallVec would be O(n) and can degrade on longer ignore chains / merges.
As also noted in my analysis, the extra overhead is negligible compared to I/O, and the cycle set is unbounded in principle.
I’d keep HashSet for predictable worst-case behavior.