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