Skip to content

Commit 13a0f4e

Browse files
authored
Display mark for remote branches with tracking branches (#861)
1 parent 1faba17 commit 13a0f4e

File tree

2 files changed

+108
-18
lines changed

2 files changed

+108
-18
lines changed

asyncgit/src/sync/branch/mod.rs

+90-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pub mod merge_ff;
55
pub mod merge_rebase;
66
pub mod rename;
77

8+
use std::collections::HashSet;
9+
810
use super::{
911
remotes::get_default_remote_in_repo, utils::bytes2string,
1012
};
@@ -55,13 +57,20 @@ pub struct LocalBranch {
5557
pub remote: Option<String>,
5658
}
5759

60+
///
61+
#[derive(Debug)]
62+
pub struct RemoteBranch {
63+
///
64+
pub has_tracking: bool,
65+
}
66+
5867
///
5968
#[derive(Debug)]
6069
pub enum BranchDetails {
6170
///
6271
Local(LocalBranch),
6372
///
64-
Remote,
73+
Remote(RemoteBranch),
6574
}
6675

6776
///
@@ -107,13 +116,26 @@ pub fn get_branches_info(
107116
) -> Result<Vec<BranchInfo>> {
108117
scope_time!("get_branches_info");
109118

110-
let filter = if local {
111-
BranchType::Local
119+
let repo = utils::repo(repo_path)?;
120+
121+
let (filter, remotes_with_tracking) = if local {
122+
(BranchType::Local, HashSet::default())
112123
} else {
113-
BranchType::Remote
124+
let remotes: HashSet<_> = repo
125+
.branches(Some(BranchType::Local))?
126+
.filter_map(|b| {
127+
let branch = b.ok()?.0;
128+
let upstream = branch.upstream();
129+
upstream
130+
.ok()?
131+
.name_bytes()
132+
.ok()
133+
.map(ToOwned::to_owned)
134+
})
135+
.collect();
136+
(BranchType::Remote, remotes)
114137
};
115138

116-
let repo = utils::repo(repo_path)?;
117139
let mut branches_for_display: Vec<BranchInfo> = repo
118140
.branches(Some(filter))?
119141
.map(|b| {
@@ -129,18 +151,23 @@ pub fn get_branches_info(
129151
.and_then(git2::Buf::as_str)
130152
.map(String::from);
131153

154+
let name_bytes = branch.name_bytes()?;
155+
132156
let details = if local {
133157
BranchDetails::Local(LocalBranch {
134158
is_head: branch.is_head(),
135159
has_upstream: upstream.is_ok(),
136160
remote,
137161
})
138162
} else {
139-
BranchDetails::Remote
163+
BranchDetails::Remote(RemoteBranch {
164+
has_tracking: remotes_with_tracking
165+
.contains(name_bytes),
166+
})
140167
};
141168

142169
Ok(BranchInfo {
143-
name: bytes2string(branch.name_bytes()?)?,
170+
name: bytes2string(name_bytes)?,
144171
reference,
145172
top_commit_message: bytes2string(
146173
top_commit.summary_bytes().unwrap_or_default(),
@@ -668,6 +695,17 @@ mod test_remote_branches {
668695
repo_clone, repo_init_bare, write_commit_file,
669696
};
670697

698+
impl BranchInfo {
699+
/// returns details about remote branch or None
700+
const fn remote_details(&self) -> Option<&RemoteBranch> {
701+
if let BranchDetails::Remote(details) = &self.details {
702+
Some(details)
703+
} else {
704+
None
705+
}
706+
}
707+
}
708+
671709
#[test]
672710
fn test_remote_branches() {
673711
let (r1_dir, _repo) = repo_init_bare().unwrap();
@@ -756,4 +794,49 @@ mod test_remote_branches {
756794

757795
assert_eq!(&get_branch_name(clone2_dir).unwrap(), "foo");
758796
}
797+
798+
#[test]
799+
fn test_has_tracking() {
800+
let (r1_dir, _repo) = repo_init_bare().unwrap();
801+
802+
let (clone1_dir, clone1) =
803+
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
804+
let clone1_dir = clone1_dir.path().to_str().unwrap();
805+
806+
// clone1
807+
808+
write_commit_file(&clone1, "test.txt", "test", "commit1");
809+
push(
810+
clone1_dir, "origin", "master", false, false, None, None,
811+
)
812+
.unwrap();
813+
create_branch(clone1_dir, "foo").unwrap();
814+
write_commit_file(&clone1, "test.txt", "test2", "commit2");
815+
push(clone1_dir, "origin", "foo", false, false, None, None)
816+
.unwrap();
817+
818+
let branches_1 =
819+
get_branches_info(clone1_dir, false).unwrap();
820+
821+
assert!(branches_1[0].remote_details().unwrap().has_tracking);
822+
assert!(branches_1[1].remote_details().unwrap().has_tracking);
823+
824+
// clone2
825+
826+
let (clone2_dir, _clone2) =
827+
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
828+
829+
let clone2_dir = clone2_dir.path().to_str().unwrap();
830+
831+
let branches_2 =
832+
get_branches_info(clone2_dir, false).unwrap();
833+
834+
assert!(
835+
!branches_2[0].remote_details().unwrap().has_tracking
836+
);
837+
assert!(
838+
!branches_2[1].remote_details().unwrap().has_tracking
839+
);
840+
assert!(branches_2[2].remote_details().unwrap().has_tracking);
841+
}
759842
}

src/components/branchlist.rs

+18-11
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ use crate::{
1313
use anyhow::Result;
1414
use asyncgit::{
1515
sync::{
16-
self, branch::checkout_remote_branch, checkout_branch,
17-
get_branches_info, BranchInfo, CommitId,
16+
self,
17+
branch::{
18+
checkout_remote_branch, BranchDetails, LocalBranch,
19+
RemoteBranch,
20+
},
21+
checkout_branch, get_branches_info, BranchInfo, CommitId,
1822
},
1923
AsyncGitNotification, CWD,
2024
};
@@ -415,6 +419,7 @@ impl BranchListComponent {
415419
height: usize,
416420
) -> Text {
417421
const UPSTREAM_SYMBOL: char = '\u{2191}';
422+
const TRACKING_SYMBOL: char = '\u{2193}';
418423
const HEAD_SYMBOL: char = '*';
419424
const EMPTY_SYMBOL: char = ' ';
420425
const THREE_DOTS: &str = "...";
@@ -473,18 +478,20 @@ impl BranchListComponent {
473478
.unwrap_or_default();
474479
let is_head_str =
475480
if is_head { HEAD_SYMBOL } else { EMPTY_SYMBOL };
476-
let has_upstream_str = if displaybranch
477-
.local_details()
478-
.map(|details| details.has_upstream)
479-
.unwrap_or_default()
480-
{
481-
UPSTREAM_SYMBOL
482-
} else {
483-
EMPTY_SYMBOL
481+
let upstream_tracking_str = match displaybranch.details {
482+
BranchDetails::Local(LocalBranch {
483+
has_upstream,
484+
..
485+
}) if has_upstream => UPSTREAM_SYMBOL,
486+
BranchDetails::Remote(RemoteBranch {
487+
has_tracking,
488+
..
489+
}) if has_tracking => TRACKING_SYMBOL,
490+
_ => EMPTY_SYMBOL,
484491
};
485492

486493
let span_prefix = Span::styled(
487-
format!("{}{} ", is_head_str, has_upstream_str),
494+
format!("{}{} ", is_head_str, upstream_tracking_str),
488495
theme.commit_author(selected),
489496
);
490497
let span_hash = Span::styled(

0 commit comments

Comments
 (0)