@@ -12,11 +12,10 @@ use gitbutler_project::access::WorktreeReadPermission;
12
12
use gitbutler_reference:: normalize_branch_name;
13
13
use gitbutler_reference:: RemoteRefname ;
14
14
use gitbutler_serde:: BStringForFrontend ;
15
- use gitbutler_stack:: { Stack as GitButlerBranch , StackId , Target } ;
15
+ use gitbutler_stack:: { Stack , StackId , Target } ;
16
16
use gix:: object:: tree:: diff:: Action ;
17
- use gix:: prelude:: { ObjectIdExt , TreeDiffChangeExt } ;
17
+ use gix:: prelude:: TreeDiffChangeExt ;
18
18
use gix:: reference:: Category ;
19
- use itertools:: Itertools ;
20
19
use serde:: { Deserialize , Serialize } ;
21
20
use std:: borrow:: Cow ;
22
21
use std:: collections:: BTreeSet ;
@@ -59,7 +58,6 @@ pub fn list_branches(
59
58
repo. object_cache_size_if_unset ( 1024 * 1024 ) ;
60
59
let has_filter = filter. is_some ( ) ;
61
60
let filter = filter. unwrap_or_default ( ) ;
62
- let vb_handle = ctx. project ( ) . virtual_branches ( ) ;
63
61
let platform = repo. references ( ) ?;
64
62
let mut branches: Vec < GroupBranch > = vec ! [ ] ;
65
63
for reference in platform. all ( ) ?. filter_map ( Result :: ok) {
@@ -89,8 +87,15 @@ pub fn list_branches(
89
87
} ) ;
90
88
}
91
89
90
+ let vb_handle = ctx. project ( ) . virtual_branches ( ) ;
92
91
let stacks = vb_handle. list_all_stacks ( ) ?;
93
- branches. extend ( stacks. iter ( ) . map ( |s| GroupBranch :: Virtual ( s. clone ( ) ) ) ) ;
92
+ let remote_names = repo. remote_names ( ) ;
93
+ branches. extend (
94
+ stacks
95
+ . iter ( )
96
+ . map ( |s| GitButlerStack :: new ( s, & remote_names) . map ( GroupBranch :: Virtual ) )
97
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?,
98
+ ) ;
94
99
let mut branches = combine_branches ( branches, & repo, vb_handle. get_default_target ( ) ?) ?;
95
100
96
101
// Apply the filter
@@ -264,24 +269,20 @@ fn branch_group_to_branch(
264
269
}
265
270
266
271
// Virtual branch associated with this branch
267
- let virtual_branch_reference = virtual_branch. map ( |stack| StackReference {
268
- given_name : stack. name . clone ( ) ,
269
- id : stack. id ,
270
- in_workspace : stack. in_workspace ,
271
- branches : stack
272
- . branches ( )
273
- . iter ( )
274
- . filter ( |b| !b. archived )
275
- . rev ( )
276
- . map ( |b| b. name ( ) )
277
- . cloned ( )
278
- . collect_vec ( ) ,
279
- pull_requests : stack
280
- . branches ( )
281
- . iter ( )
282
- . filter ( |b| !b. archived )
283
- . filter_map ( |b| b. pr_number . map ( |pr| ( b. name ( ) . to_owned ( ) , pr) ) )
284
- . collect ( ) ,
272
+ let virtual_branch_reference = virtual_branch. map ( |stack| {
273
+ let unarchived_branches = stack. unarchived_segments . iter ( ) ;
274
+ StackReference {
275
+ given_name : stack. name . clone ( ) ,
276
+ id : stack. id ,
277
+ in_workspace : stack. in_workspace ,
278
+ branches : unarchived_branches
279
+ . clone ( )
280
+ . map ( |b| b. short_name ( ) )
281
+ . collect ( ) ,
282
+ pull_requests : unarchived_branches
283
+ . filter_map ( |b| b. pr_or_mr . map ( |pr| ( b. short_name ( ) . to_owned ( ) , pr) ) )
284
+ . collect ( ) ,
285
+ }
285
286
} ) ;
286
287
287
288
let mut remotes: Vec < gix:: remote:: Name < ' static > > = Vec :: new ( ) ;
@@ -298,7 +299,7 @@ fn branch_group_to_branch(
298
299
// If there is a virtual branch let's get it's head. Alternatively, pick the first local branch and use it's head.
299
300
// If there are no local branches, pick the first remote branch.
300
301
let head_commit = if let Some ( vbranch) = virtual_branch {
301
- Some ( vbranch. head_oid ( repo) ?. attach ( repo ) )
302
+ Some ( vbranch. head_oid ( repo) ?)
302
303
} else if let Some ( mut branch) = local_branches. into_iter ( ) . next ( ) {
303
304
branch. peel_to_id_in_place_packed ( packed) . ok ( )
304
305
} else if let Some ( mut branch) = remote_branches. into_iter ( ) . next ( ) {
@@ -329,11 +330,95 @@ fn branch_group_to_branch(
329
330
}
330
331
331
332
/// A sum type of branch that can be a plain git branch or a virtual branch
332
- #[ expect( clippy:: large_enum_variant) ]
333
333
enum GroupBranch < ' a > {
334
334
Local ( gix:: Reference < ' a > ) ,
335
335
Remote ( gix:: Reference < ' a > ) ,
336
- Virtual ( GitButlerBranch ) ,
336
+ Virtual ( GitButlerStack ) ,
337
+ }
338
+
339
+ /// A type to just keep the parts we currently need.
340
+ #[ derive( Debug ) ]
341
+ struct GitButlerStack {
342
+ id : StackId ,
343
+ /// `true` if the stack is applied to the workspace.
344
+ in_workspace : bool ,
345
+ /// The short name of the top-most segment.
346
+ name : String ,
347
+ /// The full ref name of the top-most segment.
348
+ source_refname : Option < gix:: refs:: FullName > ,
349
+ /// The full ref name of the remote tracking branch of the top-most segment.
350
+ upstream : Option < but_workspace:: ui:: ref_info:: RemoteTrackingReference > ,
351
+ /// The time at which anything in the stack was last updated.
352
+ updated_timestamp_ms : u128 ,
353
+ // All segments of the stack, as long as they are not archived.
354
+ // The tip comes first.
355
+ unarchived_segments : Vec < GitbutlerStackSegment > ,
356
+ }
357
+
358
+ #[ derive( Debug ) ]
359
+ struct GitbutlerStackSegment {
360
+ /// The name of the segment, without support for these to be anonymous (which is a problem).
361
+ tip : gix:: refs:: FullName ,
362
+ /// The PR or MR associated with it.
363
+ pr_or_mr : Option < usize > ,
364
+ }
365
+
366
+ impl GitbutlerStackSegment {
367
+ fn short_name ( & self ) -> String {
368
+ self . tip . shorten ( ) . to_string ( )
369
+ }
370
+ }
371
+
372
+ impl GitButlerStack {
373
+ fn new ( s : & Stack , names : & gix:: remote:: Names ) -> anyhow:: Result < Self > {
374
+ Ok ( GitButlerStack {
375
+ id : s. id ,
376
+ in_workspace : s. in_workspace ,
377
+ name : s. name . clone ( ) ,
378
+ source_refname : s
379
+ . source_refname
380
+ . as_ref ( )
381
+ . and_then ( |r| r. to_string ( ) . try_into ( ) . ok ( ) ) ,
382
+ upstream : s
383
+ . upstream
384
+ . as_ref ( )
385
+ . and_then ( |r| {
386
+ r. to_string ( ) . try_into ( ) . ok ( ) . map ( |rn| {
387
+ but_workspace:: ui:: ref_info:: RemoteTrackingReference :: for_ui ( rn, names)
388
+ } )
389
+ } )
390
+ . transpose ( ) ?,
391
+ updated_timestamp_ms : s. updated_timestamp_ms ,
392
+ unarchived_segments : s
393
+ . branches ( )
394
+ . iter ( )
395
+ // The tip is at the bottom here.
396
+ . rev ( )
397
+ . filter ( |s| !s. archived )
398
+ . map ( |s| GitbutlerStackSegment {
399
+ tip : s
400
+ . full_name ( )
401
+ . expect ( "full names are always valid, as their short names were valid" ) ,
402
+ pr_or_mr : s. pr_number ,
403
+ } )
404
+ . collect ( ) ,
405
+ } )
406
+ }
407
+ }
408
+
409
+ impl GitButlerStack {
410
+ /// Return the top-most stack's commit id.
411
+ fn head_oid < ' repo > ( & self , repo : & ' repo gix:: Repository ) -> anyhow:: Result < gix:: Id < ' repo > > {
412
+ let tip_ref = self
413
+ . unarchived_segments
414
+ . iter ( )
415
+ . map ( |s| s. tip . as_ref ( ) )
416
+ . next ( )
417
+ . with_context ( || format ! ( "Stack {} didn't have a tip ref name" , self . id) ) ?;
418
+ repo. find_reference ( tip_ref) ?
419
+ . try_id ( )
420
+ . with_context ( || format ! ( "'{}' was as symbolic reference" , tip_ref. shorten( ) ) )
421
+ }
337
422
}
338
423
339
424
impl fmt:: Debug for GroupBranch < ' _ > {
@@ -352,7 +437,7 @@ impl fmt::Debug for GroupBranch<'_> {
352
437
)
353
438
. finish ( ) ,
354
439
GroupBranch :: Virtual ( branch) => formatter
355
- . debug_struct ( "GroupBranch::Virtal " )
440
+ . debug_struct ( "GroupBranch::Virtual " )
356
441
. field ( "0" , branch)
357
442
. finish ( ) ,
358
443
}
@@ -370,12 +455,15 @@ impl GroupBranch<'_> {
370
455
}
371
456
// The identity of a Virtual branch is derived from the source refname, upstream or the branch given name, in that order
372
457
GroupBranch :: Virtual ( branch) => {
373
- let name_from_source = branch. source_refname . as_ref ( ) . and_then ( |n| n. branch ( ) ) ;
374
- let name_from_upstream = branch. upstream . as_ref ( ) . map ( |n| n. branch ( ) ) ;
458
+ let name_from_source = branch. source_refname . as_ref ( ) . map ( |n| n. shorten ( ) ) ;
459
+ let name_from_upstream = branch
460
+ . upstream
461
+ . as_ref ( )
462
+ . map ( |n| n. display_name . as_str ( ) . into ( ) ) ;
375
463
376
464
// If we have a source refname or upstream, use those directly
377
465
if let Some ( name) = name_from_source. or ( name_from_upstream) {
378
- return Some ( name. into ( ) ) ;
466
+ return name. try_into ( ) . ok ( ) ;
379
467
}
380
468
381
469
// Only fall back to the normalized rich name if no source/upstream is available
@@ -515,7 +603,7 @@ pub struct StackReference {
515
603
/// List of branches that are part of the stack
516
604
/// Ordered from newest to oldest (the most recent branch is first in the list)
517
605
pub branches : Vec < String > ,
518
- /// Pull Request numbes by branch name associated with the stack
606
+ /// Pull Request numbers by branch name associated with the stack
519
607
pub pull_requests : HashMap < String , usize > ,
520
608
}
521
609
0 commit comments