@@ -20,19 +20,20 @@ use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders};
20
20
use gitbutler_diff:: { trees, GitHunk , Hunk } ;
21
21
use gitbutler_error:: error:: Code ;
22
22
use gitbutler_operating_modes:: assure_open_workspace_mode;
23
- use gitbutler_oxidize:: git2_signature_to_gix_signature;
23
+ use gitbutler_oxidize:: { git2_signature_to_gix_signature, git2_to_gix_object_id , gix_to_git2_oid } ;
24
24
use gitbutler_project:: access:: WorktreeWritePermission ;
25
25
use gitbutler_reference:: { normalize_branch_name, Refname , RemoteRefname } ;
26
26
use gitbutler_repo:: {
27
27
rebase:: { cherry_rebase, cherry_rebase_group} ,
28
- LogUntil , RepositoryExt ,
28
+ GixRepositoryExt , LogUntil , RepositoryExt ,
29
29
} ;
30
30
use gitbutler_repo_actions:: RepoActionsExt ;
31
31
use gitbutler_stack:: {
32
32
reconcile_claims, BranchOwnershipClaims , ForgeIdentifier , Stack , StackId , Target ,
33
33
VirtualBranchesHandle ,
34
34
} ;
35
35
use gitbutler_time:: time:: now_since_unix_epoch_ms;
36
+ use gix:: objs:: Write ;
36
37
use serde:: Serialize ;
37
38
use std:: collections:: HashSet ;
38
39
use std:: { collections:: HashMap , path:: PathBuf , vec} ;
@@ -300,8 +301,15 @@ pub fn list_virtual_branches_cached(
300
301
301
302
let branches_span =
302
303
tracing:: debug_span!( "handle branches" , num_branches = status. branches. len( ) ) . entered ( ) ;
304
+ let repo = ctx. repository ( ) ;
305
+ let gix_repo = ctx
306
+ . gix_repository ( ) ?
307
+ . for_tree_diffing ( ) ?
308
+ . with_object_memory ( ) ;
309
+ // We will perform virtual merges, no need to write them to the ODB.
310
+ let cache = gix_repo. commit_graph_if_enabled ( ) ?;
311
+ let mut graph = gix_repo. revision_graph ( cache. as_ref ( ) ) ;
303
312
for ( mut branch, mut files) in status. branches {
304
- let repo = ctx. repository ( ) ;
305
313
update_conflict_markers ( ctx, files. clone ( ) ) ?;
306
314
307
315
let upstream_branch = match branch. clone ( ) . upstream {
@@ -323,13 +331,18 @@ pub fn list_virtual_branches_cached(
323
331
. as_ref ( )
324
332
. map (
325
333
|upstream| -> Result < ( HashSet < git2:: Oid > , HashMap < CommitData , git2:: Oid > ) > {
326
- let merge_base =
327
- repo. merge_base ( upstream. id ( ) , default_target. sha )
328
- . context ( format ! (
329
- "failed to find merge base between {} and {}" ,
330
- upstream. id( ) ,
331
- default_target. sha
332
- ) ) ?;
334
+ let merge_base = gix_repo
335
+ . merge_base_with_graph (
336
+ git2_to_gix_object_id ( upstream. id ( ) ) ,
337
+ git2_to_gix_object_id ( default_target. sha ) ,
338
+ & mut graph,
339
+ )
340
+ . context ( format ! (
341
+ "failed to find merge base between {} and {}" ,
342
+ upstream. id( ) ,
343
+ default_target. sha
344
+ ) ) ?;
345
+ let merge_base = gitbutler_oxidize:: gix_to_git2_oid ( merge_base) ;
333
346
let remote_commit_ids = HashSet :: from_iter ( repo. l (
334
347
upstream. id ( ) ,
335
348
LogUntil :: Commit ( merge_base) ,
@@ -356,7 +369,8 @@ pub fn list_virtual_branches_cached(
356
369
357
370
// find all commits on head that are not on target.sha
358
371
let commits = repo. log ( branch. head ( ) , LogUntil :: Commit ( default_target. sha ) , false ) ?;
359
- let check_commit = IsCommitIntegrated :: new ( ctx, & default_target) ?;
372
+ let mut check_commit =
373
+ IsCommitIntegrated :: new ( ctx, & default_target, & gix_repo, & mut graph) ?;
360
374
let vbranch_commits = {
361
375
let _span = tracing:: debug_span!(
362
376
"is-commit-integrated" ,
@@ -397,9 +411,14 @@ pub fn list_virtual_branches_cached(
397
411
. collect :: < Result < Vec < _ > > > ( ) ?
398
412
} ;
399
413
400
- let merge_base = repo
401
- . merge_base ( default_target. sha , branch. head ( ) )
414
+ let merge_base = gix_repo
415
+ . merge_base_with_graph (
416
+ git2_to_gix_object_id ( default_target. sha ) ,
417
+ git2_to_gix_object_id ( branch. head ( ) ) ,
418
+ check_commit. graph ,
419
+ )
402
420
. context ( "failed to find merge base" ) ?;
421
+ let merge_base = gix_to_git2_oid ( merge_base) ;
403
422
let base_current = true ;
404
423
405
424
let upstream = upstream_branch. and_then ( |upstream_branch| {
@@ -436,8 +455,9 @@ pub fn list_virtual_branches_cached(
436
455
ctx,
437
456
& mut branch,
438
457
& default_target,
439
- & check_commit,
458
+ & mut check_commit,
440
459
remote_commit_data,
460
+ & vbranch_commits,
441
461
) {
442
462
Ok ( ( series, force) ) => {
443
463
if series. iter ( ) . any ( |s| s. upstream_reference . is_some ( ) ) {
@@ -943,40 +963,50 @@ pub(crate) fn push(
943
963
} )
944
964
}
945
965
946
- pub ( crate ) struct IsCommitIntegrated < ' repo > {
947
- repo : & ' repo git2:: Repository ,
948
- target_commit_id : git2:: Oid ,
949
- remote_head_id : git2:: Oid ,
966
+ type MergeBaseCommitGraph < ' repo , ' cache > = gix:: revwalk:: Graph <
967
+ ' repo ,
968
+ ' cache ,
969
+ gix:: revision:: plumbing:: graph:: Commit < gix:: revision:: plumbing:: merge_base:: Flags > ,
970
+ > ;
971
+
972
+ pub ( crate ) struct IsCommitIntegrated < ' repo , ' cache , ' graph > {
973
+ gix_repo : & ' repo gix:: Repository ,
974
+ graph : & ' graph mut MergeBaseCommitGraph < ' repo , ' cache > ,
975
+ target_commit_id : gix:: ObjectId ,
976
+ upstream_tree_id : gix:: ObjectId ,
950
977
upstream_commits : Vec < git2:: Oid > ,
951
- /// A repository opened at the same path as `repo`, but with an in-memory ODB attached
952
- /// to avoid writing intermediate objects.
953
- inmemory_repo : git2:: Repository ,
954
978
}
955
979
956
- impl < ' repo > IsCommitIntegrated < ' repo > {
957
- pub ( crate ) fn new ( ctx : & ' repo CommandContext , target : & Target ) -> anyhow:: Result < Self > {
980
+ impl < ' repo , ' cache , ' graph > IsCommitIntegrated < ' repo , ' cache , ' graph > {
981
+ pub ( crate ) fn new (
982
+ ctx : & ' repo CommandContext ,
983
+ target : & Target ,
984
+ gix_repo : & ' repo gix:: Repository ,
985
+ graph : & ' graph mut MergeBaseCommitGraph < ' repo , ' cache > ,
986
+ ) -> anyhow:: Result < Self > {
958
987
let remote_branch = ctx
959
988
. repository ( )
960
989
. maybe_find_branch_by_refname ( & target. branch . clone ( ) . into ( ) ) ?
961
990
. ok_or ( anyhow ! ( "failed to get branch" ) ) ?;
962
991
let remote_head = remote_branch. get ( ) . peel_to_commit ( ) ?;
963
- let upstream_commits =
992
+ let mut upstream_commits =
964
993
ctx. repository ( )
965
994
. l ( remote_head. id ( ) , LogUntil :: Commit ( target. sha ) , false ) ?;
966
- let inmemory_repo = ctx. repository ( ) . in_memory_repo ( ) ?;
995
+ upstream_commits. sort ( ) ;
996
+ let upstream_tree_id = ctx. repository ( ) . find_commit ( remote_head. id ( ) ) ?. tree_id ( ) ;
967
997
Ok ( Self {
968
- repo : ctx. repository ( ) ,
969
- target_commit_id : target. sha ,
970
- remote_head_id : remote_head. id ( ) ,
998
+ gix_repo,
999
+ graph,
1000
+ target_commit_id : git2_to_gix_object_id ( target. sha ) ,
1001
+ upstream_tree_id : git2_to_gix_object_id ( upstream_tree_id) ,
971
1002
upstream_commits,
972
- inmemory_repo,
973
1003
} )
974
1004
}
975
1005
}
976
1006
977
- impl IsCommitIntegrated < ' _ > {
978
- pub ( crate ) fn is_integrated ( & self , commit : & git2:: Commit ) -> Result < bool > {
979
- if self . target_commit_id == commit. id ( ) {
1007
+ impl IsCommitIntegrated < ' _ , ' _ , ' _ > {
1008
+ pub ( crate ) fn is_integrated ( & mut self , commit : & git2:: Commit ) -> Result < bool > {
1009
+ if self . target_commit_id == git2_to_gix_object_id ( commit. id ( ) ) {
980
1010
// could not be integrated if heads are the same.
981
1011
return Ok ( false ) ;
982
1012
}
@@ -986,44 +1016,54 @@ impl IsCommitIntegrated<'_> {
986
1016
return Ok ( false ) ;
987
1017
}
988
1018
989
- if self . upstream_commits . contains ( & commit. id ( ) ) {
1019
+ if self . upstream_commits . binary_search ( & commit. id ( ) ) . is_ok ( ) {
990
1020
return Ok ( true ) ;
991
1021
}
992
1022
993
- let merge_base_id = self . repo . merge_base ( self . target_commit_id , commit. id ( ) ) ?;
994
- if merge_base_id. eq ( & commit. id ( ) ) {
1023
+ let merge_base_id = self . gix_repo . merge_base_with_graph (
1024
+ self . target_commit_id ,
1025
+ git2_to_gix_object_id ( commit. id ( ) ) ,
1026
+ self . graph ,
1027
+ ) ?;
1028
+ if gix_to_git2_oid ( merge_base_id) . eq ( & commit. id ( ) ) {
995
1029
// if merge branch is the same as branch head and there are upstream commits
996
1030
// then it's integrated
997
1031
return Ok ( true ) ;
998
1032
}
999
1033
1000
- let merge_base = self . repo . find_commit ( merge_base_id) ?;
1001
- let merge_base_tree = merge_base. tree ( ) ?;
1002
- let upstream = self . repo . find_commit ( self . remote_head_id ) ?;
1003
- let upstream_tree = upstream. tree ( ) ?;
1004
-
1005
- if merge_base_tree. id ( ) == upstream_tree. id ( ) {
1034
+ let merge_base_tree_id = self . gix_repo . find_commit ( merge_base_id) ?. tree_id ( ) ?;
1035
+ if merge_base_tree_id == self . upstream_tree_id {
1006
1036
// if merge base is the same as upstream tree, then it's integrated
1007
1037
return Ok ( true ) ;
1008
1038
}
1009
1039
1010
1040
// try to merge our tree into the upstream tree
1011
- let mut merge_index = self
1012
- . repo
1013
- . merge_trees ( & merge_base_tree, & commit. tree ( ) ?, & upstream_tree, None )
1041
+ let mut merge_options = self . gix_repo . tree_merge_options ( ) ?;
1042
+ let conflict_kind = gix:: merge:: tree:: UnresolvedConflict :: Renames ;
1043
+ merge_options. fail_on_conflict = Some ( conflict_kind) ;
1044
+ let mut merge_output = self
1045
+ . gix_repo
1046
+ . merge_trees (
1047
+ merge_base_tree_id,
1048
+ git2_to_gix_object_id ( commit. tree_id ( ) ) ,
1049
+ self . upstream_tree_id ,
1050
+ Default :: default ( ) ,
1051
+ merge_options,
1052
+ )
1014
1053
. context ( "failed to merge trees" ) ?;
1015
1054
1016
- if merge_index . has_conflicts ( ) {
1055
+ if merge_output . has_unresolved_conflicts ( conflict_kind ) {
1017
1056
return Ok ( false ) ;
1018
1057
}
1019
1058
1020
- let merge_tree_oid = merge_index
1021
- . write_tree_to ( & self . inmemory_repo )
1022
- . context ( "failed to write tree" ) ?;
1059
+ let merge_tree_id = merge_output
1060
+ . tree
1061
+ . write ( |tree| self . gix_repo . write ( tree) )
1062
+ . map_err ( |err| anyhow ! ( "failed to write tree: {err}" ) ) ?;
1023
1063
1024
1064
// if the merge_tree is the same as the new_target_tree and there are no files (uncommitted changes)
1025
1065
// then the vbranch is fully merged
1026
- Ok ( merge_tree_oid == upstream_tree . id ( ) )
1066
+ Ok ( merge_tree_id == self . upstream_tree_id )
1027
1067
}
1028
1068
}
1029
1069
0 commit comments