@@ -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,7 +87,36 @@ pub fn list_branches(
89
87
} ) ;
90
88
}
91
89
92
- let stacks = vb_handle. list_all_stacks ( ) ?;
90
+ let vb_handle = ctx. project ( ) . virtual_branches ( ) ;
91
+ let remote_names = repo. remote_names ( ) ;
92
+ let stacks = if ctx. app_settings ( ) . feature_flags . ws3 {
93
+ if let Some ( workspace_ref) = repo. try_find_reference ( "refs/heads/gitbutler/workspace" ) ? {
94
+ // Let's get this here for convenience, and hope this isn't ever called by a writer (or there will be a deadlock).
95
+ let read_guard = ctx. project ( ) . shared_worktree_access ( ) ;
96
+ let meta = ctx. meta ( read_guard. read_permission ( ) ) ?;
97
+ let info = but_workspace:: ref_info (
98
+ workspace_ref,
99
+ & * meta,
100
+ but_workspace:: ref_info:: Options {
101
+ traversal : but_graph:: init:: Options :: limited ( ) ,
102
+ expensive_commit_info : false ,
103
+ } ,
104
+ ) ?;
105
+ info. stacks
106
+ . into_iter ( )
107
+ . map ( |s| GitButlerStack :: new ( s, & remote_names) )
108
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?
109
+ } else {
110
+ Vec :: new ( )
111
+ }
112
+ } else {
113
+ vb_handle
114
+ . list_all_stacks ( ) ?
115
+ . iter ( )
116
+ . map ( |s| GitButlerStack :: new_from_old ( s, & remote_names) )
117
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?
118
+ } ;
119
+
93
120
branches. extend ( stacks. iter ( ) . map ( |s| GroupBranch :: Virtual ( s. clone ( ) ) ) ) ;
94
121
let mut branches = combine_branches ( branches, & repo, vb_handle. get_default_target ( ) ?) ?;
95
122
@@ -130,13 +157,13 @@ pub fn list_branches(
130
157
// To do this, we build up a list of all the branch identities that are
131
158
// part of a stack and then filter out any branches that have been grouped
132
159
// without a stack and match one of these identities.
133
- let branch_identities_to_exclude = stacks
134
- . iter ( )
160
+ let branch_identities_to_exclude: HashSet < BString > = stacks
161
+ . into_iter ( )
135
162
. flat_map ( |s| {
136
- s. branches ( )
163
+ s. unarchived_segments
137
164
. into_iter ( )
138
- . map ( |b| BString :: from ( b . name ( ) . to_owned ( ) ) )
139
- . chain ( [ BString :: from ( s. name . to_owned ( ) ) ] )
165
+ . map ( |b| b . short_name ( ) . into ( ) )
166
+ . chain ( Some ( s. name . into ( ) ) )
140
167
} )
141
168
. collect :: < HashSet < _ > > ( ) ;
142
169
@@ -264,24 +291,20 @@ fn branch_group_to_branch(
264
291
}
265
292
266
293
// 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 ( ) ,
294
+ let virtual_branch_reference = virtual_branch. map ( |stack| {
295
+ let unarchived_branches = stack. unarchived_segments . iter ( ) ;
296
+ StackReference {
297
+ given_name : stack. name . clone ( ) ,
298
+ id : stack. id ,
299
+ in_workspace : stack. in_workspace ,
300
+ branches : unarchived_branches
301
+ . clone ( )
302
+ . map ( |b| b. short_name ( ) )
303
+ . collect ( ) ,
304
+ pull_requests : unarchived_branches
305
+ . filter_map ( |b| b. pr_or_mr . map ( |pr| ( b. short_name ( ) . to_owned ( ) , pr) ) )
306
+ . collect ( ) ,
307
+ }
285
308
} ) ;
286
309
287
310
let mut remotes: Vec < gix:: remote:: Name < ' static > > = Vec :: new ( ) ;
@@ -298,7 +321,7 @@ fn branch_group_to_branch(
298
321
// If there is a virtual branch let's get it's head. Alternatively, pick the first local branch and use it's head.
299
322
// If there are no local branches, pick the first remote branch.
300
323
let head_commit = if let Some ( vbranch) = virtual_branch {
301
- Some ( vbranch. head_oid ( repo) ?. attach ( repo ) )
324
+ Some ( vbranch. head_oid ( repo) ?)
302
325
} else if let Some ( mut branch) = local_branches. into_iter ( ) . next ( ) {
303
326
branch. peel_to_id_in_place_packed ( packed) . ok ( )
304
327
} else if let Some ( mut branch) = remote_branches. into_iter ( ) . next ( ) {
@@ -329,11 +352,141 @@ fn branch_group_to_branch(
329
352
}
330
353
331
354
/// A sum type of branch that can be a plain git branch or a virtual branch
332
- #[ expect( clippy:: large_enum_variant) ]
333
355
enum GroupBranch < ' a > {
334
356
Local ( gix:: Reference < ' a > ) ,
335
357
Remote ( gix:: Reference < ' a > ) ,
336
- Virtual ( GitButlerBranch ) ,
358
+ Virtual ( GitButlerStack ) ,
359
+ }
360
+
361
+ /// A type to just keep the parts we currently need.
362
+ #[ derive( Debug , Clone ) ]
363
+ struct GitButlerStack {
364
+ id : StackId ,
365
+ /// `true` if the stack is applied to the workspace.
366
+ in_workspace : bool ,
367
+ /// The short name of the top-most segment.
368
+ name : String ,
369
+ /// The full ref name of the top-most segment.
370
+ source_refname : Option < gix:: refs:: FullName > ,
371
+ /// The full ref name of the remote tracking branch of the top-most segment.
372
+ upstream : Option < but_workspace:: ui:: ref_info:: RemoteTrackingReference > ,
373
+ /// The time at which anything in the stack was last updated.
374
+ updated_timestamp_ms : u128 ,
375
+ // All segments of the stack, as long as they are not archived.
376
+ // The tip comes first.
377
+ unarchived_segments : Vec < GitbutlerStackSegment > ,
378
+ }
379
+
380
+ #[ derive( Debug , Clone ) ]
381
+ struct GitbutlerStackSegment {
382
+ /// The name of the segment, without support for these to be anonymous (which is a problem).
383
+ tip : gix:: refs:: FullName ,
384
+ /// The PR or MR associated with it.
385
+ pr_or_mr : Option < usize > ,
386
+ }
387
+
388
+ impl GitbutlerStackSegment {
389
+ fn short_name ( & self ) -> String {
390
+ self . tip . shorten ( ) . to_string ( )
391
+ }
392
+ }
393
+
394
+ impl GitButlerStack {
395
+ fn new (
396
+ stack : but_workspace:: branch:: Stack ,
397
+ names : & gix:: remote:: Names ,
398
+ ) -> anyhow:: Result < Self > {
399
+ let first_segment = stack. segments . first ( ) ;
400
+ Ok ( GitButlerStack {
401
+ id : stack. id . context ( "Can't handle stacks without ID yet" ) ?,
402
+ // The ones we have reachable are never
403
+ in_workspace : true ,
404
+ name : stack
405
+ . name ( )
406
+ . map ( |rn| rn. shorten ( ) . to_string ( ) )
407
+ // Hack it - the datastructure isn't suitable and this needs a `gitbutler->but` port.
408
+ . unwrap_or_default ( ) ,
409
+ source_refname : stack. ref_name ( ) . map ( |rn| rn. to_owned ( ) ) ,
410
+ upstream : first_segment
411
+ . and_then ( |s| {
412
+ s. remote_tracking_ref_name . as_ref ( ) . map ( |rn| {
413
+ but_workspace:: ui:: ref_info:: RemoteTrackingReference :: for_ui (
414
+ rn. clone ( ) ,
415
+ names,
416
+ )
417
+ } )
418
+ } )
419
+ . transpose ( ) ?,
420
+ updated_timestamp_ms : first_segment
421
+ . and_then ( |s| {
422
+ let md = s. metadata . as_ref ( ) ?;
423
+ Some ( md. ref_info . updated_at ?. seconds as u128 * 1_000 )
424
+ } )
425
+ . unwrap_or_default ( ) ,
426
+ unarchived_segments : stack
427
+ . segments
428
+ . iter ( )
429
+ . map ( |s| GitbutlerStackSegment {
430
+ tip : s. ref_name . clone ( ) . unwrap_or_else ( || {
431
+ gix:: refs:: FullName :: try_from (
432
+ "refs/heads/unnamed-ref-and-we-fake-a-name-fix-me" ,
433
+ )
434
+ . expect ( "known to be valid statically" )
435
+ } ) ,
436
+ pr_or_mr : s. metadata . as_ref ( ) . and_then ( |md| md. review . pull_request ) ,
437
+ } )
438
+ . collect ( ) ,
439
+ } )
440
+ }
441
+ fn new_from_old ( stack : & Stack , names : & gix:: remote:: Names ) -> anyhow:: Result < Self > {
442
+ Ok ( GitButlerStack {
443
+ id : stack. id ,
444
+ in_workspace : stack. in_workspace ,
445
+ name : stack. name . clone ( ) ,
446
+ source_refname : stack
447
+ . source_refname
448
+ . as_ref ( )
449
+ . and_then ( |r| r. to_string ( ) . try_into ( ) . ok ( ) ) ,
450
+ upstream : stack
451
+ . upstream
452
+ . as_ref ( )
453
+ . and_then ( |r| {
454
+ r. to_string ( ) . try_into ( ) . ok ( ) . map ( |rn| {
455
+ but_workspace:: ui:: ref_info:: RemoteTrackingReference :: for_ui ( rn, names)
456
+ } )
457
+ } )
458
+ . transpose ( ) ?,
459
+ updated_timestamp_ms : stack. updated_timestamp_ms ,
460
+ unarchived_segments : stack
461
+ . branches ( )
462
+ . iter ( )
463
+ // The tip is at the bottom here.
464
+ . rev ( )
465
+ . filter ( |s| !s. archived )
466
+ . map ( |s| GitbutlerStackSegment {
467
+ tip : s
468
+ . full_name ( )
469
+ . expect ( "full names are always valid, as their short names were valid" ) ,
470
+ pr_or_mr : s. pr_number ,
471
+ } )
472
+ . collect ( ) ,
473
+ } )
474
+ }
475
+ }
476
+
477
+ impl GitButlerStack {
478
+ /// Return the top-most stack's commit id.
479
+ fn head_oid < ' repo > ( & self , repo : & ' repo gix:: Repository ) -> anyhow:: Result < gix:: Id < ' repo > > {
480
+ let tip_ref = self
481
+ . unarchived_segments
482
+ . iter ( )
483
+ . map ( |s| s. tip . as_ref ( ) )
484
+ . next ( )
485
+ . with_context ( || format ! ( "Stack {} didn't have a tip ref name" , self . id) ) ?;
486
+ repo. find_reference ( tip_ref) ?
487
+ . try_id ( )
488
+ . with_context ( || format ! ( "'{}' was as symbolic reference" , tip_ref. shorten( ) ) )
489
+ }
337
490
}
338
491
339
492
impl fmt:: Debug for GroupBranch < ' _ > {
@@ -352,7 +505,7 @@ impl fmt::Debug for GroupBranch<'_> {
352
505
)
353
506
. finish ( ) ,
354
507
GroupBranch :: Virtual ( branch) => formatter
355
- . debug_struct ( "GroupBranch::Virtal " )
508
+ . debug_struct ( "GroupBranch::Virtual " )
356
509
. field ( "0" , branch)
357
510
. finish ( ) ,
358
511
}
@@ -370,12 +523,15 @@ impl GroupBranch<'_> {
370
523
}
371
524
// The identity of a Virtual branch is derived from the source refname, upstream or the branch given name, in that order
372
525
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 ( ) ) ;
526
+ let name_from_source = branch. source_refname . as_ref ( ) . map ( |n| n. shorten ( ) ) ;
527
+ let name_from_upstream = branch
528
+ . upstream
529
+ . as_ref ( )
530
+ . map ( |n| n. display_name . as_str ( ) . into ( ) ) ;
375
531
376
532
// If we have a source refname or upstream, use those directly
377
533
if let Some ( name) = name_from_source. or ( name_from_upstream) {
378
- return Some ( name. into ( ) ) ;
534
+ return name. try_into ( ) . ok ( ) ;
379
535
}
380
536
381
537
// Only fall back to the normalized rich name if no source/upstream is available
@@ -515,7 +671,7 @@ pub struct StackReference {
515
671
/// List of branches that are part of the stack
516
672
/// Ordered from newest to oldest (the most recent branch is first in the list)
517
673
pub branches : Vec < String > ,
518
- /// Pull Request numbes by branch name associated with the stack
674
+ /// Pull Request numbers by branch name associated with the stack
519
675
pub pull_requests : HashMap < String , usize > ,
520
676
}
521
677
0 commit comments