8383 . count ( )
8484 } ;
8585
86+ // Binary or otherwise empty?
87+ if num_lines_in_blamed == 0 {
88+ return Ok ( Outcome :: default ( ) ) ;
89+ }
90+
8691 let mut hunks_to_blame = vec ! [ {
8792 let range_in_blamed_file = 0 ..num_lines_in_blamed as u32 ;
8893 UnblamedHunk {
9499 let mut out = Vec :: new ( ) ;
95100 let mut diff_state = gix_diff:: tree:: State :: default ( ) ;
96101 ' outer: while let Some ( item) = traverse. next ( ) {
102+ if hunks_to_blame. is_empty ( ) {
103+ break ;
104+ }
97105 let commit = item. map_err ( |err| Error :: Traverse ( err. into ( ) ) ) ?;
98106 let suspect = commit. id ;
99107 stats. commits_traversed += 1 ;
@@ -106,12 +114,13 @@ where
106114 // remaining lines to it, even though we don’t explicitly check whether that is true
107115 // here. We could perhaps use diff-tree-to-tree to compare `suspect`
108116 // against an empty tree to validate this assumption.
109- unblamed_to_out ( & mut hunks_to_blame, & mut out, suspect) ;
110- break ;
111- } else {
112- // There is more, keep looking.
113- continue ;
117+ if unblamed_to_out_is_done ( & mut hunks_to_blame, & mut out, suspect) {
118+ break ' outer;
119+ }
114120 }
121+
122+ // There is more, keep looking.
123+ continue ;
115124 }
116125
117126 let Some ( entry) = find_path_entry_in_commit ( & odb, & suspect, file_path, & mut buf, & mut buf2, & mut stats) ? else {
@@ -162,8 +171,9 @@ where
162171 // implies that the file comes from a different parent, compared to which
163172 // it was modified, not added.
164173 } else {
165- unblamed_to_out ( & mut hunks_to_blame, & mut out, suspect) ;
166- break ;
174+ if unblamed_to_out_is_done ( & mut hunks_to_blame, & mut out, suspect) {
175+ break ' outer;
176+ }
167177 }
168178 }
169179 gix_diff:: tree:: recorder:: Change :: Deletion { .. } => {
@@ -209,13 +219,22 @@ fn pass_blame_from_to(from: ObjectId, to: ObjectId, hunks_to_blame: &mut Vec<Unb
209219}
210220
211221/// Convert each of the unblamed hunk in `hunks_to_blame` into a [`BlameEntry`], consuming them in the process.
212- /// `suspect` is expected to be present in the suspect-map in each [`UnblamedHunk`].
213- fn unblamed_to_out ( hunks_to_blame : & mut Vec < UnblamedHunk > , out : & mut Vec < BlameEntry > , suspect : ObjectId ) {
214- out. extend (
215- hunks_to_blame
216- . drain ( ..)
217- . map ( |hunk| BlameEntry :: from_unblamed_hunk ( hunk, suspect) ) ,
218- ) ;
222+ ///
223+ /// Return `true` if we are done because `hunks_to_blame` is empty.
224+ fn unblamed_to_out_is_done (
225+ hunks_to_blame : & mut Vec < UnblamedHunk > ,
226+ out : & mut Vec < BlameEntry > ,
227+ suspect : ObjectId ,
228+ ) -> bool {
229+ let mut without_suspect = Vec :: new ( ) ;
230+ out. extend ( hunks_to_blame. drain ( ..) . filter_map ( |hunk| {
231+ BlameEntry :: from_unblamed_hunk ( & hunk, suspect) . or_else ( || {
232+ without_suspect. push ( hunk) ;
233+ None
234+ } )
235+ } ) ) ;
236+ * hunks_to_blame = without_suspect;
237+ hunks_to_blame. is_empty ( )
219238}
220239
221240/// This function merges adjacent blame entries. It merges entries that are adjacent both in the
0 commit comments