1- use std:: {
2- borrow:: Cow ,
3- collections:: HashMap ,
4- path:: { Path , PathBuf } ,
5- vec,
6- } ;
7-
81use crate :: {
92 branch_manager:: BranchManagerExt ,
103 commit:: { commit_to_vbranch_commit, VirtualBranchCommit } ,
@@ -17,7 +10,7 @@ use crate::{
1710 Get , VirtualBranchesExt ,
1811} ;
1912use anyhow:: { anyhow, bail, Context , Result } ;
20- use bstr:: ByteSlice ;
13+ use bstr:: { BString , ByteSlice } ;
2114use git2_hooks:: HookResult ;
2215use gitbutler_branch:: {
2316 dedup, dedup_fmt, reconcile_claims, signature, Branch , BranchId , BranchOwnershipClaims ,
@@ -38,6 +31,13 @@ use gitbutler_repo::{
3831} ;
3932use gitbutler_time:: time:: now_since_unix_epoch_ms;
4033use serde:: Serialize ;
34+ use std:: collections:: HashSet ;
35+ use std:: {
36+ borrow:: Cow ,
37+ collections:: HashMap ,
38+ path:: { Path , PathBuf } ,
39+ vec,
40+ } ;
4141use tracing:: instrument;
4242
4343// this struct is a mapping to the view `Branch` type in Typescript
@@ -313,19 +313,34 @@ pub fn list_virtual_branches_cached(
313313 ) ) ?;
314314
315315 // 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 ( ) ;
329344
330345 let mut is_integrated = false ;
331346 let mut is_remote = false ;
@@ -346,15 +361,28 @@ pub fn list_virtual_branches_cached(
346361 is_remote = if is_remote {
347362 is_remote
348363 } 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 ( ) )
350367 } ;
351368
352369 // only check for integration if we haven't already found an integration
353370 if !is_integrated {
354371 is_integrated = check_commit. is_integrated ( commit) ?
355372 } ;
356373
357- commit_to_vbranch_commit ( ctx, & branch, commit, is_integrated, is_remote)
374+ let copied_from_remote_id = CommitData :: try_from ( commit)
375+ . ok ( )
376+ . and_then ( |data| remote_commit_data. get ( & data) . copied ( ) ) ;
377+
378+ commit_to_vbranch_commit (
379+ ctx,
380+ & branch,
381+ commit,
382+ is_integrated,
383+ is_remote,
384+ copied_from_remote_id,
385+ )
358386 } )
359387 . collect :: < Result < Vec < _ > > > ( ) ?
360388 } ;
@@ -427,6 +455,40 @@ pub fn list_virtual_branches_cached(
427455 Ok ( ( branches, status. skipped_files ) )
428456}
429457
458+ /// The commit-data we can use for comparison to see which remote-commit was used to craete
459+ /// a local commit from.
460+ /// Note that trees can't be used for comparison as these are typically rebased.
461+ #[ derive( Hash , Eq , PartialEq ) ]
462+ struct CommitData {
463+ message : BString ,
464+ author : gix:: actor:: Signature ,
465+ committer : gix:: actor:: Signature ,
466+ }
467+
468+ impl TryFrom < & git2:: Commit < ' _ > > for CommitData {
469+ type Error = anyhow:: Error ;
470+
471+ fn try_from ( commit : & git2:: Commit < ' _ > ) -> std:: result:: Result < Self , Self :: Error > {
472+ Ok ( CommitData {
473+ message : commit. message_raw_bytes ( ) . into ( ) ,
474+ author : git2_signature_to_gix_signature ( commit. author ( ) ) ,
475+ committer : git2_signature_to_gix_signature ( commit. committer ( ) ) ,
476+ } )
477+ }
478+ }
479+
480+ fn git2_signature_to_gix_signature ( input : git2:: Signature < ' _ > ) -> gix:: actor:: Signature {
481+ gix:: actor:: Signature {
482+ name : input. name_bytes ( ) . into ( ) ,
483+ email : input. email_bytes ( ) . into ( ) ,
484+ time : gix:: date:: Time {
485+ seconds : input. when ( ) . seconds ( ) ,
486+ offset : input. when ( ) . offset_minutes ( ) * 60 ,
487+ sign : input. when ( ) . offset_minutes ( ) . into ( ) ,
488+ } ,
489+ }
490+ }
491+
430492fn branches_with_large_files_abridged ( mut branches : Vec < VirtualBranch > ) -> Vec < VirtualBranch > {
431493 for branch in & mut branches {
432494 for file in & mut branch. files {
0 commit comments