@@ -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
0 commit comments