@@ -91,51 +91,48 @@ where
91
91
92
92
let mut out = Vec :: new ( ) ;
93
93
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 ;
97
97
stats. commits_traversed += 1 ;
98
98
99
- let mut parent_ids = item . parent_ids ;
99
+ let parent_ids = commit . parent_ids ;
100
100
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
+ }
114
113
}
115
114
116
115
let Some ( entry) = find_path_entry_in_commit ( & odb, & suspect, file_path, & mut buf, & mut buf2, & mut stats) ? else {
117
116
continue ;
118
117
} ;
119
118
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 {
122
120
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) ?
124
122
{
125
123
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;
132
126
}
133
127
}
128
+ }
134
129
130
+ let more_than_one_parent = parent_ids. len ( ) > 1 ;
131
+ for parent_id in parent_ids {
135
132
let changes_for_file_path = tree_diff_at_file_path (
136
133
& odb,
137
134
file_path,
138
- item . id ,
135
+ commit . id ,
139
136
parent_id,
140
137
& mut stats,
141
138
& mut diff_state,
@@ -144,92 +141,42 @@ where
144
141
& mut buf3,
145
142
) ?;
146
143
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 {
206
145
// None of the changes affected the file we’re currently blaming. Pass blame
207
146
// to parent.
208
147
for unblamed_hunk in & mut hunks_to_blame {
209
148
unblamed_hunk. clone_blame ( suspect, parent_id) ;
210
149
}
150
+ } else {
151
+ pass_blame_from_to ( suspect, parent_id, & mut hunks_to_blame) ;
152
+ }
153
+ continue ;
154
+ } ;
211
155
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 {
217
159
// Do nothing under the assumption that this always (or almost always)
218
160
// implies that the file comes from a different parent, compared to which
219
161
// it was modified, not added.
220
162
//
221
163
// 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 ;
222
167
}
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 ) ;
231
176
}
232
177
}
178
+ }
179
+ if more_than_one_parent {
233
180
for unblamed_hunk in & mut hunks_to_blame {
234
181
unblamed_hunk. remove_blame ( suspect) ;
235
182
}
@@ -252,6 +199,24 @@ where
252
199
} )
253
200
}
254
201
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
+
255
220
/// This function merges adjacent blame entries. It merges entries that are adjacent both in the
256
221
/// blamed file and in the original file that introduced them. This follows `git`’s
257
222
/// behaviour. `libgit2`, as of 2024-09-19, only checks whether two entries are adjacent in the
0 commit comments