@@ -45,6 +45,27 @@ pub(crate) fn get_branch_name_repo(
45
45
}
46
46
47
47
///
48
+ #[ derive( Debug ) ]
49
+ pub struct LocalBranch {
50
+ ///
51
+ pub is_head : bool ,
52
+ ///
53
+ pub has_upstream : bool ,
54
+ ///
55
+ pub remote : Option < String > ,
56
+ }
57
+
58
+ ///
59
+ #[ derive( Debug ) ]
60
+ pub enum BranchDetails {
61
+ ///
62
+ Local ( LocalBranch ) ,
63
+ ///
64
+ Remote ,
65
+ }
66
+
67
+ ///
68
+ #[ derive( Debug ) ]
48
69
pub struct BranchInfo {
49
70
///
50
71
pub name : String ,
@@ -55,20 +76,37 @@ pub struct BranchInfo {
55
76
///
56
77
pub top_commit : CommitId ,
57
78
///
58
- pub is_head : bool ,
59
- ///
60
- pub has_upstream : bool ,
61
- ///
62
- pub remote : Option < String > ,
79
+ pub details : BranchDetails ,
80
+ }
81
+
82
+ impl BranchInfo {
83
+ /// returns details about local branch or None
84
+ pub fn local_details ( & self ) -> Option < & LocalBranch > {
85
+ if let BranchDetails :: Local ( details) = & self . details {
86
+ return Some ( details) ;
87
+ }
88
+
89
+ None
90
+ }
63
91
}
64
92
65
- /// returns a list of `BranchInfo` with a simple summary of info about a single branch
66
- pub fn get_branches_info ( repo_path : & str ) -> Result < Vec < BranchInfo > > {
93
+ /// returns a list of `BranchInfo` with a simple summary on each branch
94
+ /// `local` filters for local branches otherwise remote branches will be returned
95
+ pub fn get_branches_info (
96
+ repo_path : & str ,
97
+ local : bool ,
98
+ ) -> Result < Vec < BranchInfo > > {
67
99
scope_time ! ( "get_branches_info" ) ;
68
100
101
+ let filter = if local {
102
+ BranchType :: Local
103
+ } else {
104
+ BranchType :: Remote
105
+ } ;
106
+
69
107
let repo = utils:: repo ( repo_path) ?;
70
- let branches_for_display = repo
71
- . branches ( Some ( BranchType :: Local ) ) ?
108
+ let mut branches_for_display: Vec < BranchInfo > = repo
109
+ . branches ( Some ( filter ) ) ?
72
110
. map ( |b| {
73
111
let branch = b?. 0 ;
74
112
let top_commit = branch. get ( ) . peel_to_commit ( ) ?;
@@ -82,21 +120,31 @@ pub fn get_branches_info(repo_path: &str) -> Result<Vec<BranchInfo>> {
82
120
. and_then ( |buf| buf. as_str ( ) )
83
121
. map ( String :: from) ;
84
122
123
+ let details = if local {
124
+ BranchDetails :: Local ( LocalBranch {
125
+ is_head : branch. is_head ( ) ,
126
+ has_upstream : upstream. is_ok ( ) ,
127
+ remote,
128
+ } )
129
+ } else {
130
+ BranchDetails :: Remote
131
+ } ;
132
+
85
133
Ok ( BranchInfo {
86
134
name : bytes2string ( branch. name_bytes ( ) ?) ?,
87
135
reference,
88
136
top_commit_message : bytes2string (
89
137
top_commit. summary_bytes ( ) . unwrap_or_default ( ) ,
90
138
) ?,
91
139
top_commit : top_commit. id ( ) . into ( ) ,
92
- is_head : branch. is_head ( ) ,
93
- has_upstream : upstream. is_ok ( ) ,
94
- remote,
140
+ details,
95
141
} )
96
142
} )
97
143
. filter_map ( Result :: ok)
98
144
. collect ( ) ;
99
145
146
+ branches_for_display. sort_by ( |a, b| a. name . cmp ( & b. name ) ) ;
147
+
100
148
Ok ( branches_for_display)
101
149
}
102
150
@@ -212,10 +260,52 @@ pub fn checkout_branch(
212
260
}
213
261
Ok ( ( ) )
214
262
} else {
215
- Err ( Error :: Generic (
216
- format ! ( "Cannot change branch. There are unstaged/staged changes which have not been committed/stashed. There is {:?} changes preventing checking out a different branch." , statuses. len( ) ) ,
217
- ) )
263
+ Err ( Error :: UncommittedChanges )
264
+ }
265
+ }
266
+
267
+ ///
268
+ pub fn checkout_remote_branch (
269
+ repo_path : & str ,
270
+ branch : & BranchInfo ,
271
+ ) -> Result < ( ) > {
272
+ scope_time ! ( "checkout_remote_branch" ) ;
273
+
274
+ let repo = utils:: repo ( repo_path) ?;
275
+ let cur_ref = repo. head ( ) ?;
276
+
277
+ if !repo
278
+ . statuses ( Some (
279
+ git2:: StatusOptions :: new ( ) . include_ignored ( false ) ,
280
+ ) ) ?
281
+ . is_empty ( )
282
+ {
283
+ return Err ( Error :: UncommittedChanges ) ;
284
+ }
285
+
286
+ let name = if let Some ( pos) = branch. name . rfind ( '/' ) {
287
+ branch. name [ pos..] . to_string ( )
288
+ } else {
289
+ branch. name . clone ( )
290
+ } ;
291
+
292
+ let commit = repo. find_commit ( branch. top_commit . into ( ) ) ?;
293
+ let mut new_branch = repo. branch ( & name, & commit, false ) ?;
294
+ new_branch. set_upstream ( Some ( & branch. name ) ) ?;
295
+
296
+ repo. set_head (
297
+ bytes2string ( new_branch. into_reference ( ) . name_bytes ( ) ) ?
298
+ . as_str ( ) ,
299
+ ) ?;
300
+
301
+ if let Err ( e) = repo. checkout_head ( Some (
302
+ git2:: build:: CheckoutBuilder :: new ( ) . force ( ) ,
303
+ ) ) {
304
+ // This is safe beacuse cur_ref was just found
305
+ repo. set_head ( bytes2string ( cur_ref. name_bytes ( ) ) ?. as_str ( ) ) ?;
306
+ return Err ( Error :: Git ( e) ) ;
218
307
}
308
+ Ok ( ( ) )
219
309
}
220
310
221
311
/// The user must not be on the branch for the branch to be deleted
@@ -341,7 +431,7 @@ mod tests_branches {
341
431
let repo_path = root. as_os_str ( ) . to_str ( ) . unwrap ( ) ;
342
432
343
433
assert_eq ! (
344
- get_branches_info( repo_path)
434
+ get_branches_info( repo_path, true )
345
435
. unwrap( )
346
436
. iter( )
347
437
. map( |b| b. name. clone( ) )
@@ -359,7 +449,7 @@ mod tests_branches {
359
449
create_branch ( repo_path, "test" ) . unwrap ( ) ;
360
450
361
451
assert_eq ! (
362
- get_branches_info( repo_path)
452
+ get_branches_info( repo_path, true )
363
453
. unwrap( )
364
454
. iter( )
365
455
. map( |b| b. name. clone( ) )
@@ -405,7 +495,7 @@ mod tests_branches {
405
495
) ;
406
496
407
497
//verify we got only master right now
408
- let branches = get_branches_info ( repo_path) . unwrap ( ) ;
498
+ let branches = get_branches_info ( repo_path, true ) . unwrap ( ) ;
409
499
assert_eq ! ( branches. len( ) , 1 ) ;
410
500
assert_eq ! ( branches[ 0 ] . name, String :: from( "master" ) ) ;
411
501
@@ -423,10 +513,26 @@ mod tests_branches {
423
513
"git checkout --track r2/r2branch" ,
424
514
) ;
425
515
426
- let branches = get_branches_info ( repo_path) . unwrap ( ) ;
516
+ let branches = get_branches_info ( repo_path, true ) . unwrap ( ) ;
427
517
assert_eq ! ( branches. len( ) , 3 ) ;
428
- assert_eq ! ( branches[ 1 ] . remote. as_ref( ) . unwrap( ) , "r1" ) ;
429
- assert_eq ! ( branches[ 2 ] . remote. as_ref( ) . unwrap( ) , "r2" ) ;
518
+ assert_eq ! (
519
+ branches[ 1 ]
520
+ . local_details( )
521
+ . unwrap( )
522
+ . remote
523
+ . as_ref( )
524
+ . unwrap( ) ,
525
+ "r1"
526
+ ) ;
527
+ assert_eq ! (
528
+ branches[ 2 ]
529
+ . local_details( )
530
+ . unwrap( )
531
+ . remote
532
+ . as_ref( )
533
+ . unwrap( ) ,
534
+ "r2"
535
+ ) ;
430
536
431
537
assert_eq ! (
432
538
get_branch_remote( repo_path, "r1branch" )
@@ -545,3 +651,95 @@ mod test_delete_branch {
545
651
) ;
546
652
}
547
653
}
654
+
655
+ #[ cfg( test) ]
656
+ mod test_remote_branches {
657
+ use super :: * ;
658
+ use crate :: sync:: remotes:: push:: push;
659
+ use crate :: sync:: tests:: {
660
+ repo_clone, repo_init_bare, write_commit_file,
661
+ } ;
662
+
663
+ #[ test]
664
+ fn test_remote_branches ( ) {
665
+ let ( r1_dir, _repo) = repo_init_bare ( ) . unwrap ( ) ;
666
+
667
+ let ( clone1_dir, clone1) =
668
+ repo_clone ( r1_dir. path ( ) . to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
669
+
670
+ let clone1_dir = clone1_dir. path ( ) . to_str ( ) . unwrap ( ) ;
671
+
672
+ // clone1
673
+
674
+ write_commit_file ( & clone1, "test.txt" , "test" , "commit1" ) ;
675
+
676
+ push ( clone1_dir, "origin" , "master" , false , None , None )
677
+ . unwrap ( ) ;
678
+
679
+ create_branch ( clone1_dir, "foo" ) . unwrap ( ) ;
680
+
681
+ write_commit_file ( & clone1, "test.txt" , "test2" , "commit2" ) ;
682
+
683
+ push ( clone1_dir, "origin" , "foo" , false , None , None ) . unwrap ( ) ;
684
+
685
+ // clone2
686
+
687
+ let ( clone2_dir, _clone2) =
688
+ repo_clone ( r1_dir. path ( ) . to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
689
+
690
+ let clone2_dir = clone2_dir. path ( ) . to_str ( ) . unwrap ( ) ;
691
+
692
+ let local_branches =
693
+ get_branches_info ( clone2_dir, true ) . unwrap ( ) ;
694
+
695
+ assert_eq ! ( local_branches. len( ) , 1 ) ;
696
+
697
+ let branches = get_branches_info ( clone2_dir, false ) . unwrap ( ) ;
698
+ assert_eq ! ( dbg!( & branches) . len( ) , 3 ) ;
699
+ assert_eq ! ( & branches[ 0 ] . name, "origin/HEAD" ) ;
700
+ assert_eq ! ( & branches[ 1 ] . name, "origin/foo" ) ;
701
+ assert_eq ! ( & branches[ 2 ] . name, "origin/master" ) ;
702
+ }
703
+
704
+ #[ test]
705
+ fn test_checkout_remote_branch ( ) {
706
+ let ( r1_dir, _repo) = repo_init_bare ( ) . unwrap ( ) ;
707
+
708
+ let ( clone1_dir, clone1) =
709
+ repo_clone ( r1_dir. path ( ) . to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
710
+ let clone1_dir = clone1_dir. path ( ) . to_str ( ) . unwrap ( ) ;
711
+
712
+ // clone1
713
+
714
+ write_commit_file ( & clone1, "test.txt" , "test" , "commit1" ) ;
715
+ push ( clone1_dir, "origin" , "master" , false , None , None )
716
+ . unwrap ( ) ;
717
+ create_branch ( clone1_dir, "foo" ) . unwrap ( ) ;
718
+ write_commit_file ( & clone1, "test.txt" , "test2" , "commit2" ) ;
719
+ push ( clone1_dir, "origin" , "foo" , false , None , None ) . unwrap ( ) ;
720
+
721
+ // clone2
722
+
723
+ let ( clone2_dir, _clone2) =
724
+ repo_clone ( r1_dir. path ( ) . to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
725
+
726
+ let clone2_dir = clone2_dir. path ( ) . to_str ( ) . unwrap ( ) ;
727
+
728
+ let local_branches =
729
+ get_branches_info ( clone2_dir, true ) . unwrap ( ) ;
730
+
731
+ assert_eq ! ( local_branches. len( ) , 1 ) ;
732
+
733
+ let branches = get_branches_info ( clone2_dir, false ) . unwrap ( ) ;
734
+
735
+ // checkout origin/foo
736
+ checkout_remote_branch ( clone2_dir, & branches[ 1 ] ) . unwrap ( ) ;
737
+
738
+ assert_eq ! (
739
+ get_branches_info( clone2_dir, true ) . unwrap( ) . len( ) ,
740
+ 2
741
+ ) ;
742
+
743
+ assert_eq ! ( & get_branch_name( clone2_dir) . unwrap( ) , "foo" ) ;
744
+ }
745
+ }
0 commit comments