git: Pick which remote to fetch (#26897)
I don't want to fetch `--all` branch, we should can picker which remote to fetch. Release Notes: - Added the `git::FetchFrom` action to fetch from a single remote. --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
a40ee74a1f
commit
edd40566b7
9 changed files with 155 additions and 42 deletions
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue