git: Enable git stash in git panel (#32821)
Related discussion #31484 Release Notes: - Added a menu entry on the git panel to git stash and git pop stash. Preview:  --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
4d00d07df1
commit
07252c3309
10 changed files with 290 additions and 2 deletions
|
@ -433,6 +433,8 @@ impl Server {
|
|||
.add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Stash>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::StashPop>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::GitInit>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GetRemotes>)
|
||||
|
|
|
@ -398,6 +398,18 @@ impl GitRepository for FakeGitRepository {
|
|||
})
|
||||
}
|
||||
|
||||
fn stash_paths(
|
||||
&self,
|
||||
_paths: Vec<RepoPath>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn stash_pop(&self, _env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn commit(
|
||||
&self,
|
||||
_message: gpui::SharedString,
|
||||
|
|
|
@ -55,6 +55,10 @@ actions!(
|
|||
StageAll,
|
||||
/// Unstages all changes in the repository.
|
||||
UnstageAll,
|
||||
/// Stashes all changes in the repository, including untracked files.
|
||||
StashAll,
|
||||
/// Pops the most recent stash.
|
||||
StashPop,
|
||||
/// Restores all tracked files to their last committed state.
|
||||
RestoreTrackedFiles,
|
||||
/// Moves all untracked files to trash.
|
||||
|
|
|
@ -395,6 +395,14 @@ pub trait GitRepository: Send + Sync {
|
|||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
|
||||
fn stash_paths(
|
||||
&self,
|
||||
paths: Vec<RepoPath>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn push(
|
||||
&self,
|
||||
branch_name: String,
|
||||
|
@ -1189,6 +1197,55 @@ impl GitRepository for RealGitRepository {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn stash_paths(
|
||||
&self,
|
||||
paths: Vec<RepoPath>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<Result<()>> {
|
||||
let working_directory = self.working_directory();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let mut cmd = new_smol_command("git");
|
||||
cmd.current_dir(&working_directory?)
|
||||
.envs(env.iter())
|
||||
.args(["stash", "push", "--quiet"])
|
||||
.arg("--include-untracked");
|
||||
|
||||
cmd.args(paths.iter().map(|p| p.as_ref()));
|
||||
|
||||
let output = cmd.output().await?;
|
||||
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"Failed to stash:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>> {
|
||||
let working_directory = self.working_directory();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let mut cmd = new_smol_command("git");
|
||||
cmd.current_dir(&working_directory?)
|
||||
.envs(env.iter())
|
||||
.args(["stash", "pop"]);
|
||||
|
||||
let output = cmd.output().await?;
|
||||
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"Failed to stash pop:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn commit(
|
||||
&self,
|
||||
message: SharedString,
|
||||
|
|
|
@ -27,7 +27,10 @@ use git::repository::{
|
|||
};
|
||||
use git::status::StageStatus;
|
||||
use git::{Amend, Signoff, ToggleStaged, repository::RepoPath, status::FileStatus};
|
||||
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||
use git::{
|
||||
ExpandCommitEditor, RestoreTrackedFiles, StageAll, StashAll, StashPop, TrashUntrackedFiles,
|
||||
UnstageAll,
|
||||
};
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
|
||||
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
|
||||
|
@ -140,6 +143,13 @@ fn git_panel_context_menu(
|
|||
UnstageAll.boxed_clone(),
|
||||
)
|
||||
.separator()
|
||||
.action_disabled_when(
|
||||
!(state.has_new_changes || state.has_tracked_changes),
|
||||
"Stash All",
|
||||
StashAll.boxed_clone(),
|
||||
)
|
||||
.action("Stash Pop", StashPop.boxed_clone())
|
||||
.separator()
|
||||
.action("Open Diff", project_diff::Diff.boxed_clone())
|
||||
.separator()
|
||||
.action_disabled_when(
|
||||
|
@ -1415,6 +1425,52 @@ impl GitPanel {
|
|||
self.tracked_staged_count + self.new_staged_count + self.conflicted_staged_count
|
||||
}
|
||||
|
||||
pub fn stash_pop(&mut self, _: &StashPop, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn({
|
||||
async move |this, cx| {
|
||||
let stash_task = active_repository
|
||||
.update(cx, |repo, cx| repo.stash_pop(cx))?
|
||||
.await;
|
||||
this.update(cx, |this, cx| {
|
||||
stash_task
|
||||
.map_err(|e| {
|
||||
this.show_error_toast("stash pop", e, cx);
|
||||
})
|
||||
.ok();
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn stash_all(&mut self, _: &StashAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn({
|
||||
async move |this, cx| {
|
||||
let stash_task = active_repository
|
||||
.update(cx, |repo, cx| repo.stash_all(cx))?
|
||||
.await;
|
||||
this.update(cx, |this, cx| {
|
||||
stash_task
|
||||
.map_err(|e| {
|
||||
this.show_error_toast("stash", e, cx);
|
||||
})
|
||||
.ok();
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn commit_message_buffer(&self, cx: &App) -> Entity<Buffer> {
|
||||
self.commit_editor
|
||||
.read(cx)
|
||||
|
@ -4365,6 +4421,8 @@ impl Render for GitPanel {
|
|||
.on_action(cx.listener(Self::revert_selected))
|
||||
.on_action(cx.listener(Self::clean_all))
|
||||
.on_action(cx.listener(Self::generate_commit_message_action))
|
||||
.on_action(cx.listener(Self::stash_all))
|
||||
.on_action(cx.listener(Self::stash_pop))
|
||||
})
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
|
|
|
@ -114,6 +114,22 @@ pub fn init(cx: &mut App) {
|
|||
});
|
||||
});
|
||||
}
|
||||
workspace.register_action(|workspace, action: &git::StashAll, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.stash_all(action, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, action: &git::StashPop, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.stash_pop(action, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, action: &git::StageAll, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
|
|
|
@ -420,6 +420,8 @@ impl GitStore {
|
|||
client.add_entity_request_handler(Self::handle_fetch);
|
||||
client.add_entity_request_handler(Self::handle_stage);
|
||||
client.add_entity_request_handler(Self::handle_unstage);
|
||||
client.add_entity_request_handler(Self::handle_stash);
|
||||
client.add_entity_request_handler(Self::handle_stash_pop);
|
||||
client.add_entity_request_handler(Self::handle_commit);
|
||||
client.add_entity_request_handler(Self::handle_reset);
|
||||
client.add_entity_request_handler(Self::handle_show);
|
||||
|
@ -1696,6 +1698,48 @@ impl GitStore {
|
|||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_stash(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Stash>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
|
||||
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.stash_entries(entries, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_stash_pop(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::StashPop>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
|
||||
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.stash_pop(cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_set_index_text(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::SetIndexText>,
|
||||
|
@ -3540,6 +3584,82 @@ impl Repository {
|
|||
self.unstage_entries(to_unstage, cx)
|
||||
}
|
||||
|
||||
pub fn stash_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
|
||||
let to_stash = self
|
||||
.cached_status()
|
||||
.map(|entry| entry.repo_path.clone())
|
||||
.collect();
|
||||
|
||||
self.stash_entries(to_stash, cx)
|
||||
}
|
||||
|
||||
pub fn stash_entries(
|
||||
&mut self,
|
||||
entries: Vec<RepoPath>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let id = self.id;
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
this.update(cx, |this, _| {
|
||||
this.send_job(None, move |git_repo, _cx| async move {
|
||||
match git_repo {
|
||||
RepositoryState::Local {
|
||||
backend,
|
||||
environment,
|
||||
..
|
||||
} => backend.stash_paths(entries, environment).await,
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
client
|
||||
.request(proto::Stash {
|
||||
project_id: project_id.0,
|
||||
repository_id: id.to_proto(),
|
||||
paths: entries
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.as_ref().to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending stash request")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
})?
|
||||
.await??;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stash_pop(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
|
||||
let id = self.id;
|
||||
cx.spawn(async move |this, cx| {
|
||||
this.update(cx, |this, _| {
|
||||
this.send_job(None, move |git_repo, _cx| async move {
|
||||
match git_repo {
|
||||
RepositoryState::Local {
|
||||
backend,
|
||||
environment,
|
||||
..
|
||||
} => backend.stash_pop(environment).await,
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
client
|
||||
.request(proto::StashPop {
|
||||
project_id: project_id.0,
|
||||
repository_id: id.to_proto(),
|
||||
})
|
||||
.await
|
||||
.context("sending stash pop request")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
})?
|
||||
.await??;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn commit(
|
||||
&mut self,
|
||||
message: SharedString,
|
||||
|
|
|
@ -286,6 +286,17 @@ message Unstage {
|
|||
repeated string paths = 4;
|
||||
}
|
||||
|
||||
message Stash {
|
||||
uint64 project_id = 1;
|
||||
uint64 repository_id = 2;
|
||||
repeated string paths = 3;
|
||||
}
|
||||
|
||||
message StashPop {
|
||||
uint64 project_id = 1;
|
||||
uint64 repository_id = 2;
|
||||
}
|
||||
|
||||
message Commit {
|
||||
uint64 project_id = 1;
|
||||
reserved 2;
|
||||
|
|
|
@ -396,8 +396,10 @@ message Envelope {
|
|||
GetDocumentColor get_document_color = 353;
|
||||
GetDocumentColorResponse get_document_color_response = 354;
|
||||
GetColorPresentation get_color_presentation = 355;
|
||||
GetColorPresentationResponse get_color_presentation_response = 356; // current max
|
||||
GetColorPresentationResponse get_color_presentation_response = 356;
|
||||
|
||||
Stash stash = 357;
|
||||
StashPop stash_pop = 358; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
|
|
@ -261,6 +261,8 @@ messages!(
|
|||
(Unfollow, Foreground),
|
||||
(UnshareProject, Foreground),
|
||||
(Unstage, Background),
|
||||
(Stash, Background),
|
||||
(StashPop, Background),
|
||||
(UpdateBuffer, Foreground),
|
||||
(UpdateBufferFile, Foreground),
|
||||
(UpdateChannelBuffer, Foreground),
|
||||
|
@ -419,6 +421,8 @@ request_messages!(
|
|||
(TaskContextForLocation, TaskContext),
|
||||
(Test, Test),
|
||||
(Unstage, Ack),
|
||||
(Stash, Ack),
|
||||
(StashPop, Ack),
|
||||
(UpdateBuffer, Ack),
|
||||
(UpdateParticipantLocation, Ack),
|
||||
(UpdateProject, Ack),
|
||||
|
@ -549,6 +553,8 @@ entity_messages!(
|
|||
TaskContextForLocation,
|
||||
UnshareProject,
|
||||
Unstage,
|
||||
Stash,
|
||||
StashPop,
|
||||
UpdateBuffer,
|
||||
UpdateBufferFile,
|
||||
UpdateDiagnosticSummary,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue