@@ -148,12 +148,13 @@ impl RefInfo {
148
148
else {
149
149
continue ;
150
150
} ;
151
- let identity_of_tip_to_base = Identifier :: ChangesetId ( id_for_tree_diff (
152
- repo,
153
- base_commit_id ,
154
- topmost_unintegrated_commit . tree_id ,
155
- ) ? ) ;
151
+ let Some ( changeset_id ) =
152
+ id_for_tree_diff ( repo, base_commit_id , topmost_unintegrated_commit . tree_id ) ?
153
+ else {
154
+ continue ;
155
+ } ;
156
156
157
+ let identity_of_tip_to_base = Identifier :: ChangesetId ( changeset_id) ;
157
158
let Some ( squashed_commit_id) = upstream_lut. get ( & identity_of_tip_to_base) . cloned ( )
158
159
else {
159
160
continue ;
@@ -209,15 +210,17 @@ impl PushStatus {
209
210
let first_commit = commits. first ( ) ;
210
211
let everything_integrated_locally =
211
212
first_commit. is_some_and ( |c| matches ! ( c. relation, LocalCommitRelation :: Integrated ( _) ) ) ;
213
+ let first_commit_is_local =
214
+ first_commit. is_some_and ( |c| matches ! ( c. relation, LocalCommitRelation :: LocalOnly ) ) ;
212
215
if everything_integrated_locally {
213
216
PushStatus :: Integrated
214
- } else if commits
215
- . iter ( )
216
- . any ( |c| matches ! ( c. relation, LocalCommitRelation :: LocalAndRemote ( id) if c. id != id) )
217
- {
217
+ } else if commits. iter ( ) . any ( |c| {
218
+ matches ! ( c. relation, LocalCommitRelation :: LocalAndRemote ( id) if c. id != id)
219
+ || ( first_commit_is_local
220
+ && matches ! ( c. relation, LocalCommitRelation :: Integrated ( _) ) )
221
+ } ) {
218
222
PushStatus :: UnpushedCommitsRequiringForce
219
- } else if first_commit. is_some_and ( |c| matches ! ( c. relation, LocalCommitRelation :: LocalOnly ) )
220
- {
223
+ } else if first_commit_is_local {
221
224
if remote_has_commits {
222
225
PushStatus :: UnpushedCommitsRequiringForce
223
226
} else {
@@ -236,11 +239,10 @@ fn changeset_identifier(
236
239
let Some ( commit) = commit else {
237
240
return Ok ( None ) ;
238
241
} ;
239
- Ok ( Some ( Identifier :: ChangesetId ( id_for_tree_diff (
240
- repo,
241
- commit. parent_ids . first ( ) . cloned ( ) ,
242
- commit. id ,
243
- ) ?) ) )
242
+ Ok (
243
+ id_for_tree_diff ( repo, commit. parent_ids . first ( ) . cloned ( ) , commit. id ) ?
244
+ . map ( Identifier :: ChangesetId ) ,
245
+ )
244
246
}
245
247
246
248
fn lookup_similar < ' a > (
@@ -276,6 +278,11 @@ fn create_similarity_lut(
276
278
}
277
279
match similarity_lut. entry ( k) {
278
280
Entry :: Occupied ( ambiguous) => {
281
+ if matches ! ( ambiguous. key( ) , Identifier :: ChangesetId ( _) ) {
282
+ // the most expensive option should never be ambiguous (which can happen with merges),
283
+ // so just keep the (typically top-most/first) commit with a changeset ID instead.
284
+ return ;
285
+ }
279
286
ambiguous_commits. insert ( ambiguous. key ( ) . clone ( ) ) ;
280
287
ambiguous. remove ( ) ;
281
288
}
@@ -297,18 +304,12 @@ fn create_similarity_lut(
297
304
commit. id ,
298
305
) ;
299
306
if expensive {
300
- // Just like with rename-tracking, let's not use 'empty' commits as specific unique value.
301
- if commit. tree_id . is_empty_tree ( ) {
307
+ let Some ( changeset_id) =
308
+ id_for_tree_diff ( repo, commit. parent_ids . first ( ) . cloned ( ) , commit. id ) ?
309
+ else {
302
310
continue ;
303
- }
304
- insert_or_expell_ambiguous (
305
- Identifier :: ChangesetId ( id_for_tree_diff (
306
- repo,
307
- commit. parent_ids . first ( ) . cloned ( ) ,
308
- commit. id ,
309
- ) ?) ,
310
- commit. id ,
311
- ) ;
311
+ } ;
312
+ insert_or_expell_ambiguous ( Identifier :: ChangesetId ( changeset_id) , commit. id ) ;
312
313
}
313
314
}
314
315
}
@@ -322,10 +323,21 @@ fn id_for_tree_diff(
322
323
repo : & gix:: Repository ,
323
324
lhs : Option < gix:: ObjectId > ,
324
325
rhs : gix:: ObjectId ,
325
- ) -> anyhow:: Result < ChangesetID > {
326
+ ) -> anyhow:: Result < Option < ChangesetID > > {
326
327
let lhs_tree = lhs. map ( |id| id_to_tree ( repo, id) ) . transpose ( ) ?;
327
328
let rhs_tree = id_to_tree ( repo, rhs) ?;
328
329
330
+ let no_changes = lhs_tree
331
+ . as_ref ( )
332
+ . map_or ( rhs_tree. id . is_empty_tree ( ) , |lhs_tree| {
333
+ lhs_tree. id == rhs_tree. id
334
+ } ) ;
335
+ if no_changes {
336
+ return Ok ( None ) ;
337
+ }
338
+ // TODO(perf): use plumbing directly to avoid resource-cache overhead.
339
+ // consider parallelization
340
+ // really needs caching to be practical, in-memory might suffice for now.
329
341
let changes = repo. diff_tree_to_tree (
330
342
lhs_tree. as_ref ( ) ,
331
343
& rhs_tree,
@@ -335,6 +347,9 @@ fn id_for_tree_diff(
335
347
// but would cost time, making it useless.
336
348
. track_rewrites ( None ) ,
337
349
) ?;
350
+ if changes. is_empty ( ) {
351
+ return Ok ( None ) ;
352
+ }
338
353
339
354
let mut hash = gix:: hash:: hasher ( gix:: hash:: Kind :: Sha1 ) ;
340
355
hash. update ( & [ CURRENT_VERSION as u8 ] ) ;
@@ -376,7 +391,7 @@ fn id_for_tree_diff(
376
391
}
377
392
}
378
393
}
379
- Ok ( hash. try_finalize ( ) ?)
394
+ Ok ( Some ( hash. try_finalize ( ) ?) )
380
395
}
381
396
382
397
// TODO: use `peel_to_tree()` once special conflict markers aren't needed anymore.
0 commit comments