1+ use crate :: VirtualBranchesExt ;
2+ use anyhow:: { Context , Result } ;
3+ use bstr:: { BStr , BString , ByteSlice } ;
14use core:: fmt;
5+ use gitbutler_branch:: { Branch as GitButlerBranch , BranchId , ReferenceExtGix , Target } ;
6+ use gitbutler_command_context:: CommandContext ;
7+ use gitbutler_reference:: normalize_branch_name;
8+ use gix:: prelude:: ObjectIdExt ;
9+ use gix:: reference:: Category ;
10+ use serde:: { Deserialize , Serialize } ;
11+ use std:: borrow:: Cow ;
12+ use std:: collections:: BTreeSet ;
213use std:: {
314 cmp:: max,
415 collections:: { HashMap , HashSet } ,
516 fmt:: Debug ,
617 vec,
718} ;
819
9- use anyhow:: { Context , Result } ;
10- use bstr:: { BString , ByteSlice } ;
11- use gitbutler_branch:: { Branch as GitButlerBranch , BranchId , ReferenceExt , Target } ;
12- use gitbutler_command_context:: CommandContext ;
13- use gitbutler_reference:: normalize_branch_name;
14- use serde:: { Deserialize , Serialize } ;
15-
16- use crate :: VirtualBranchesExt ;
17-
1820/// Returns a list of branches associated with this project.
1921pub fn list_branches (
2022 ctx : & CommandContext ,
2123 filter : Option < BranchListingFilter > ,
2224 filter_branch_names : Option < Vec < BranchIdentity > > ,
2325) -> Result < Vec < BranchListing > > {
26+ let mut repo = gix:: open ( ctx. repository ( ) . path ( ) ) ?;
27+ repo. object_cache_size_if_unset ( 1024 * 1024 ) ;
2428 let has_filter = filter. is_some ( ) ;
2529 let filter = filter. unwrap_or_default ( ) ;
2630 let vb_handle = ctx. project ( ) . virtual_branches ( ) ;
31+ let platform = repo. references ( ) ?;
2732 let mut branches: Vec < GroupBranch > = vec ! [ ] ;
28- for ( branch , branch_type ) in ctx . repository ( ) . branches ( None ) ?. filter_map ( Result :: ok) {
33+ for reference in platform . all ( ) ?. filter_map ( Result :: ok) {
2934 // Loosely match on branch names
3035 if let Some ( branch_names) = & filter_branch_names {
31- let has_matching_name = branch_names. iter ( ) . any ( |branch_name| {
32- if let Ok ( Some ( name) ) = branch. name ( ) {
33- name. ends_with ( & branch_name. 0 )
34- } else {
35- false
36- }
37- } ) ;
36+ let has_matching_name = branch_names
37+ . iter ( )
38+ . any ( |branch_name| reference. name ( ) . as_bstr ( ) . ends_with_str ( & branch_name. 0 ) ) ;
3839
3940 if !has_matching_name {
4041 continue ;
4142 }
4243 }
4344
44- match branch_type {
45- git2:: BranchType :: Local => {
46- branches. push ( GroupBranch :: Local ( branch) ) ;
47- }
48- git2:: BranchType :: Remote => {
49- branches. push ( GroupBranch :: Remote ( branch) ) ;
50- }
45+ let is_local_branch = match reference. name ( ) . category ( ) {
46+ Some ( Category :: LocalBranch ) => true ,
47+ Some ( Category :: RemoteBranch ) => false ,
48+ _ => continue ,
5149 } ;
50+ branches. push ( if is_local_branch {
51+ GroupBranch :: Local ( reference)
52+ } else {
53+ GroupBranch :: Remote ( reference)
54+ } ) ;
5255 }
5356
5457 let virtual_branches = vb_handle. list_all_branches ( ) ?;
5558
5659 for branch in virtual_branches {
5760 branches. push ( GroupBranch :: Virtual ( branch) ) ;
5861 }
59- let mut branches = combine_branches ( branches, ctx , vb_handle. get_default_target ( ) ?) ?;
62+ let mut branches = combine_branches ( branches, & repo , vb_handle. get_default_target ( ) ?) ?;
6063
6164 // Apply the filter
6265 branches. retain ( |branch| !has_filter || matches_all ( branch, filter) ) ;
@@ -111,11 +114,11 @@ fn matches_all(branch: &BranchListing, filter: BranchListingFilter) -> bool {
111114
112115fn combine_branches (
113116 group_branches : Vec < GroupBranch > ,
114- ctx : & CommandContext ,
117+ repo : & gix :: Repository ,
115118 target_branch : Target ,
116119) -> Result < Vec < BranchListing > > {
117- let repo = ctx . repository ( ) ;
118- let remotes = repo. remotes ( ) ?;
120+ let remotes = repo . remote_names ( ) ;
121+ let packed = repo. refs . cached_packed_buffer ( ) ?;
119122
120123 // Group branches by identity
121124 let mut groups: HashMap < BranchIdentity , Vec < GroupBranch > > = HashMap :: new ( ) ;
@@ -134,8 +137,14 @@ fn combine_branches(
134137 Ok ( groups
135138 . into_iter ( )
136139 . filter_map ( |( identity, group_branches) | {
137- let res =
138- branch_group_to_branch ( & identity, group_branches, repo, & remotes, & target_branch) ;
140+ let res = branch_group_to_branch (
141+ & identity,
142+ group_branches,
143+ repo,
144+ packed. as_ref ( ) . map ( |p| & * * * p) ,
145+ & remotes,
146+ & target_branch,
147+ ) ;
139148 match res {
140149 Ok ( branch_entry) => branch_entry,
141150 Err ( err) => {
@@ -155,17 +164,22 @@ fn combine_branches(
155164fn branch_group_to_branch (
156165 identity : & BranchIdentity ,
157166 group_branches : Vec < GroupBranch > ,
158- repo : & git2:: Repository ,
159- remotes : & git2:: string_array:: StringArray ,
167+ repo : & gix:: Repository ,
168+ packed : Option < & gix:: refs:: packed:: Buffer > ,
169+ remotes : & BTreeSet < Cow < ' _ , BStr > > ,
160170 target : & Target ,
161171) -> Result < Option < BranchListing > > {
162- let mut vbranches = group_branches
163- . iter ( )
164- . filter_map ( |branch| match branch {
165- GroupBranch :: Virtual ( vb) => Some ( vb) ,
166- _ => None ,
167- } )
168- . collect :: < Vec < _ > > ( ) ;
172+ let ( local_branches, remote_branches, mut vbranches) =
173+ group_branches
174+ . into_iter ( )
175+ . fold ( ( Vec :: new ( ) , Vec :: new ( ) , Vec :: new ( ) ) , |mut acc, item| {
176+ match item {
177+ GroupBranch :: Local ( branch) => acc. 0 . push ( branch) ,
178+ GroupBranch :: Remote ( branch) => acc. 1 . push ( branch) ,
179+ GroupBranch :: Virtual ( branch) => acc. 2 . push ( branch) ,
180+ }
181+ acc
182+ } ) ;
169183
170184 let virtual_branch = if vbranches. len ( ) > 1 {
171185 vbranches. sort_by_key ( |virtual_branch| virtual_branch. updated_timestamp_ms ) ;
@@ -174,25 +188,10 @@ fn branch_group_to_branch(
174188 vbranches. first ( )
175189 } ;
176190
177- let remote_branches: Vec < & git2:: Branch > = group_branches
178- . iter ( )
179- . filter_map ( |branch| match branch {
180- GroupBranch :: Remote ( gb) => Some ( gb) ,
181- _ => None ,
182- } )
183- . collect ( ) ;
184- let local_branches: Vec < & git2:: Branch > = group_branches
185- . iter ( )
186- . filter_map ( |branch| match branch {
187- GroupBranch :: Local ( gb) => Some ( gb) ,
188- _ => None ,
189- } )
190- . collect ( ) ;
191-
192191 if virtual_branch. is_none ( )
193192 && local_branches
194193 . iter ( )
195- . any ( |b| b. get ( ) . given_name ( remotes) . as_deref ( ) . ok ( ) == Some ( target. branch . branch ( ) ) )
194+ . any ( |b| b. name ( ) . given_name ( remotes) . as_deref ( ) . ok ( ) == Some ( target. branch . branch ( ) ) )
196195 {
197196 return Ok ( None ) ;
198197 }
@@ -204,12 +203,11 @@ fn branch_group_to_branch(
204203 in_workspace : branch. in_workspace ,
205204 } ) ;
206205
206+ // TODO(ST): keep the type alive, don't reduce to BString
207207 let mut remotes: Vec < BString > = Vec :: new ( ) ;
208208 for branch in remote_branches. iter ( ) {
209- if let Some ( name) = branch. get ( ) . name ( ) {
210- if let Ok ( remote_name) = repo. branch_remote_name ( name) {
211- remotes. push ( remote_name. as_bstr ( ) . into ( ) ) ;
212- }
209+ if let Some ( remote_name) = branch. remote_name ( gix:: remote:: Direction :: Fetch ) {
210+ remotes. push ( remote_name. as_bstr ( ) . into ( ) ) ;
213211 }
214212 }
215213
@@ -218,21 +216,22 @@ fn branch_group_to_branch(
218216 // The head commit for which we calculate statistics.
219217 // If there is a virtual branch let's get it's head. Alternatively, pick the first local branch and use it's head.
220218 // If there are no local branches, pick the first remote branch.
221- let head = if let Some ( vbranch) = virtual_branch {
222- Some ( vbranch. head )
223- } else if let Some ( branch) = local_branches. first ( ) . cloned ( ) {
224- branch. get ( ) . peel_to_commit ( ) . ok ( ) . map ( |c| c . id ( ) )
225- } else if let Some ( branch) = remote_branches. first ( ) . cloned ( ) {
226- branch. get ( ) . peel_to_commit ( ) . ok ( ) . map ( |c| c . id ( ) )
219+ let head_commit = if let Some ( vbranch) = virtual_branch {
220+ Some ( git2_to_gix_object_id ( vbranch. head ) . attach ( repo ) )
221+ } else if let Some ( mut branch) = local_branches. into_iter ( ) . next ( ) {
222+ branch. peel_to_id_in_place_packed ( packed ) . ok ( )
223+ } else if let Some ( mut branch) = remote_branches. into_iter ( ) . next ( ) {
224+ branch. peel_to_id_in_place_packed ( packed ) . ok ( )
227225 } else {
228226 None
229227 }
230228 . context ( "Could not get any valid reference in order to build branch stats" ) ?;
231229
232- let head_commit = repo. find_commit ( head) ?;
233- // If this was a virtual branch and there was never any remote set, use the virtual branch name as the identity
230+ let head = gix_to_git2_oid ( head_commit. detach ( ) ) ;
231+ let head_commit = head_commit. object ( ) ?. try_into_commit ( ) ?;
232+ let head_commit = head_commit. decode ( ) ?;
234233 let last_modified_ms = max (
235- ( head_commit. time ( ) . seconds ( ) * 1000 ) as u128 ,
234+ ( head_commit. time ( ) . seconds * 1000 ) as u128 ,
236235 virtual_branch. map_or ( 0 , |x| x. updated_timestamp_ms ) ,
237236 ) ;
238237 let last_commiter = head_commit. author ( ) . into ( ) ;
@@ -248,30 +247,37 @@ fn branch_group_to_branch(
248247 } ) )
249248}
250249
250+ fn gix_to_git2_oid ( id : gix:: ObjectId ) -> git2:: Oid {
251+ git2:: Oid :: from_bytes ( id. as_bytes ( ) ) . expect ( "always valid" )
252+ }
253+
254+ fn git2_to_gix_object_id ( id : git2:: Oid ) -> gix:: ObjectId {
255+ gix:: ObjectId :: try_from ( id. as_bytes ( ) ) . expect ( "git2 oid is always valid" )
256+ }
257+
251258/// A sum type of branch that can be a plain git branch or a virtual branch
252259#[ allow( clippy:: large_enum_variant) ]
253260enum GroupBranch < ' a > {
254- Local ( git2 :: Branch < ' a > ) ,
255- Remote ( git2 :: Branch < ' a > ) ,
261+ Local ( gix :: Reference < ' a > ) ,
262+ Remote ( gix :: Reference < ' a > ) ,
256263 Virtual ( GitButlerBranch ) ,
257264}
258265
259266impl fmt:: Debug for GroupBranch < ' _ > {
260267 fn fmt ( & self , formatter : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
261268 match self {
262- GroupBranch :: Local ( branch) | GroupBranch :: Remote ( branch) => {
263- let reference = branch. get ( ) ;
264- let target = reference
265- . target ( )
266- . expect ( "Failed to reference target in debug formatting" ) ;
267- let name = reference
268- . name ( )
269- . expect ( "Failed to get reference name in debug" ) ;
270- formatter
271- . debug_struct ( "GroupBranch::Local/Remote" )
272- . field ( "0" , & & format ! ( "id: {}, name: {}" , target, name) . as_str ( ) )
273- . finish ( )
274- }
269+ GroupBranch :: Local ( branch) | GroupBranch :: Remote ( branch) => formatter
270+ . debug_struct ( "GroupBranch::Local/Remote" )
271+ . field (
272+ "0" ,
273+ & format ! (
274+ "id: {:?}, name: {}" ,
275+ branch. target( ) ,
276+ branch. name( ) . as_bstr( )
277+ )
278+ . as_str ( ) ,
279+ )
280+ . finish ( ) ,
275281 GroupBranch :: Virtual ( branch) => formatter
276282 . debug_struct ( "GroupBranch::Virtal" )
277283 . field ( "0" , branch)
@@ -284,10 +290,11 @@ impl GroupBranch<'_> {
284290 /// A name identifier for the branch. When multiple branches (e.g. virtual, local, remote) have the same identity,
285291 /// they are grouped together under the same `Branch` entry.
286292 /// `None` means an identity could not be obtained, which makes this branch odd enough to ignore.
287- fn identity ( & self , remotes : & git2 :: string_array :: StringArray ) -> Option < BranchIdentity > {
293+ fn identity ( & self , remotes : & BTreeSet < Cow < ' _ , BStr > > ) -> Option < BranchIdentity > {
288294 match self {
289- GroupBranch :: Local ( branch) => branch. get ( ) . given_name ( remotes) . ok ( ) ,
290- GroupBranch :: Remote ( branch) => branch. get ( ) . given_name ( remotes) . ok ( ) ,
295+ GroupBranch :: Local ( branch) | GroupBranch :: Remote ( branch) => {
296+ branch. name ( ) . given_name ( remotes) . ok ( )
297+ }
291298 // The identity of a Virtual branch is derived from the source refname, upstream or the branch given name, in that order
292299 GroupBranch :: Virtual ( branch) => {
293300 let name_from_source = branch. source_refname . as_ref ( ) . and_then ( |n| n. branch ( ) ) ;
@@ -354,7 +361,7 @@ pub struct BranchListing {
354361 /// The head of interest for the branch group, used for calculating branch statistics.
355362 /// If there is a virtual branch, a local branch and remote branches, the head is determined in the following order:
356363 /// 1. The head of the virtual branch
357- /// 2. The head of the local branch
364+ /// 2. The head of the first local branch
358365 /// 3. The head of the first remote branch
359366 #[ serde( skip) ]
360367 pub head : git2:: Oid ,
@@ -363,6 +370,7 @@ pub struct BranchListing {
363370/// Represents a "commit author" or "signature", based on the data from the git history
364371#[ derive( Debug , Clone , Serialize , PartialEq , Eq , Hash ) ]
365372pub struct Author {
373+ // TODO(ST): use `BString` here to not degenerate information
366374 /// The name of the author as configured in the git config
367375 pub name : Option < String > ,
368376 /// The email of the author as configured in the git config
@@ -403,6 +411,15 @@ impl From<git2::Signature<'_>> for Author {
403411 }
404412}
405413
414+ impl From < gix:: actor:: SignatureRef < ' _ > > for Author {
415+ fn from ( value : gix:: actor:: SignatureRef < ' _ > ) -> Self {
416+ Author {
417+ name : Some ( value. name . to_string ( ) ) ,
418+ email : Some ( value. email . to_string ( ) ) ,
419+ }
420+ }
421+ }
422+
406423/// Represents a reference to an associated virtual branch
407424#[ derive( Debug , Clone , Serialize , PartialEq ) ]
408425#[ serde( rename_all = "camelCase" ) ]
0 commit comments