Detailed changes
@@ -5,7 +5,7 @@ use futures::future::{self, BoxFuture};
use git::{
blame::Blame,
repository::{
- AskPassDelegate, Branch, CommitDetails, CommitOptions, GitRepository,
+ AskPassDelegate, Branch, CommitDetails, CommitOptions, FetchOptions, GitRepository,
GitRepositoryCheckpoint, PushOptions, Remote, RepoPath, ResetMode,
},
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
@@ -405,6 +405,7 @@ impl GitRepository for FakeGitRepository {
fn fetch(
&self,
+ _fetch_options: FetchOptions,
_askpass: AskPassDelegate,
_env: Arc<HashMap<String, String>>,
_cx: AsyncApp,
@@ -49,6 +49,7 @@ actions!(
ForcePush,
Pull,
Fetch,
+ FetchFrom,
Commit,
Amend,
Cancel,
@@ -193,6 +193,44 @@ pub enum ResetMode {
Mixed,
}
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum FetchOptions {
+ All,
+ Remote(Remote),
+}
+
+impl FetchOptions {
+ pub fn to_proto(&self) -> Option<String> {
+ match self {
+ FetchOptions::All => None,
+ FetchOptions::Remote(remote) => Some(remote.clone().name.into()),
+ }
+ }
+
+ pub fn from_proto(remote_name: Option<String>) -> Self {
+ match remote_name {
+ Some(name) => FetchOptions::Remote(Remote { name: name.into() }),
+ None => FetchOptions::All,
+ }
+ }
+
+ pub fn name(&self) -> SharedString {
+ match self {
+ Self::All => "Fetch all remotes".into(),
+ Self::Remote(remote) => remote.name.clone(),
+ }
+ }
+}
+
+impl std::fmt::Display for FetchOptions {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ FetchOptions::All => write!(f, "--all"),
+ FetchOptions::Remote(remote) => write!(f, "{}", remote.name),
+ }
+ }
+}
+
/// Modifies .git/info/exclude temporarily
pub struct GitExcludeOverride {
git_exclude_path: PathBuf,
@@ -381,6 +419,7 @@ pub trait GitRepository: Send + Sync {
fn fetch(
&self,
+ fetch_options: FetchOptions,
askpass: AskPassDelegate,
env: Arc<HashMap<String, String>>,
// This method takes an AsyncApp to ensure it's invoked on the main thread,
@@ -1196,18 +1235,20 @@ impl GitRepository for RealGitRepository {
fn fetch(
&self,
+ fetch_options: FetchOptions,
ask_pass: AskPassDelegate,
env: Arc<HashMap<String, String>>,
cx: AsyncApp,
) -> BoxFuture<Result<RemoteCommandOutput>> {
let working_directory = self.working_directory();
+ let remote_name = format!("{}", fetch_options);
let executor = cx.background_executor().clone();
async move {
let mut command = new_smol_command("git");
command
.envs(env.iter())
.current_dir(&working_directory?)
- .args(["fetch", "--all"])
+ .args(["fetch", &remote_name])
.stdout(smol::process::Stdio::piped())
.stderr(smol::process::Stdio::piped());
@@ -20,8 +20,8 @@ use editor::{
use futures::StreamExt as _;
use git::blame::ParsedCommitMessage;
use git::repository::{
- Branch, CommitDetails, CommitOptions, CommitSummary, DiffType, PushOptions, Remote,
- RemoteCommandOutput, ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
+ Branch, CommitDetails, CommitOptions, CommitSummary, DiffType, FetchOptions, PushOptions,
+ Remote, RemoteCommandOutput, ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
};
use git::status::StageStatus;
use git::{Amend, ToggleStaged, repository::RepoPath, status::FileStatus};
@@ -1840,7 +1840,49 @@ impl GitPanel {
}));
}
- pub(crate) fn fetch(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ fn get_fetch_options(
+ &self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Task<Option<FetchOptions>> {
+ let repo = self.active_repository.clone();
+ let workspace = self.workspace.clone();
+
+ cx.spawn_in(window, async move |_, cx| {
+ let repo = repo?;
+ let remotes = repo
+ .update(cx, |repo, _| repo.get_remotes(None))
+ .ok()?
+ .await
+ .ok()?
+ .log_err()?;
+
+ let mut remotes: Vec<_> = remotes.into_iter().map(FetchOptions::Remote).collect();
+ if remotes.len() > 1 {
+ remotes.push(FetchOptions::All);
+ }
+ let selection = cx
+ .update(|window, cx| {
+ picker_prompt::prompt(
+ "Pick which remote to fetch",
+ remotes.iter().map(|r| r.name()).collect(),
+ workspace,
+ window,
+ cx,
+ )
+ })
+ .ok()?
+ .await?;
+ remotes.get(selection).cloned()
+ })
+ }
+
+ pub(crate) fn fetch(
+ &mut self,
+ is_fetch_all: bool,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
if !self.can_push_and_pull(cx) {
return;
}
@@ -1851,13 +1893,28 @@ impl GitPanel {
telemetry::event!("Git Fetched");
let askpass = self.askpass_delegate("git fetch", window, cx);
let this = cx.weak_entity();
+
+ let fetch_options = if is_fetch_all {
+ Task::ready(Some(FetchOptions::All))
+ } else {
+ self.get_fetch_options(window, cx)
+ };
+
window
.spawn(cx, async move |cx| {
- let fetch = repo.update(cx, |repo, cx| repo.fetch(askpass, cx))?;
+ let Some(fetch_options) = fetch_options.await else {
+ return Ok(());
+ };
+ let fetch = repo.update(cx, |repo, cx| {
+ repo.fetch(fetch_options.clone(), askpass, cx)
+ })?;
let remote_message = fetch.await?;
this.update(cx, |this, cx| {
- let action = RemoteAction::Fetch;
+ let action = match fetch_options {
+ FetchOptions::All => RemoteAction::Fetch(None),
+ FetchOptions::Remote(remote) => RemoteAction::Fetch(Some(remote)),
+ };
match remote_message {
Ok(remote_message) => this.show_remote_output(action, remote_message, cx),
Err(e) => {
@@ -2123,38 +2180,32 @@ impl GitPanel {
async move {
let repo = repo.context("No active repository")?;
- let mut current_remotes: Vec<Remote> = repo
+ let current_remotes: Vec<Remote> = repo
.update(&mut cx, |repo, _| {
let current_branch = repo.branch.as_ref().context("No active branch")?;
anyhow::Ok(repo.get_remotes(Some(current_branch.name().to_string())))
})??
.await??;
- if current_remotes.len() == 0 {
- anyhow::bail!("No active remote");
- } else if current_remotes.len() == 1 {
- return Ok(Some(current_remotes.pop().unwrap()));
- } else {
- let current_remotes: Vec<_> = current_remotes
- .into_iter()
- .map(|remotes| remotes.name)
- .collect();
- let selection = cx
- .update(|window, cx| {
- picker_prompt::prompt(
- "Pick which remote to push to",
- current_remotes.clone(),
- workspace,
- window,
- cx,
- )
- })?
- .await;
+ let current_remotes: Vec<_> = current_remotes
+ .into_iter()
+ .map(|remotes| remotes.name)
+ .collect();
+ let selection = cx
+ .update(|window, cx| {
+ picker_prompt::prompt(
+ "Pick which remote to push to",
+ current_remotes.clone(),
+ workspace,
+ window,
+ cx,
+ )
+ })?
+ .await;
- Ok(selection.map(|selection| Remote {
- name: current_remotes[selection].clone(),
- }))
- }
+ Ok(selection.map(|selection| Remote {
+ name: current_remotes[selection].clone(),
+ }))
}
}
@@ -59,7 +59,15 @@ pub fn init(cx: &mut App) {
return;
};
panel.update(cx, |panel, cx| {
- panel.fetch(window, cx);
+ panel.fetch(true, window, cx);
+ });
+ });
+ workspace.register_action(|workspace, _: &git::FetchFrom, window, cx| {
+ let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+ return;
+ };
+ panel.update(cx, |panel, cx| {
+ panel.fetch(false, window, cx);
});
});
workspace.register_action(|workspace, _: &git::Push, window, cx| {
@@ -367,6 +375,7 @@ mod remote_button {
el.context(keybinding_target.clone())
})
.action("Fetch", git::Fetch.boxed_clone())
+ .action("Fetch From", git::FetchFrom.boxed_clone())
.action("Pull", git::Pull.boxed_clone())
.separator()
.action("Push", git::Push.boxed_clone())
@@ -28,6 +28,8 @@ pub fn prompt(
) -> Task<Option<usize>> {
if options.is_empty() {
return Task::ready(None);
+ } else if options.len() == 1 {
+ return Task::ready(Some(0));
}
let prompt = prompt.to_string().into();
@@ -6,7 +6,7 @@ use util::ResultExt as _;
#[derive(Clone)]
pub enum RemoteAction {
- Fetch,
+ Fetch(Option<Remote>),
Pull(Remote),
Push(SharedString, Remote),
}
@@ -14,7 +14,7 @@ pub enum RemoteAction {
impl RemoteAction {
pub fn name(&self) -> &'static str {
match self {
- RemoteAction::Fetch => "fetch",
+ RemoteAction::Fetch(_) => "fetch",
RemoteAction::Pull(_) => "pull",
RemoteAction::Push(_, _) => "push",
}
@@ -34,15 +34,19 @@ pub struct SuccessMessage {
pub fn format_output(action: &RemoteAction, output: RemoteCommandOutput) -> SuccessMessage {
match action {
- RemoteAction::Fetch => {
+ RemoteAction::Fetch(remote) => {
if output.stderr.is_empty() {
SuccessMessage {
message: "Already up to date".into(),
style: SuccessStyle::Toast,
}
} else {
+ let message = match remote {
+ Some(remote) => format!("Synchronized with {}", remote.name),
+ None => "Synchronized with remotes".into(),
+ };
SuccessMessage {
- message: "Synchronized with remotes".into(),
+ message,
style: SuccessStyle::ToastWithLog { output },
}
}
@@ -23,9 +23,9 @@ use git::{
blame::Blame,
parse_git_remote_url,
repository::{
- Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, GitRepository,
- GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
- UpstreamTrackingStatus,
+ Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, FetchOptions,
+ GitRepository, GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath,
+ ResetMode, UpstreamTrackingStatus,
},
status::{
FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
@@ -1553,6 +1553,7 @@ impl GitStore {
) -> Result<proto::RemoteMessageResponse> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
+ let fetch_options = FetchOptions::from_proto(envelope.payload.remote);
let askpass_id = envelope.payload.askpass_id;
let askpass = make_remote_delegate(
@@ -1565,7 +1566,7 @@ impl GitStore {
let remote_output = repository_handle
.update(&mut cx, |repository_handle, cx| {
- repository_handle.fetch(askpass, cx)
+ repository_handle.fetch(fetch_options, askpass, cx)
})?
.await??;
@@ -3500,6 +3501,7 @@ impl Repository {
pub fn fetch(
&mut self,
+ fetch_options: FetchOptions,
askpass: AskPassDelegate,
_cx: &mut App,
) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
@@ -3513,7 +3515,7 @@ impl Repository {
backend,
environment,
..
- } => backend.fetch(askpass, environment, cx).await,
+ } => backend.fetch(fetch_options, askpass, environment, cx).await,
RepositoryState::Remote { project_id, client } => {
askpass_delegates.lock().insert(askpass_id, askpass);
let _defer = util::defer(|| {
@@ -3526,6 +3528,7 @@ impl Repository {
project_id: project_id.0,
repository_id: id.to_proto(),
askpass_id,
+ remote: fetch_options.to_proto(),
})
.await
.context("sending fetch request")?;
@@ -326,6 +326,7 @@ message Fetch {
reserved 2;
uint64 repository_id = 3;
uint64 askpass_id = 4;
+ optional string remote = 5;
}
message GetRemotes {