@@ -10,7 +10,7 @@ use git::{
},
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
};
-use gpui::{AsyncApp, BackgroundExecutor};
+use gpui::{AsyncApp, BackgroundExecutor, SharedString};
use ignore::gitignore::GitignoreBuilder;
use rope::Rope;
use smol::future::FutureExt as _;
@@ -491,4 +491,8 @@ impl GitRepository for FakeGitRepository {
) -> BoxFuture<'_, Result<String>> {
unimplemented!()
}
+
+ fn default_branch(&self) -> BoxFuture<'_, Result<Option<SharedString>>> {
+ unimplemented!()
+ }
}
@@ -463,6 +463,8 @@ pub trait GitRepository: Send + Sync {
base_checkpoint: GitRepositoryCheckpoint,
target_checkpoint: GitRepositoryCheckpoint,
) -> BoxFuture<'_, Result<String>>;
+
+ fn default_branch(&self) -> BoxFuture<'_, Result<Option<SharedString>>>;
}
pub enum DiffType {
@@ -1607,6 +1609,37 @@ impl GitRepository for RealGitRepository {
})
.boxed()
}
+
+ fn default_branch(&self) -> BoxFuture<'_, Result<Option<SharedString>>> {
+ let working_directory = self.working_directory();
+ let git_binary_path = self.git_binary_path.clone();
+
+ let executor = self.executor.clone();
+ self.executor
+ .spawn(async move {
+ let working_directory = working_directory?;
+ let git = GitBinary::new(git_binary_path, working_directory, executor);
+
+ if let Ok(output) = git
+ .run(&["symbolic-ref", "refs/remotes/upstream/HEAD"])
+ .await
+ {
+ let output = output
+ .strip_prefix("refs/remotes/upstream/")
+ .map(|s| SharedString::from(s.to_owned()));
+ return Ok(output);
+ }
+
+ let output = git
+ .run(&["symbolic-ref", "refs/remotes/origin/HEAD"])
+ .await?;
+
+ Ok(output
+ .strip_prefix("refs/remotes/origin/")
+ .map(|s| SharedString::from(s.to_owned())))
+ })
+ .boxed()
+ }
}
fn git_status_args(path_prefixes: &[RepoPath]) -> Vec<OsString> {
@@ -13,7 +13,7 @@ use project::git_store::Repository;
use std::sync::Arc;
use time::OffsetDateTime;
use time_format::format_local_timestamp;
-use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
+use ui::{HighlightedLabel, ListItem, ListItemSpacing, Tooltip, prelude::*};
use util::ResultExt;
use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace};
@@ -90,11 +90,21 @@ impl BranchList {
let all_branches_request = repository
.clone()
.map(|repository| repository.update(cx, |repository, _| repository.branches()));
+ let default_branch_request = repository
+ .clone()
+ .map(|repository| repository.update(cx, |repository, _| repository.default_branch()));
cx.spawn_in(window, async move |this, cx| {
let mut all_branches = all_branches_request
.context("No active repository")?
.await??;
+ let default_branch = default_branch_request
+ .context("No active repository")?
+ .await
+ .map(Result::ok)
+ .ok()
+ .flatten()
+ .flatten();
let all_branches = cx
.background_spawn(async move {
@@ -124,6 +134,7 @@ impl BranchList {
this.update_in(cx, |this, window, cx| {
this.picker.update(cx, |picker, cx| {
+ picker.delegate.default_branch = default_branch;
picker.delegate.all_branches = Some(all_branches);
picker.refresh(window, cx);
})
@@ -192,6 +203,7 @@ struct BranchEntry {
pub struct BranchListDelegate {
matches: Vec<BranchEntry>,
all_branches: Option<Vec<Branch>>,
+ default_branch: Option<SharedString>,
repo: Option<Entity<Repository>>,
style: BranchListStyle,
selected_index: usize,
@@ -206,6 +218,7 @@ impl BranchListDelegate {
repo,
style,
all_branches: None,
+ default_branch: None,
selected_index: 0,
last_query: Default::default(),
modifiers: Default::default(),
@@ -214,6 +227,7 @@ impl BranchListDelegate {
fn create_branch(
&self,
+ from_branch: Option<SharedString>,
new_branch_name: SharedString,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
@@ -223,6 +237,11 @@ impl BranchListDelegate {
};
let new_branch_name = new_branch_name.to_string().replace(' ', "-");
cx.spawn(async move |_, cx| {
+ if let Some(based_branch) = from_branch {
+ repo.update(cx, |repo, _| repo.change_branch(based_branch.to_string()))?
+ .await??;
+ }
+
repo.update(cx, |repo, _| {
repo.create_branch(new_branch_name.to_string())
})?
@@ -353,12 +372,22 @@ impl PickerDelegate for BranchListDelegate {
})
}
- fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
+ fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index()) else {
return;
};
if entry.is_new {
- self.create_branch(entry.branch.name().to_owned().into(), window, cx);
+ let from_branch = if secondary {
+ self.default_branch.clone()
+ } else {
+ None
+ };
+ self.create_branch(
+ from_branch,
+ entry.branch.name().to_owned().into(),
+ window,
+ cx,
+ );
return;
}
@@ -439,6 +468,28 @@ impl PickerDelegate for BranchListDelegate {
})
.unwrap_or_else(|| (None, None));
+ let icon = if let Some(default_branch) = self.default_branch.clone()
+ && entry.is_new
+ {
+ Some(
+ IconButton::new("branch-from-default", IconName::GitBranchSmall)
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.delegate.set_selected_index(ix, window, cx);
+ this.delegate.confirm(true, window, cx);
+ }))
+ .tooltip(move |window, cx| {
+ Tooltip::for_action(
+ format!("Create branch based off default: {default_branch}"),
+ &menu::SecondaryConfirm,
+ window,
+ cx,
+ )
+ }),
+ )
+ } else {
+ None
+ };
+
let branch_name = if entry.is_new {
h_flex()
.gap_1()
@@ -504,7 +555,8 @@ impl PickerDelegate for BranchListDelegate {
.color(Color::Muted)
}))
}),
- ),
+ )
+ .end_slot::<IconButton>(icon),
)
}
@@ -4025,6 +4025,25 @@ impl Repository {
})
}
+ pub fn default_branch(&mut self) -> oneshot::Receiver<Result<Option<SharedString>>> {
+ let id = self.id;
+ self.send_job(None, move |repo, _| async move {
+ match repo {
+ RepositoryState::Local { backend, .. } => backend.default_branch().await,
+ RepositoryState::Remote { project_id, client } => {
+ let response = client
+ .request(proto::GetDefaultBranch {
+ project_id: project_id.0,
+ repository_id: id.to_proto(),
+ })
+ .await?;
+
+ anyhow::Ok(response.branch.map(SharedString::from))
+ }
+ }
+ })
+ }
+
pub fn diff(&mut self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
let id = self.id;
self.send_job(None, move |repo, _cx| async move {
@@ -315,7 +315,9 @@ messages!(
(LogToDebugConsole, Background),
(GetDocumentDiagnostics, Background),
(GetDocumentDiagnosticsResponse, Background),
- (PullWorkspaceDiagnostics, Background)
+ (PullWorkspaceDiagnostics, Background),
+ (GetDefaultBranch, Background),
+ (GetDefaultBranchResponse, Background),
);
request_messages!(
@@ -483,7 +485,8 @@ request_messages!(
(GetDebugAdapterBinary, DebugAdapterBinary),
(RunDebugLocators, DebugRequest),
(GetDocumentDiagnostics, GetDocumentDiagnosticsResponse),
- (PullWorkspaceDiagnostics, Ack)
+ (PullWorkspaceDiagnostics, Ack),
+ (GetDefaultBranch, GetDefaultBranchResponse),
);
entity_messages!(
@@ -615,7 +618,8 @@ entity_messages!(
GetDebugAdapterBinary,
LogToDebugConsole,
GetDocumentDiagnostics,
- PullWorkspaceDiagnostics
+ PullWorkspaceDiagnostics,
+ GetDefaultBranch
);
entity_messages!(