@@ -45,6 +45,27 @@ pub(crate) fn get_branch_name_repo(
4545}
4646
4747///
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 ) ]
4869pub struct BranchInfo {
4970 ///
5071 pub name : String ,
@@ -55,20 +76,37 @@ pub struct BranchInfo {
5576 ///
5677 pub top_commit : CommitId ,
5778 ///
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+ }
6391}
6492
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 > > {
6799 scope_time ! ( "get_branches_info" ) ;
68100
101+ let filter = if local {
102+ BranchType :: Local
103+ } else {
104+ BranchType :: Remote
105+ } ;
106+
69107 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 ) ) ?
72110 . map ( |b| {
73111 let branch = b?. 0 ;
74112 let top_commit = branch. get ( ) . peel_to_commit ( ) ?;
@@ -82,21 +120,31 @@ pub fn get_branches_info(repo_path: &str) -> Result<Vec<BranchInfo>> {
82120 . and_then ( |buf| buf. as_str ( ) )
83121 . map ( String :: from) ;
84122
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+
85133 Ok ( BranchInfo {
86134 name : bytes2string ( branch. name_bytes ( ) ?) ?,
87135 reference,
88136 top_commit_message : bytes2string (
89137 top_commit. summary_bytes ( ) . unwrap_or_default ( ) ,
90138 ) ?,
91139 top_commit : top_commit. id ( ) . into ( ) ,
92- is_head : branch. is_head ( ) ,
93- has_upstream : upstream. is_ok ( ) ,
94- remote,
140+ details,
95141 } )
96142 } )
97143 . filter_map ( Result :: ok)
98144 . collect ( ) ;
99145
146+ branches_for_display. sort_by ( |a, b| a. name . cmp ( & b. name ) ) ;
147+
100148 Ok ( branches_for_display)
101149}
102150
@@ -212,10 +260,52 @@ pub fn checkout_branch(
212260 }
213261 Ok ( ( ) )
214262 } 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) ) ;
218307 }
308+ Ok ( ( ) )
219309}
220310
221311/// The user must not be on the branch for the branch to be deleted
@@ -341,7 +431,7 @@ mod tests_branches {
341431 let repo_path = root. as_os_str ( ) . to_str ( ) . unwrap ( ) ;
342432
343433 assert_eq ! (
344- get_branches_info( repo_path)
434+ get_branches_info( repo_path, true )
345435 . unwrap( )
346436 . iter( )
347437 . map( |b| b. name. clone( ) )
@@ -359,7 +449,7 @@ mod tests_branches {
359449 create_branch ( repo_path, "test" ) . unwrap ( ) ;
360450
361451 assert_eq ! (
362- get_branches_info( repo_path)
452+ get_branches_info( repo_path, true )
363453 . unwrap( )
364454 . iter( )
365455 . map( |b| b. name. clone( ) )
@@ -405,7 +495,7 @@ mod tests_branches {
405495 ) ;
406496
407497 //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 ( ) ;
409499 assert_eq ! ( branches. len( ) , 1 ) ;
410500 assert_eq ! ( branches[ 0 ] . name, String :: from( "master" ) ) ;
411501
@@ -423,10 +513,26 @@ mod tests_branches {
423513 "git checkout --track r2/r2branch" ,
424514 ) ;
425515
426- let branches = get_branches_info ( repo_path) . unwrap ( ) ;
516+ let branches = get_branches_info ( repo_path, true ) . unwrap ( ) ;
427517 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+ ) ;
430536
431537 assert_eq ! (
432538 get_branch_remote( repo_path, "r1branch" )
@@ -545,3 +651,95 @@ mod test_delete_branch {
545651 ) ;
546652 }
547653}
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