Improve handling of remote-tracking branches in the picker (#29744)

Release Notes:

- Changed the git branch picker to make remote-tracking branches less
prominent

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
Cole Miller 2025-05-01 21:24:26 -04:00 committed by GitHub
parent 92b9ecd7d2
commit e1e3f2e423
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 150 additions and 124 deletions

View file

@ -2110,7 +2110,7 @@ impl Thread {
.map(|repo| { .map(|repo| {
repo.update(cx, |repo, _| { repo.update(cx, |repo, _| {
let current_branch = let current_branch =
repo.branch.as_ref().map(|branch| branch.name.to_string()); repo.branch.as_ref().map(|branch| branch.name().to_owned());
repo.send_job(None, |state, _| async move { repo.send_job(None, |state, _| async move {
let RepositoryState::Local { backend, .. } = state else { let RepositoryState::Local { backend, .. } = state else {
return GitState { return GitState {

View file

@ -2902,7 +2902,7 @@ async fn test_git_branch_name(
.read(cx) .read(cx)
.branch .branch
.as_ref() .as_ref()
.map(|branch| branch.name.to_string()), .map(|branch| branch.name().to_owned()),
branch_name branch_name
) )
} }
@ -6864,7 +6864,7 @@ async fn test_remote_git_branches(
let branches_b = branches_b let branches_b = branches_b
.into_iter() .into_iter()
.map(|branch| branch.name.to_string()) .map(|branch| branch.name().to_string())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!(branches_b, branches_set); assert_eq!(branches_b, branches_set);
@ -6895,7 +6895,7 @@ async fn test_remote_git_branches(
}) })
}); });
assert_eq!(host_branch.name, branches[2]); assert_eq!(host_branch.name(), branches[2]);
// Also try creating a new branch // Also try creating a new branch
cx_b.update(|cx| { cx_b.update(|cx| {
@ -6933,5 +6933,5 @@ async fn test_remote_git_branches(
}) })
}); });
assert_eq!(host_branch.name, "totally-new-branch"); assert_eq!(host_branch.name(), "totally-new-branch");
} }

View file

@ -293,7 +293,7 @@ async fn test_ssh_collaboration_git_branches(
let branches_b = branches_b let branches_b = branches_b
.into_iter() .into_iter()
.map(|branch| branch.name.to_string()) .map(|branch| branch.name().to_string())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!(&branches_b, &branches_set); assert_eq!(&branches_b, &branches_set);
@ -326,7 +326,7 @@ async fn test_ssh_collaboration_git_branches(
}) })
}); });
assert_eq!(server_branch.name, branches[2]); assert_eq!(server_branch.name(), branches[2]);
// Also try creating a new branch // Also try creating a new branch
cx_b.update(|cx| { cx_b.update(|cx| {
@ -366,7 +366,7 @@ async fn test_ssh_collaboration_git_branches(
}) })
}); });
assert_eq!(server_branch.name, "totally-new-branch"); assert_eq!(server_branch.name(), "totally-new-branch");
// Remove the git repository and check that all participants get the update. // Remove the git repository and check that all participants get the update.
remote_fs remote_fs

View file

@ -322,7 +322,7 @@ impl GitRepository for FakeGitRepository {
.iter() .iter()
.map(|branch_name| Branch { .map(|branch_name| Branch {
is_head: Some(branch_name) == current_branch.as_ref(), is_head: Some(branch_name) == current_branch.as_ref(),
name: branch_name.into(), ref_name: branch_name.into(),
most_recent_commit: None, most_recent_commit: None,
upstream: None, upstream: None,
}) })

View file

@ -37,12 +37,24 @@ pub const REMOTE_CANCELLED_BY_USER: &str = "Operation cancelled by user";
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Branch { pub struct Branch {
pub is_head: bool, pub is_head: bool,
pub name: SharedString, pub ref_name: SharedString,
pub upstream: Option<Upstream>, pub upstream: Option<Upstream>,
pub most_recent_commit: Option<CommitSummary>, pub most_recent_commit: Option<CommitSummary>,
} }
impl Branch { impl Branch {
pub fn name(&self) -> &str {
self.ref_name
.as_ref()
.strip_prefix("refs/heads/")
.or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/"))
.unwrap_or(self.ref_name.as_ref())
}
pub fn is_remote(&self) -> bool {
self.ref_name.starts_with("refs/remotes/")
}
pub fn tracking_status(&self) -> Option<UpstreamTrackingStatus> { pub fn tracking_status(&self) -> Option<UpstreamTrackingStatus> {
self.upstream self.upstream
.as_ref() .as_ref()
@ -71,6 +83,10 @@ impl Upstream {
.strip_prefix("refs/remotes/") .strip_prefix("refs/remotes/")
.and_then(|stripped| stripped.split("/").next()) .and_then(|stripped| stripped.split("/").next())
} }
pub fn stripped_ref_name(&self) -> Option<&str> {
self.ref_name.strip_prefix("refs/remotes/")
}
} }
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
@ -803,68 +819,69 @@ impl GitRepository for RealGitRepository {
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> { fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> {
let working_directory = self.working_directory(); let working_directory = self.working_directory();
let git_binary_path = self.git_binary_path.clone(); let git_binary_path = self.git_binary_path.clone();
async move { self.executor
let fields = [ .spawn(async move {
"%(HEAD)", let fields = [
"%(objectname)", "%(HEAD)",
"%(parent)", "%(objectname)",
"%(refname)", "%(parent)",
"%(upstream)", "%(refname)",
"%(upstream:track)", "%(upstream)",
"%(committerdate:unix)", "%(upstream:track)",
"%(contents:subject)", "%(committerdate:unix)",
] "%(contents:subject)",
.join("%00"); ]
let args = vec![ .join("%00");
"for-each-ref", let args = vec![
"refs/heads/**/*", "for-each-ref",
"refs/remotes/**/*", "refs/heads/**/*",
"--format", "refs/remotes/**/*",
&fields, "--format",
]; &fields,
let working_directory = working_directory?; ];
let output = new_smol_command(&git_binary_path) let working_directory = working_directory?;
.current_dir(&working_directory)
.args(args)
.output()
.await?;
if !output.status.success() {
return Err(anyhow!(
"Failed to git git branches:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
let input = String::from_utf8_lossy(&output.stdout);
let mut branches = parse_branch_input(&input)?;
if branches.is_empty() {
let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
let output = new_smol_command(&git_binary_path) let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(args) .args(args)
.output() .output()
.await?; .await?;
// git symbolic-ref returns a non-0 exit code if HEAD points if !output.status.success() {
// to something other than a branch return Err(anyhow!(
if output.status.success() { "Failed to git git branches:\n{}",
let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); String::from_utf8_lossy(&output.stderr)
));
branches.push(Branch {
name: name.into(),
is_head: true,
upstream: None,
most_recent_commit: None,
});
} }
}
Ok(branches) let input = String::from_utf8_lossy(&output.stdout);
}
.boxed() let mut branches = parse_branch_input(&input)?;
if branches.is_empty() {
let args = vec!["symbolic-ref", "--quiet", "HEAD"];
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()
.await?;
// git symbolic-ref returns a non-0 exit code if HEAD points
// to something other than a branch
if output.status.success() {
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
branches.push(Branch {
ref_name: name.into(),
is_head: true,
upstream: None,
most_recent_commit: None,
});
}
}
Ok(branches)
})
.boxed()
} }
fn change_branch(&self, name: String) -> BoxFuture<Result<()>> { fn change_branch(&self, name: String) -> BoxFuture<Result<()>> {
@ -1691,15 +1708,7 @@ fn parse_branch_input(input: &str) -> Result<Vec<Branch>> {
let is_current_branch = fields.next().context("no HEAD")? == "*"; let is_current_branch = fields.next().context("no HEAD")? == "*";
let head_sha: SharedString = fields.next().context("no objectname")?.to_string().into(); let head_sha: SharedString = fields.next().context("no objectname")?.to_string().into();
let parent_sha: SharedString = fields.next().context("no parent")?.to_string().into(); let parent_sha: SharedString = fields.next().context("no parent")?.to_string().into();
let raw_ref_name = fields.next().context("no refname")?; let ref_name = fields.next().context("no refname")?.to_string().into();
let ref_name: SharedString =
if let Some(ref_name) = raw_ref_name.strip_prefix("refs/heads/") {
ref_name.to_string().into()
} else if let Some(ref_name) = raw_ref_name.strip_prefix("refs/remotes/") {
ref_name.to_string().into()
} else {
return Err(anyhow!("unexpected format for refname"));
};
let upstream_name = fields.next().context("no upstream")?.to_string(); let upstream_name = fields.next().context("no upstream")?.to_string();
let upstream_tracking = parse_upstream_track(fields.next().context("no upstream:track")?)?; let upstream_tracking = parse_upstream_track(fields.next().context("no upstream:track")?)?;
let commiterdate = fields.next().context("no committerdate")?.parse::<i64>()?; let commiterdate = fields.next().context("no committerdate")?.parse::<i64>()?;
@ -1711,7 +1720,7 @@ fn parse_branch_input(input: &str) -> Result<Vec<Branch>> {
branches.push(Branch { branches.push(Branch {
is_head: is_current_branch, is_head: is_current_branch,
name: ref_name, ref_name: ref_name,
most_recent_commit: Some(CommitSummary { most_recent_commit: Some(CommitSummary {
sha: head_sha, sha: head_sha,
subject, subject,
@ -1974,7 +1983,7 @@ mod tests {
parse_branch_input(&input).unwrap(), parse_branch_input(&input).unwrap(),
vec![Branch { vec![Branch {
is_head: true, is_head: true,
name: "zed-patches".into(), ref_name: "refs/heads/zed-patches".into(),
upstream: Some(Upstream { upstream: Some(Upstream {
ref_name: "refs/remotes/origin/zed-patches".into(), ref_name: "refs/remotes/origin/zed-patches".into(),
tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus { tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus {

View file

@ -1,6 +1,7 @@
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow};
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use collections::HashSet;
use git::repository::Branch; use git::repository::Branch;
use gpui::{ use gpui::{
App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
@ -95,12 +96,28 @@ impl BranchList {
.context("No active repository")? .context("No active repository")?
.await??; .await??;
all_branches.sort_by_key(|branch| { let all_branches = cx
branch .background_spawn(async move {
.most_recent_commit let upstreams: HashSet<_> = all_branches
.as_ref() .iter()
.map(|commit| 0 - commit.commit_timestamp) .filter_map(|branch| {
}); let upstream = branch.upstream.as_ref()?;
Some(upstream.ref_name.clone())
})
.collect();
all_branches.retain(|branch| !upstreams.contains(&branch.ref_name));
all_branches.sort_by_key(|branch| {
branch
.most_recent_commit
.as_ref()
.map(|commit| 0 - commit.commit_timestamp)
});
all_branches
})
.await;
this.update_in(cx, |this, window, cx| { this.update_in(cx, |this, window, cx| {
this.picker.update(cx, |picker, cx| { this.picker.update(cx, |picker, cx| {
@ -266,6 +283,7 @@ impl PickerDelegate for BranchListDelegate {
let mut matches: Vec<BranchEntry> = if query.is_empty() { let mut matches: Vec<BranchEntry> = if query.is_empty() {
all_branches all_branches
.into_iter() .into_iter()
.filter(|branch| !branch.is_remote())
.take(RECENT_BRANCHES_COUNT) .take(RECENT_BRANCHES_COUNT)
.map(|branch| BranchEntry { .map(|branch| BranchEntry {
branch, branch,
@ -277,7 +295,7 @@ impl PickerDelegate for BranchListDelegate {
let candidates = all_branches let candidates = all_branches
.iter() .iter()
.enumerate() .enumerate()
.map(|(ix, command)| StringMatchCandidate::new(ix, &command.name.clone())) .map(|(ix, branch)| StringMatchCandidate::new(ix, branch.name()))
.collect::<Vec<StringMatchCandidate>>(); .collect::<Vec<StringMatchCandidate>>();
fuzzy::match_strings( fuzzy::match_strings(
&candidates, &candidates,
@ -303,11 +321,11 @@ impl PickerDelegate for BranchListDelegate {
if !query.is_empty() if !query.is_empty()
&& !matches && !matches
.first() .first()
.is_some_and(|entry| entry.branch.name == query) .is_some_and(|entry| entry.branch.name() == query)
{ {
matches.push(BranchEntry { matches.push(BranchEntry {
branch: Branch { branch: Branch {
name: query.clone().into(), ref_name: format!("refs/heads/{query}").into(),
is_head: false, is_head: false,
upstream: None, upstream: None,
most_recent_commit: None, most_recent_commit: None,
@ -335,19 +353,19 @@ impl PickerDelegate for BranchListDelegate {
return; return;
}; };
if entry.is_new { if entry.is_new {
self.create_branch(entry.branch.name.clone(), window, cx); self.create_branch(entry.branch.name().to_owned().into(), window, cx);
return; return;
} }
let current_branch = self.repo.as_ref().map(|repo| { let current_branch = self.repo.as_ref().map(|repo| {
repo.update(cx, |repo, _| { repo.update(cx, |repo, _| {
repo.branch.as_ref().map(|branch| branch.name.clone()) repo.branch.as_ref().map(|branch| branch.ref_name.clone())
}) })
}); });
if current_branch if current_branch
.flatten() .flatten()
.is_some_and(|current_branch| current_branch == entry.branch.name) .is_some_and(|current_branch| current_branch == entry.branch.ref_name)
{ {
cx.emit(DismissEvent); cx.emit(DismissEvent);
return; return;
@ -368,7 +386,7 @@ impl PickerDelegate for BranchListDelegate {
anyhow::Ok(async move { anyhow::Ok(async move {
repo.update(&mut cx, |repo, _| { repo.update(&mut cx, |repo, _| {
repo.change_branch(branch.name.to_string()) repo.change_branch(branch.name().to_string())
})? })?
.await? .await?
}) })
@ -443,13 +461,13 @@ impl PickerDelegate for BranchListDelegate {
if entry.is_new { if entry.is_new {
Label::new(format!( Label::new(format!(
"Create branch \"{}\"", "Create branch \"{}\"",
entry.branch.name entry.branch.name()
)) ))
.single_line() .single_line()
.into_any_element() .into_any_element()
} else { } else {
HighlightedLabel::new( HighlightedLabel::new(
entry.branch.name.clone(), entry.branch.name().to_owned(),
entry.positions.clone(), entry.positions.clone(),
) )
.truncate() .truncate()
@ -470,7 +488,7 @@ impl PickerDelegate for BranchListDelegate {
let message = if entry.is_new { let message = if entry.is_new {
if let Some(current_branch) = if let Some(current_branch) =
self.repo.as_ref().and_then(|repo| { self.repo.as_ref().and_then(|repo| {
repo.read(cx).branch.as_ref().map(|b| b.name.clone()) repo.read(cx).branch.as_ref().map(|b| b.name())
}) })
{ {
format!("based off {}", current_branch) format!("based off {}", current_branch)

View file

@ -321,8 +321,8 @@ impl CommitModal {
let branch = active_repo let branch = active_repo
.as_ref() .as_ref()
.and_then(|repo| repo.read(cx).branch.as_ref()) .and_then(|repo| repo.read(cx).branch.as_ref())
.map(|b| b.name.clone()) .map(|b| b.name().to_owned())
.unwrap_or_else(|| "<no branch>".into()); .unwrap_or_else(|| "<no branch>".to_owned());
let branch_picker_button = panel_button(branch) let branch_picker_button = panel_button(branch)
.icon(IconName::GitBranch) .icon(IconName::GitBranch)

View file

@ -1953,7 +1953,12 @@ impl GitPanel {
})?; })?;
let pull = repo.update(cx, |repo, cx| { let pull = repo.update(cx, |repo, cx| {
repo.pull(branch.name.clone(), remote.name.clone(), askpass, cx) repo.pull(
branch.name().to_owned().into(),
remote.name.clone(),
askpass,
cx,
)
})?; })?;
let remote_message = pull.await?; let remote_message = pull.await?;
@ -2020,7 +2025,7 @@ impl GitPanel {
let push = repo.update(cx, |repo, cx| { let push = repo.update(cx, |repo, cx| {
repo.push( repo.push(
branch.name.clone(), branch.name().to_owned().into(),
remote.name.clone(), remote.name.clone(),
options, options,
askpass_delegate, askpass_delegate,
@ -2030,7 +2035,7 @@ impl GitPanel {
let remote_output = push.await?; let remote_output = push.await?;
let action = RemoteAction::Push(branch.name, remote); let action = RemoteAction::Push(branch.name().to_owned().into(), remote);
this.update(cx, |this, cx| match remote_output { this.update(cx, |this, cx| match remote_output {
Ok(remote_message) => this.show_remote_output(action, remote_message, cx), Ok(remote_message) => this.show_remote_output(action, remote_message, cx),
Err(e) => { Err(e) => {
@ -2092,7 +2097,7 @@ impl GitPanel {
return Err(anyhow::anyhow!("No active branch")); return Err(anyhow::anyhow!("No active branch"));
}; };
Ok(repo.get_remotes(Some(current_branch.name.to_string()))) Ok(repo.get_remotes(Some(current_branch.name().to_string())))
})?? })??
.await??; .await??;
@ -4363,19 +4368,17 @@ impl RenderOnce for PanelRepoFooter {
let branch_name = self let branch_name = self
.branch .branch
.as_ref() .as_ref()
.map(|branch| branch.name.clone()) .map(|branch| branch.name().to_owned())
.or_else(|| { .or_else(|| {
self.head_commit.as_ref().map(|commit| { self.head_commit.as_ref().map(|commit| {
SharedString::from( commit
commit .sha
.sha .chars()
.chars() .take(MAX_SHORT_SHA_LEN)
.take(MAX_SHORT_SHA_LEN) .collect::<String>()
.collect::<String>(),
)
}) })
}) })
.unwrap_or_else(|| SharedString::from(" (no branch)")); .unwrap_or_else(|| " (no branch)".to_owned());
let show_separator = self.branch.is_some() || self.head_commit.is_some(); let show_separator = self.branch.is_some() || self.head_commit.is_some();
let active_repo_name = self.active_repository.clone(); let active_repo_name = self.active_repository.clone();
@ -4542,7 +4545,7 @@ impl Component for PanelRepoFooter {
fn branch(upstream: Option<UpstreamTracking>) -> Branch { fn branch(upstream: Option<UpstreamTracking>) -> Branch {
Branch { Branch {
is_head: true, is_head: true,
name: "some-branch".into(), ref_name: "some-branch".into(),
upstream: upstream.map(|tracking| Upstream { upstream: upstream.map(|tracking| Upstream {
ref_name: "origin/some-branch".into(), ref_name: "origin/some-branch".into(),
tracking, tracking,
@ -4559,7 +4562,7 @@ impl Component for PanelRepoFooter {
fn custom(branch_name: &str, upstream: Option<UpstreamTracking>) -> Branch { fn custom(branch_name: &str, upstream: Option<UpstreamTracking>) -> Branch {
Branch { Branch {
is_head: true, is_head: true,
name: branch_name.to_string().into(), ref_name: branch_name.to_string().into(),
upstream: upstream.map(|tracking| Upstream { upstream: upstream.map(|tracking| Upstream {
ref_name: format!("zed/{}", branch_name).into(), ref_name: format!("zed/{}", branch_name).into(),
tracking, tracking,

View file

@ -1099,7 +1099,7 @@ impl RenderOnce for ProjectDiffEmptyState {
v_flex() v_flex()
.child(Headline::new(ahead_string).size(HeadlineSize::Small)) .child(Headline::new(ahead_string).size(HeadlineSize::Small))
.child( .child(
Label::new(format!("Push your changes to {}", branch.name)) Label::new(format!("Push your changes to {}", branch.name()))
.color(Color::Muted), .color(Color::Muted),
), ),
) )
@ -1113,7 +1113,7 @@ impl RenderOnce for ProjectDiffEmptyState {
v_flex() v_flex()
.child(Headline::new("Publish Branch").size(HeadlineSize::Small)) .child(Headline::new("Publish Branch").size(HeadlineSize::Small))
.child( .child(
Label::new(format!("Create {} on remote", branch.name)) Label::new(format!("Create {} on remote", branch.name()))
.color(Color::Muted), .color(Color::Muted),
), ),
) )
@ -1183,7 +1183,7 @@ mod preview {
fn branch(upstream: Option<UpstreamTracking>) -> Branch { fn branch(upstream: Option<UpstreamTracking>) -> Branch {
Branch { Branch {
is_head: true, is_head: true,
name: "some-branch".into(), ref_name: "some-branch".into(),
upstream: upstream.map(|tracking| Upstream { upstream: upstream.map(|tracking| Upstream {
ref_name: "origin/some-branch".into(), ref_name: "origin/some-branch".into(),
tracking, tracking,

View file

@ -3790,13 +3790,9 @@ impl Repository {
pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> { pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> {
let id = self.id; let id = self.id;
self.send_job(None, move |repo, cx| async move { self.send_job(None, move |repo, _| async move {
match repo { match repo {
RepositoryState::Local { backend, .. } => { RepositoryState::Local { backend, .. } => backend.branches().await,
let backend = backend.clone();
cx.background_spawn(async move { backend.branches().await })
.await
}
RepositoryState::Remote { project_id, client } => { RepositoryState::Remote { project_id, client } => {
let response = client let response = client
.request(proto::GitGetBranches { .request(proto::GitGetBranches {
@ -4460,7 +4456,7 @@ fn deserialize_blame_buffer_response(
fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch { fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
proto::Branch { proto::Branch {
is_head: branch.is_head, is_head: branch.is_head,
name: branch.name.to_string(), ref_name: branch.ref_name.to_string(),
unix_timestamp: branch unix_timestamp: branch
.most_recent_commit .most_recent_commit
.as_ref() .as_ref()
@ -4489,7 +4485,7 @@ fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch { fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
git::repository::Branch { git::repository::Branch {
is_head: proto.is_head, is_head: proto.is_head,
name: proto.name.clone().into(), ref_name: proto.ref_name.clone().into(),
upstream: proto upstream: proto
.upstream .upstream
.as_ref() .as_ref()

View file

@ -75,7 +75,7 @@ message GetPermalinkToLineResponse {
message Branch { message Branch {
bool is_head = 1; bool is_head = 1;
string name = 2; string ref_name = 2;
optional uint64 unix_timestamp = 3; optional uint64 unix_timestamp = 3;
optional GitUpstream upstream = 4; optional GitUpstream upstream = 4;
optional CommitSummary most_recent_commit = 5; optional CommitSummary most_recent_commit = 5;

View file

@ -1472,7 +1472,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
let remote_branches = remote_branches let remote_branches = remote_branches
.into_iter() .into_iter()
.map(|branch| branch.name.to_string()) .map(|branch| branch.name().to_string())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!(&remote_branches, &branches_set); assert_eq!(&remote_branches, &branches_set);
@ -1505,7 +1505,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
}) })
}); });
assert_eq!(server_branch.name, branches[2]); assert_eq!(server_branch.name(), branches[2]);
// Also try creating a new branch // Also try creating a new branch
cx.update(|cx| { cx.update(|cx| {
@ -1545,7 +1545,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
}) })
}); });
assert_eq!(server_branch.name, "totally-new-branch"); assert_eq!(server_branch.name(), "totally-new-branch");
} }
pub async fn init_test( pub async fn init_test(

View file

@ -518,7 +518,7 @@ impl TitleBar {
let repo = repository.read(cx); let repo = repository.read(cx);
repo.branch repo.branch
.as_ref() .as_ref()
.map(|branch| branch.name.clone()) .map(|branch| branch.name())
.map(|name| util::truncate_and_trailoff(&name, MAX_BRANCH_NAME_LENGTH)) .map(|name| util::truncate_and_trailoff(&name, MAX_BRANCH_NAME_LENGTH))
.or_else(|| { .or_else(|| {
repo.head_commit.as_ref().map(|commit| { repo.head_commit.as_ref().map(|commit| {