1
- use std:: {
2
- borrow:: Cow ,
3
- collections:: HashMap ,
4
- path:: { Path , PathBuf } ,
5
- vec,
6
- } ;
7
-
8
1
use crate :: {
9
2
branch_manager:: BranchManagerExt ,
10
3
commit:: { commit_to_vbranch_commit, VirtualBranchCommit } ,
@@ -17,7 +10,7 @@ use crate::{
17
10
Get , VirtualBranchesExt ,
18
11
} ;
19
12
use anyhow:: { anyhow, bail, Context , Result } ;
20
- use bstr:: ByteSlice ;
13
+ use bstr:: { BString , ByteSlice } ;
21
14
use git2_hooks:: HookResult ;
22
15
use gitbutler_branch:: {
23
16
dedup, dedup_fmt, reconcile_claims, signature, Branch , BranchId , BranchOwnershipClaims ,
@@ -38,6 +31,13 @@ use gitbutler_repo::{
38
31
} ;
39
32
use gitbutler_time:: time:: now_since_unix_epoch_ms;
40
33
use serde:: Serialize ;
34
+ use std:: collections:: HashSet ;
35
+ use std:: {
36
+ borrow:: Cow ,
37
+ collections:: HashMap ,
38
+ path:: { Path , PathBuf } ,
39
+ vec,
40
+ } ;
41
41
use tracing:: instrument;
42
42
43
43
// this struct is a mapping to the view `Branch` type in Typescript
@@ -313,19 +313,34 @@ pub fn list_virtual_branches_cached(
313
313
) ) ?;
314
314
315
315
// find upstream commits if we found an upstream reference
316
- let mut pushed_commits = HashMap :: new ( ) ;
317
- if let Some ( upstream) = & upstram_branch_commit {
318
- let merge_base =
319
- repo. merge_base ( upstream. id ( ) , default_target. sha )
320
- . context ( format ! (
321
- "failed to find merge base between {} and {}" ,
322
- upstream. id( ) ,
323
- default_target. sha
324
- ) ) ?;
325
- for oid in ctx. l ( upstream. id ( ) , LogUntil :: Commit ( merge_base) ) ? {
326
- pushed_commits. insert ( oid, true ) ;
327
- }
328
- }
316
+ let ( remote_commit_ids, remote_commit_data) = upstram_branch_commit
317
+ . as_ref ( )
318
+ . map (
319
+ |upstream| -> Result < ( HashSet < git2:: Oid > , HashMap < CommitData , git2:: Oid > ) > {
320
+ let merge_base =
321
+ repo. merge_base ( upstream. id ( ) , default_target. sha )
322
+ . context ( format ! (
323
+ "failed to find merge base between {} and {}" ,
324
+ upstream. id( ) ,
325
+ default_target. sha
326
+ ) ) ?;
327
+ let remote_commit_ids =
328
+ HashSet :: from_iter ( ctx. l ( upstream. id ( ) , LogUntil :: Commit ( merge_base) ) ?) ;
329
+ let remote_commit_data: HashMap < _ , _ > = remote_commit_ids
330
+ . iter ( )
331
+ . copied ( )
332
+ . filter_map ( |id| repo. find_commit ( id) . ok ( ) )
333
+ . filter_map ( |commit| {
334
+ CommitData :: try_from ( & commit)
335
+ . ok ( )
336
+ . map ( |key| ( key, commit. id ( ) ) )
337
+ } )
338
+ . collect ( ) ;
339
+ Ok ( ( remote_commit_ids, remote_commit_data) )
340
+ } ,
341
+ )
342
+ . transpose ( ) ?
343
+ . unwrap_or_default ( ) ;
329
344
330
345
let mut is_integrated = false ;
331
346
let mut is_remote = false ;
@@ -346,15 +361,33 @@ pub fn list_virtual_branches_cached(
346
361
is_remote = if is_remote {
347
362
is_remote
348
363
} else {
349
- pushed_commits. contains_key ( & commit. id ( ) )
364
+ // This can only work once we have pushed our commits to the remote.
365
+ // Otherwise, even local commits created from a remote commit will look different.
366
+ remote_commit_ids. contains ( & commit. id ( ) )
350
367
} ;
351
368
352
369
// only check for integration if we haven't already found an integration
353
370
if !is_integrated {
354
371
is_integrated = check_commit. is_integrated ( commit) ?
355
372
} ;
356
373
357
- commit_to_vbranch_commit ( ctx, & branch, commit, is_integrated, is_remote)
374
+ let copied_from_remote_id = if is_remote {
375
+ // No need to use additional matching, the commits match already
376
+ // which does the right thing.
377
+ None
378
+ } else {
379
+ CommitData :: try_from ( commit)
380
+ . ok ( )
381
+ . and_then ( |data| remote_commit_data. get ( & data) . copied ( ) )
382
+ } ;
383
+ commit_to_vbranch_commit (
384
+ ctx,
385
+ & branch,
386
+ commit,
387
+ is_integrated,
388
+ is_remote || copied_from_remote_id. is_some ( ) ,
389
+ copied_from_remote_id,
390
+ )
358
391
} )
359
392
. collect :: < Result < Vec < _ > > > ( ) ?
360
393
} ;
@@ -427,6 +460,40 @@ pub fn list_virtual_branches_cached(
427
460
Ok ( ( branches, status. skipped_files ) )
428
461
}
429
462
463
+ /// The commit-data we can use for comparison to see which remote-commit was used to craete
464
+ /// a local commit from.
465
+ /// Note that trees can't be used for comparison as these are typically rebased.
466
+ #[ derive( Hash , Eq , PartialEq ) ]
467
+ struct CommitData {
468
+ message : BString ,
469
+ author : gix:: actor:: Signature ,
470
+ committer : gix:: actor:: Signature ,
471
+ }
472
+
473
+ impl TryFrom < & git2:: Commit < ' _ > > for CommitData {
474
+ type Error = anyhow:: Error ;
475
+
476
+ fn try_from ( commit : & git2:: Commit < ' _ > ) -> std:: result:: Result < Self , Self :: Error > {
477
+ Ok ( CommitData {
478
+ message : commit. message_raw_bytes ( ) . into ( ) ,
479
+ author : git2_signature_to_gix_signature ( commit. author ( ) ) ,
480
+ committer : git2_signature_to_gix_signature ( commit. committer ( ) ) ,
481
+ } )
482
+ }
483
+ }
484
+
485
+ fn git2_signature_to_gix_signature ( input : git2:: Signature < ' _ > ) -> gix:: actor:: Signature {
486
+ gix:: actor:: Signature {
487
+ name : input. name_bytes ( ) . into ( ) ,
488
+ email : input. email_bytes ( ) . into ( ) ,
489
+ time : gix:: date:: Time {
490
+ seconds : input. when ( ) . seconds ( ) ,
491
+ offset : input. when ( ) . offset_minutes ( ) * 60 ,
492
+ sign : input. when ( ) . offset_minutes ( ) . into ( ) ,
493
+ } ,
494
+ }
495
+ }
496
+
430
497
fn branches_with_large_files_abridged ( mut branches : Vec < VirtualBranch > ) -> Vec < VirtualBranch > {
431
498
for branch in & mut branches {
432
499
for file in & mut branch. files {
0 commit comments