1pub mod merge_commit;
4pub mod merge_ff;
5pub mod merge_rebase;
6pub mod rename;
7
8use std::collections::HashSet;
9
10use git2::{Branch, BranchType, Repository};
11use scopetime::scope_time;
12
13use super::{RepoPath, utils::bytes2string};
14use crate::{
15 error::{Error, Result},
16 sync::{
17 CommitId, remotes::get_default_remote_for_push_in_repo,
18 repository::repo, utils::get_head_repo,
19 },
20};
21
22pub(crate) fn get_branch_name(
25 repo_path: &RepoPath,
26) -> Result<String> {
27 let repo = repo(repo_path)?;
28
29 get_branch_name_repo(&repo)
30}
31
32pub(crate) fn get_branch_name_repo(
34 repo: &Repository,
35) -> Result<String> {
36 scope_time!("get_branch_name_repo");
37
38 let head_ref = repo.head().map_err(|e| {
39 if e.code() == git2::ErrorCode::UnbornBranch {
40 Error::NoHead
41 } else {
42 e.into()
43 }
44 })?;
45
46 bytes2string(head_ref.shorthand_bytes())
47}
48
49#[derive(Clone, Debug)]
51pub struct LocalBranch {
52 pub is_head: bool,
54 pub has_upstream: bool,
56 pub upstream: Option<UpstreamBranch>,
58 pub remote: Option<String>,
60}
61
62#[derive(Clone, Debug)]
64pub struct UpstreamBranch {
65 pub reference: String,
67}
68
69#[derive(Clone, Debug)]
71pub struct RemoteBranch {
72 pub has_tracking: bool,
74}
75
76#[derive(Clone, Debug)]
78pub enum BranchDetails {
79 Local(LocalBranch),
81 Remote(RemoteBranch),
83}
84
85#[derive(Clone, Debug)]
87pub struct BranchInfo {
88 pub name: String,
90 pub reference: String,
92 pub top_commit_message: String,
94 pub top_commit: CommitId,
96 pub details: BranchDetails,
98}
99
100impl BranchInfo {
101 pub const fn local_details(&self) -> Option<&LocalBranch> {
103 if let BranchDetails::Local(details) = &self.details {
104 return Some(details);
105 }
106
107 None
108 }
109}
110
111pub fn validate_branch_name(name: &str) -> Result<bool> {
113 scope_time!("validate_branch_name");
114
115 let valid = Branch::name_is_valid(name)?;
116
117 Ok(valid)
118}
119
120pub fn get_branches_info(
124 repo_path: &RepoPath,
125 local: bool,
126) -> Result<Vec<BranchInfo>> {
127 scope_time!("get_branches_info");
128
129 let repo = repo(repo_path)?;
130
131 let (filter, remotes_with_tracking) = if local {
132 (BranchType::Local, HashSet::default())
133 } else {
134 let remotes: HashSet<_> = repo
135 .branches(Some(BranchType::Local))?
136 .filter_map(|b| {
137 let branch = b.ok()?.0;
138 let upstream = branch.upstream();
139 upstream
140 .ok()?
141 .name_bytes()
142 .ok()
143 .map(ToOwned::to_owned)
144 })
145 .collect();
146 (BranchType::Remote, remotes)
147 };
148
149 let mut branches_for_display: Vec<BranchInfo> = repo
150 .branches(Some(filter))?
151 .map(|b| {
152 let branch = b?.0;
153 let top_commit = branch.get().peel_to_commit()?;
154 let reference = bytes2string(branch.get().name_bytes())?;
155 let upstream = branch.upstream();
156
157 let remote = repo
158 .branch_upstream_remote(&reference)
159 .ok()
160 .as_ref()
161 .and_then(git2::Buf::as_str)
162 .map(String::from);
163
164 let name_bytes = branch.name_bytes()?;
165
166 let upstream_branch =
167 upstream.ok().and_then(|upstream| {
168 bytes2string(upstream.get().name_bytes())
169 .ok()
170 .map(|reference| UpstreamBranch { reference })
171 });
172
173 let details = if local {
174 BranchDetails::Local(LocalBranch {
175 is_head: branch.is_head(),
176 has_upstream: upstream_branch.is_some(),
177 upstream: upstream_branch,
178 remote,
179 })
180 } else {
181 BranchDetails::Remote(RemoteBranch {
182 has_tracking: remotes_with_tracking
183 .contains(name_bytes),
184 })
185 };
186
187 Ok(BranchInfo {
188 name: bytes2string(name_bytes)?,
189 reference,
190 top_commit_message: bytes2string(
191 top_commit.summary_bytes().unwrap_or_default(),
192 )?,
193 top_commit: top_commit.id().into(),
194 details,
195 })
196 })
197 .filter_map(Result::ok)
198 .collect();
199
200 branches_for_display.sort_by(|a, b| a.name.cmp(&b.name));
201
202 Ok(branches_for_display)
203}
204
205#[derive(Debug, Default)]
207pub struct BranchCompare {
208 pub ahead: usize,
210 pub behind: usize,
212}
213
214pub(crate) fn branch_set_upstream_after_push(
216 repo: &Repository,
217 branch_name: &str,
218) -> Result<()> {
219 scope_time!("branch_set_upstream");
220
221 let mut branch =
222 repo.find_branch(branch_name, BranchType::Local)?;
223
224 if branch.upstream().is_err() {
225 let remote = get_default_remote_for_push_in_repo(repo)?;
226 let upstream_name = format!("{remote}/{branch_name}");
227 branch.set_upstream(Some(upstream_name.as_str()))?;
228 }
229
230 Ok(())
231}
232
233pub fn get_branch_remote(
235 repo_path: &RepoPath,
236 branch: &str,
237) -> Result<Option<String>> {
238 let repo = repo(repo_path)?;
239 let branch = repo.find_branch(branch, BranchType::Local)?;
240 let reference = bytes2string(branch.get().name_bytes())?;
241 let remote_name = repo.branch_upstream_remote(&reference).ok();
242 if let Some(remote_name) = remote_name {
243 Ok(Some(bytes2string(remote_name.as_ref())?))
244 } else {
245 Ok(None)
246 }
247}
248
249pub fn config_is_pull_rebase(repo_path: &RepoPath) -> Result<bool> {
251 let repo = repo(repo_path)?;
252 let config = repo.config()?;
253
254 if let Ok(rebase) = config.get_entry("pull.rebase") {
255 let value =
256 rebase.value().map(String::from).unwrap_or_default();
257 return Ok(value == "true");
258 };
259
260 Ok(false)
261}
262
263pub fn branch_compare_upstream(
265 repo_path: &RepoPath,
266 branch: &str,
267) -> Result<BranchCompare> {
268 scope_time!("branch_compare_upstream");
269
270 let repo = repo(repo_path)?;
271
272 let branch = repo.find_branch(branch, BranchType::Local)?;
273
274 let upstream = branch.upstream()?;
275
276 let branch_commit =
277 branch.into_reference().peel_to_commit()?.id();
278
279 let upstream_commit =
280 upstream.into_reference().peel_to_commit()?.id();
281
282 let (ahead, behind) =
283 repo.graph_ahead_behind(branch_commit, upstream_commit)?;
284
285 Ok(BranchCompare { ahead, behind })
286}
287
288pub fn checkout_branch(
295 repo_path: &RepoPath,
296 branch_name: &str,
297) -> Result<()> {
298 scope_time!("checkout_branch");
299
300 let repo = repo(repo_path)?;
301
302 let branch = repo.find_branch(branch_name, BranchType::Local)?;
303
304 let branch_ref = branch.into_reference();
305
306 let target_treeish = branch_ref.peel_to_tree()?;
307 let target_treeish_object = target_treeish.as_object();
308
309 repo.checkout_tree(
311 target_treeish_object,
312 Some(&mut git2::build::CheckoutBuilder::new()),
313 )?;
314
315 let branch_ref = branch_ref.name().ok_or_else(|| {
316 Error::Generic(String::from("branch ref not found"))
317 });
318
319 repo.set_head(branch_ref?)?;
321
322 Ok(())
323}
324
325pub fn checkout_commit(
328 repo_path: &RepoPath,
329 commit_hash: CommitId,
330) -> Result<()> {
331 scope_time!("checkout_commit");
332
333 let repo = repo(repo_path)?;
334 let cur_ref = repo.head()?;
335 let statuses = repo.statuses(Some(
336 git2::StatusOptions::new().include_ignored(false),
337 ))?;
338
339 if statuses.is_empty() {
340 repo.set_head_detached(commit_hash.into())?;
341
342 if let Err(e) = repo.checkout_head(Some(
343 git2::build::CheckoutBuilder::new().force(),
344 )) {
345 repo.set_head(
346 bytes2string(cur_ref.name_bytes())?.as_str(),
347 )?;
348 return Err(Error::Git(e));
349 }
350 Ok(())
351 } else {
352 Err(Error::UncommittedChanges)
353 }
354}
355
356pub fn checkout_remote_branch(
358 repo_path: &RepoPath,
359 branch: &BranchInfo,
360) -> Result<()> {
361 scope_time!("checkout_remote_branch");
362
363 let repo = repo(repo_path)?;
364 let cur_ref = repo.head()?;
365
366 if !repo
367 .statuses(Some(
368 git2::StatusOptions::new().include_ignored(false),
369 ))?
370 .is_empty()
371 {
372 return Err(Error::UncommittedChanges);
373 }
374
375 let name = branch.name.find('/').map_or_else(
376 || branch.name.clone(),
377 |pos| branch.name[pos..].to_string(),
378 );
379
380 let commit = repo.find_commit(branch.top_commit.into())?;
381 let mut new_branch = repo.branch(&name, &commit, false)?;
382 new_branch.set_upstream(Some(&branch.name))?;
383
384 repo.set_head(
385 bytes2string(new_branch.into_reference().name_bytes())?
386 .as_str(),
387 )?;
388
389 if let Err(e) = repo.checkout_head(Some(
390 git2::build::CheckoutBuilder::new().force(),
391 )) {
392 repo.set_head(bytes2string(cur_ref.name_bytes())?.as_str())?;
394 return Err(Error::Git(e));
395 }
396 Ok(())
397}
398
399pub fn delete_branch(
401 repo_path: &RepoPath,
402 branch_ref: &str,
403) -> Result<()> {
404 scope_time!("delete_branch");
405
406 let repo = repo(repo_path)?;
407 let branch_as_ref = repo.find_reference(branch_ref)?;
408 let mut branch = git2::Branch::wrap(branch_as_ref);
409 if branch.is_head() {
410 return Err(Error::Generic("You cannot be on the branch you want to delete, switch branch, then delete this branch".to_string()));
411 }
412 branch.delete()?;
413 Ok(())
414}
415
416pub fn create_branch(
419 repo_path: &RepoPath,
420 name: &str,
421) -> Result<String> {
422 scope_time!("create_branch");
423
424 let repo = repo(repo_path)?;
425
426 let head_id = get_head_repo(&repo)?;
427 let head_commit = repo.find_commit(head_id.into())?;
428
429 let branch = repo.branch(name, &head_commit, false)?;
430 let branch_ref = branch.into_reference();
431 let branch_ref_name = bytes2string(branch_ref.name_bytes())?;
432 repo.set_head(branch_ref_name.as_str())?;
433
434 Ok(branch_ref_name)
435}
436
437#[cfg(test)]
438mod tests_branch_name {
439 use super::*;
440 use crate::sync::tests::{repo_init, repo_init_empty};
441
442 #[test]
443 fn test_smoke() {
444 let (_td, repo) = repo_init().unwrap();
445 let root = repo.path().parent().unwrap();
446 let repo_path: &RepoPath =
447 &root.as_os_str().to_str().unwrap().into();
448
449 assert_eq!(
450 get_branch_name(repo_path).unwrap().as_str(),
451 "master"
452 );
453 }
454
455 #[test]
456 fn test_empty_repo() {
457 let (_td, repo) = repo_init_empty().unwrap();
458 let root = repo.path().parent().unwrap();
459 let repo_path: &RepoPath =
460 &root.as_os_str().to_str().unwrap().into();
461
462 assert!(matches!(
463 get_branch_name(repo_path),
464 Err(Error::NoHead)
465 ));
466 }
467}
468
469#[cfg(test)]
470mod tests_create_branch {
471 use super::*;
472 use crate::sync::tests::repo_init;
473
474 #[test]
475 fn test_smoke() {
476 let (_td, repo) = repo_init().unwrap();
477 let root = repo.path().parent().unwrap();
478 let repo_path: &RepoPath =
479 &root.as_os_str().to_str().unwrap().into();
480
481 create_branch(repo_path, "branch1").unwrap();
482
483 assert_eq!(
484 get_branch_name(repo_path).unwrap().as_str(),
485 "branch1"
486 );
487 }
488}
489
490#[cfg(test)]
491mod tests_branch_compare {
492 use super::*;
493 use crate::sync::tests::repo_init;
494
495 #[test]
496 fn test_smoke() {
497 let (_td, repo) = repo_init().unwrap();
498 let root = repo.path().parent().unwrap();
499 let repo_path: &RepoPath =
500 &root.as_os_str().to_str().unwrap().into();
501
502 create_branch(repo_path, "test").unwrap();
503
504 let res = branch_compare_upstream(repo_path, "test");
505
506 assert_eq!(res.is_err(), true);
507 }
508}
509
510#[cfg(test)]
511mod tests_branches {
512 use super::*;
513 use crate::sync::{
514 remotes::{get_remotes, push::push_branch},
515 rename_branch,
516 tests::{
517 debug_cmd_print, repo_clone, repo_init, repo_init_bare,
518 write_commit_file,
519 },
520 };
521
522 #[test]
523 fn test_smoke() {
524 let (_td, repo) = repo_init().unwrap();
525 let root = repo.path().parent().unwrap();
526 let repo_path: &RepoPath =
527 &root.as_os_str().to_str().unwrap().into();
528
529 assert_eq!(
530 get_branches_info(repo_path, true)
531 .unwrap()
532 .iter()
533 .map(|b| b.name.clone())
534 .collect::<Vec<_>>(),
535 vec!["master"]
536 );
537 }
538
539 #[test]
540 fn test_multiple() {
541 let (_td, repo) = repo_init().unwrap();
542 let root = repo.path().parent().unwrap();
543 let repo_path: &RepoPath =
544 &root.as_os_str().to_str().unwrap().into();
545
546 create_branch(repo_path, "test").unwrap();
547
548 assert_eq!(
549 get_branches_info(repo_path, true)
550 .unwrap()
551 .iter()
552 .map(|b| b.name.clone())
553 .collect::<Vec<_>>(),
554 vec!["master", "test"]
555 );
556 }
557
558 fn clone_branch_commit_push(target: &str, branch_name: &str) {
559 let (dir, repo) = repo_clone(target).unwrap();
560 let dir = dir.path().to_str().unwrap();
561
562 write_commit_file(&repo, "f1.txt", "foo", "c1");
563 rename_branch(&dir.into(), "refs/heads/master", branch_name)
564 .unwrap();
565 push_branch(
566 &dir.into(),
567 "origin",
568 branch_name,
569 false,
570 false,
571 None,
572 None,
573 )
574 .unwrap();
575 }
576
577 #[test]
578 fn test_remotes_of_branches() {
579 let (r1_path, _remote1) = repo_init_bare().unwrap();
580 let (r2_path, _remote2) = repo_init_bare().unwrap();
581 let (_r, repo) = repo_init().unwrap();
582
583 let r1_path = r1_path.path().to_str().unwrap();
584 let r2_path = r2_path.path().to_str().unwrap();
585
586 clone_branch_commit_push(r1_path, "r1branch");
588 clone_branch_commit_push(r2_path, "r2branch");
589
590 let root = repo.path().parent().unwrap();
591 let repo_path: &RepoPath =
592 &root.as_os_str().to_str().unwrap().into();
593
594 repo.remote("r1", r1_path).unwrap();
596 repo.remote("r2", r2_path).unwrap();
597
598 let remotes = get_remotes(repo_path).unwrap();
600 assert_eq!(remotes, vec![
601 String::from("r1"),
602 String::from("r2")
603 ]);
604
605 let branches = get_branches_info(repo_path, true).unwrap();
607 assert_eq!(branches.len(), 1);
608 assert_eq!(branches[0].name, String::from("master"));
609
610 debug_cmd_print(repo_path, "git pull r1");
612 debug_cmd_print(repo_path, "git pull r2");
613
614 debug_cmd_print(
616 repo_path,
617 "git checkout --track r1/r1branch",
618 );
619 debug_cmd_print(
620 repo_path,
621 "git checkout --track r2/r2branch",
622 );
623
624 let branches = get_branches_info(repo_path, true).unwrap();
625 assert_eq!(branches.len(), 3);
626 assert_eq!(
627 branches[1]
628 .local_details()
629 .unwrap()
630 .remote
631 .as_ref()
632 .unwrap(),
633 "r1"
634 );
635 assert_eq!(
636 branches[2]
637 .local_details()
638 .unwrap()
639 .remote
640 .as_ref()
641 .unwrap(),
642 "r2"
643 );
644
645 assert_eq!(
646 get_branch_remote(repo_path, "r1branch")
647 .unwrap()
648 .unwrap(),
649 String::from("r1")
650 );
651
652 assert_eq!(
653 get_branch_remote(repo_path, "r2branch")
654 .unwrap()
655 .unwrap(),
656 String::from("r2")
657 );
658 }
659
660 #[test]
661 fn test_branch_remote_no_upstream() {
662 let (_r, repo) = repo_init().unwrap();
663 let root = repo.path().parent().unwrap();
664 let repo_path: &RepoPath =
665 &root.as_os_str().to_str().unwrap().into();
666
667 assert_eq!(
668 get_branch_remote(repo_path, "master").unwrap(),
669 None
670 );
671 }
672
673 #[test]
674 fn test_branch_remote_no_branch() {
675 let (_r, repo) = repo_init().unwrap();
676 let root = repo.path().parent().unwrap();
677 let repo_path: &RepoPath =
678 &root.as_os_str().to_str().unwrap().into();
679
680 assert!(get_branch_remote(repo_path, "foo").is_err());
681 }
682}
683
684#[cfg(test)]
685mod tests_checkout {
686 use std::{fs::File, path::Path};
687
688 use super::*;
689 use crate::sync::{stage_add_file, tests::repo_init};
690
691 #[test]
692 fn test_smoke() {
693 let (_td, repo) = repo_init().unwrap();
694 let root = repo.path().parent().unwrap();
695 let repo_path: &RepoPath =
696 &root.as_os_str().to_str().unwrap().into();
697
698 assert!(checkout_branch(repo_path, "master").is_ok());
699 assert!(checkout_branch(repo_path, "foobar").is_err());
700 }
701
702 #[test]
703 fn test_multiple() {
704 let (_td, repo) = repo_init().unwrap();
705 let root = repo.path().parent().unwrap();
706 let repo_path: &RepoPath =
707 &root.as_os_str().to_str().unwrap().into();
708
709 create_branch(repo_path, "test").unwrap();
710
711 assert!(checkout_branch(repo_path, "test").is_ok());
712 assert!(checkout_branch(repo_path, "master").is_ok());
713 assert!(checkout_branch(repo_path, "test").is_ok());
714 }
715
716 #[test]
717 fn test_branch_with_slash_in_name() {
718 let (_td, repo) = repo_init().unwrap();
719 let root = repo.path().parent().unwrap();
720 let repo_path: &RepoPath =
721 &root.as_os_str().to_str().unwrap().into();
722
723 create_branch(repo_path, "foo/bar").unwrap();
724 checkout_branch(repo_path, "foo/bar").unwrap();
725 }
726
727 #[test]
728 fn test_staged_new_file() {
729 let (_td, repo) = repo_init().unwrap();
730 let root = repo.path().parent().unwrap();
731 let repo_path: &RepoPath =
732 &root.as_os_str().to_str().unwrap().into();
733
734 create_branch(repo_path, "test").unwrap();
735
736 let filename = "file.txt";
737 let file = root.join(filename);
738 File::create(&file).unwrap();
739
740 stage_add_file(&repo_path, &Path::new(filename)).unwrap();
741
742 assert!(checkout_branch(repo_path, "test").is_ok());
743 }
744}
745
746#[cfg(test)]
747mod tests_checkout_commit {
748 use super::*;
749 use crate::sync::{
750 RepoPath,
751 tests::{repo_init, write_commit_file},
752 };
753
754 #[test]
755 fn test_smoke() {
756 let (_td, repo) = repo_init().unwrap();
757 let root = repo.path().parent().unwrap();
758 let repo_path: &RepoPath =
759 &root.as_os_str().to_str().unwrap().into();
760
761 let commit =
762 write_commit_file(&repo, "test_1.txt", "test", "commit1");
763 write_commit_file(&repo, "test_2.txt", "test", "commit2");
764
765 checkout_commit(repo_path, commit).unwrap();
766
767 assert!(repo.head_detached().unwrap());
768 assert_eq!(
769 repo.head().unwrap().target().unwrap(),
770 commit.get_oid()
771 );
772 }
773}
774
775#[cfg(test)]
776mod test_delete_branch {
777 use super::*;
778 use crate::sync::tests::repo_init;
779
780 #[test]
781 fn test_delete_branch() {
782 let (_td, repo) = repo_init().unwrap();
783 let root = repo.path().parent().unwrap();
784 let repo_path: &RepoPath =
785 &root.as_os_str().to_str().unwrap().into();
786
787 create_branch(repo_path, "branch1").unwrap();
788 create_branch(repo_path, "branch2").unwrap();
789
790 checkout_branch(repo_path, "branch1").unwrap();
791
792 assert_eq!(
793 repo.branches(None)
794 .unwrap()
795 .nth(1)
796 .unwrap()
797 .unwrap()
798 .0
799 .name()
800 .unwrap()
801 .unwrap(),
802 "branch2"
803 );
804
805 delete_branch(repo_path, "refs/heads/branch2").unwrap();
806
807 assert_eq!(
808 repo.branches(None)
809 .unwrap()
810 .nth(1)
811 .unwrap()
812 .unwrap()
813 .0
814 .name()
815 .unwrap()
816 .unwrap(),
817 "master"
818 );
819 }
820}
821
822#[cfg(test)]
823mod test_remote_branches {
824 use super::*;
825 use crate::sync::{
826 remotes::push::push_branch,
827 tests::{repo_clone, repo_init_bare, write_commit_file},
828 };
829
830 impl BranchInfo {
831 const fn remote_details(&self) -> Option<&RemoteBranch> {
833 if let BranchDetails::Remote(details) = &self.details {
834 Some(details)
835 } else {
836 None
837 }
838 }
839 }
840
841 #[test]
842 fn test_remote_branches() {
843 let (r1_dir, _repo) = repo_init_bare().unwrap();
844
845 let (clone1_dir, clone1) =
846 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
847
848 let clone1_dir = clone1_dir.path().to_str().unwrap();
849
850 write_commit_file(&clone1, "test.txt", "test", "commit1");
853
854 push_branch(
855 &clone1_dir.into(),
856 "origin",
857 "master",
858 false,
859 false,
860 None,
861 None,
862 )
863 .unwrap();
864
865 create_branch(&clone1_dir.into(), "foo").unwrap();
866
867 write_commit_file(&clone1, "test.txt", "test2", "commit2");
868
869 push_branch(
870 &clone1_dir.into(),
871 "origin",
872 "foo",
873 false,
874 false,
875 None,
876 None,
877 )
878 .unwrap();
879
880 let (clone2_dir, _clone2) =
883 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
884
885 let clone2_dir = clone2_dir.path().to_str().unwrap();
886
887 let local_branches =
888 get_branches_info(&clone2_dir.into(), true).unwrap();
889
890 assert_eq!(local_branches.len(), 1);
891
892 let branches =
893 get_branches_info(&clone2_dir.into(), false).unwrap();
894 assert_eq!(dbg!(&branches).len(), 3);
895 assert_eq!(&branches[0].name, "origin/HEAD");
896 assert_eq!(&branches[1].name, "origin/foo");
897 assert_eq!(&branches[2].name, "origin/master");
898 }
899
900 #[test]
901 fn test_checkout_remote_branch() {
902 let (r1_dir, _repo) = repo_init_bare().unwrap();
903
904 let (clone1_dir, clone1) =
905 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
906 let clone1_dir = clone1_dir.path().to_str().unwrap();
907
908 write_commit_file(&clone1, "test.txt", "test", "commit1");
911 push_branch(
912 &clone1_dir.into(),
913 "origin",
914 "master",
915 false,
916 false,
917 None,
918 None,
919 )
920 .unwrap();
921 create_branch(&clone1_dir.into(), "foo").unwrap();
922 write_commit_file(&clone1, "test.txt", "test2", "commit2");
923 push_branch(
924 &clone1_dir.into(),
925 "origin",
926 "foo",
927 false,
928 false,
929 None,
930 None,
931 )
932 .unwrap();
933
934 let (clone2_dir, _clone2) =
937 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
938
939 let clone2_dir = clone2_dir.path().to_str().unwrap();
940
941 let local_branches =
942 get_branches_info(&clone2_dir.into(), true).unwrap();
943
944 assert_eq!(local_branches.len(), 1);
945
946 let branches =
947 get_branches_info(&clone2_dir.into(), false).unwrap();
948
949 checkout_remote_branch(&clone2_dir.into(), &branches[1])
951 .unwrap();
952
953 assert_eq!(
954 get_branches_info(&clone2_dir.into(), true)
955 .unwrap()
956 .len(),
957 2
958 );
959
960 assert_eq!(
961 &get_branch_name(&clone2_dir.into()).unwrap(),
962 "foo"
963 );
964 }
965
966 #[test]
967 fn test_checkout_remote_branch_hirachical() {
968 let (r1_dir, _repo) = repo_init_bare().unwrap();
969
970 let (clone1_dir, clone1) =
971 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
972 let clone1_dir = clone1_dir.path().to_str().unwrap();
973
974 let branch_name = "bar/foo";
977
978 write_commit_file(&clone1, "test.txt", "test", "commit1");
979 push_branch(
980 &clone1_dir.into(),
981 "origin",
982 "master",
983 false,
984 false,
985 None,
986 None,
987 )
988 .unwrap();
989 create_branch(&clone1_dir.into(), branch_name).unwrap();
990 write_commit_file(&clone1, "test.txt", "test2", "commit2");
991 push_branch(
992 &clone1_dir.into(),
993 "origin",
994 branch_name,
995 false,
996 false,
997 None,
998 None,
999 )
1000 .unwrap();
1001
1002 let (clone2_dir, _clone2) =
1005 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
1006 let clone2_dir = clone2_dir.path().to_str().unwrap();
1007
1008 let branches =
1009 get_branches_info(&clone2_dir.into(), false).unwrap();
1010
1011 checkout_remote_branch(&clone2_dir.into(), &branches[1])
1012 .unwrap();
1013
1014 assert_eq!(
1015 &get_branch_name(&clone2_dir.into()).unwrap(),
1016 branch_name
1017 );
1018 }
1019
1020 #[test]
1021 fn test_has_tracking() {
1022 let (r1_dir, _repo) = repo_init_bare().unwrap();
1023
1024 let (clone1_dir, clone1) =
1025 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
1026 let clone1_dir = clone1_dir.path().to_str().unwrap();
1027
1028 write_commit_file(&clone1, "test.txt", "test", "commit1");
1031 push_branch(
1032 &clone1_dir.into(),
1033 "origin",
1034 "master",
1035 false,
1036 false,
1037 None,
1038 None,
1039 )
1040 .unwrap();
1041 create_branch(&clone1_dir.into(), "foo").unwrap();
1042 write_commit_file(&clone1, "test.txt", "test2", "commit2");
1043 push_branch(
1044 &clone1_dir.into(),
1045 "origin",
1046 "foo",
1047 false,
1048 false,
1049 None,
1050 None,
1051 )
1052 .unwrap();
1053
1054 let branches_1 =
1055 get_branches_info(&clone1_dir.into(), false).unwrap();
1056
1057 assert!(branches_1[0].remote_details().unwrap().has_tracking);
1058 assert!(branches_1[1].remote_details().unwrap().has_tracking);
1059
1060 let (clone2_dir, _clone2) =
1063 repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
1064
1065 let clone2_dir = clone2_dir.path().to_str().unwrap();
1066
1067 let branches_2 =
1068 get_branches_info(&clone2_dir.into(), false).unwrap();
1069
1070 assert!(
1071 !branches_2[0].remote_details().unwrap().has_tracking
1072 );
1073 assert!(
1074 !branches_2[1].remote_details().unwrap().has_tracking
1075 );
1076 assert!(branches_2[2].remote_details().unwrap().has_tracking);
1077 }
1078}