Add an undo button to the git panel (#24593)

Also prep infrastructure for pushing a commit

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Mikayla Maki 2025-02-12 14:57:08 -08:00 committed by GitHub
parent df8adc8b11
commit b014afa938
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1437 additions and 738 deletions

View file

@ -1,22 +1,20 @@
use ::proto::{FromProto, ToProto};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Result};
use extension::ExtensionHostProxy;
use extension_host::headless_host::HeadlessExtensionStore;
use fs::Fs;
use git::repository::RepoPath;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel, SharedString};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel};
use http_client::HttpClient;
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
use node_runtime::NodeRuntime;
use project::{
buffer_store::{BufferStore, BufferStoreEvent},
git::{GitState, Repository},
git::GitStore,
project_settings::SettingsObserver,
search::SearchQuery,
task_store::TaskStore,
worktree_store::WorktreeStore,
LspStore, LspStoreEvent, PrettierStore, ProjectEntryId, ProjectPath, ToolchainStore,
WorktreeId,
LspStore, LspStoreEvent, PrettierStore, ProjectPath, ToolchainStore, WorktreeId,
};
use remote::ssh_session::ChannelClient;
use rpc::{
@ -44,7 +42,7 @@ pub struct HeadlessProject {
pub next_entry_id: Arc<AtomicUsize>,
pub languages: Arc<LanguageRegistry>,
pub extensions: Entity<HeadlessExtensionStore>,
pub git_state: Entity<GitState>,
pub git_store: Entity<GitStore>,
}
pub struct HeadlessAppState {
@ -83,13 +81,14 @@ impl HeadlessProject {
store
});
let git_state = cx.new(|cx| GitState::new(&worktree_store, None, None, cx));
let buffer_store = cx.new(|cx| {
let mut buffer_store = BufferStore::local(worktree_store.clone(), cx);
buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
buffer_store
});
let git_store =
cx.new(|cx| GitStore::new(&worktree_store, buffer_store.clone(), None, None, cx));
let prettier_store = cx.new(|cx| {
PrettierStore::new(
node_runtime.clone(),
@ -180,6 +179,7 @@ impl HeadlessProject {
session.subscribe_to_entity(SSH_PROJECT_ID, &task_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &toolchain_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
session.subscribe_to_entity(SSH_PROJECT_ID, &git_store);
client.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory);
client.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata);
@ -197,12 +197,6 @@ impl HeadlessProject {
client.add_entity_request_handler(BufferStore::handle_update_buffer);
client.add_entity_message_handler(BufferStore::handle_close_buffer);
client.add_entity_request_handler(Self::handle_stage);
client.add_entity_request_handler(Self::handle_unstage);
client.add_entity_request_handler(Self::handle_commit);
client.add_entity_request_handler(Self::handle_set_index_text);
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
client.add_request_handler(
extensions.clone().downgrade(),
HeadlessExtensionStore::handle_sync_extensions,
@ -218,6 +212,7 @@ impl HeadlessProject {
LspStore::init(&client);
TaskStore::init(Some(&client));
ToolchainStore::init(&client);
GitStore::init(&client);
HeadlessProject {
session: client,
@ -230,7 +225,7 @@ impl HeadlessProject {
next_entry_id: Default::default(),
languages,
extensions,
git_state,
git_store,
}
}
@ -616,157 +611,6 @@ impl HeadlessProject {
log::debug!("Received ping from client");
Ok(proto::Ack {})
}
async fn handle_stage(
this: Entity<Self>,
envelope: TypedEnvelope<proto::Stage>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
let repository_handle =
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
let entries = envelope
.payload
.paths
.into_iter()
.map(PathBuf::from)
.map(RepoPath::new)
.collect();
repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.stage_entries(entries)
})?
.await??;
Ok(proto::Ack {})
}
async fn handle_unstage(
this: Entity<Self>,
envelope: TypedEnvelope<proto::Unstage>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
let repository_handle =
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
let entries = envelope
.payload
.paths
.into_iter()
.map(PathBuf::from)
.map(RepoPath::new)
.collect();
repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.unstage_entries(entries)
})?
.await??;
Ok(proto::Ack {})
}
async fn handle_commit(
this: Entity<Self>,
envelope: TypedEnvelope<proto::Commit>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
let repository_handle =
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
let message = SharedString::from(envelope.payload.message);
let name = envelope.payload.name.map(SharedString::from);
let email = envelope.payload.email.map(SharedString::from);
repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.commit(message, name.zip(email))
})?
.await??;
Ok(proto::Ack {})
}
async fn handle_set_index_text(
this: Entity<Self>,
envelope: TypedEnvelope<proto::SetIndexText>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
let repository =
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
repository
.update(&mut cx, |repository, _| {
repository.set_index_text(
&RepoPath::from(envelope.payload.path.as_str()),
envelope.payload.text,
)
})?
.await??;
Ok(proto::Ack {})
}
async fn handle_open_commit_message_buffer(
this: Entity<Self>,
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
mut cx: AsyncApp,
) -> Result<proto::OpenBufferResponse> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
let repository =
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
let buffer = repository
.update(&mut cx, |repository, cx| {
repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
})?
.await?;
let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
this.update(&mut cx, |headless_project, cx| {
headless_project
.buffer_store
.update(cx, |buffer_store, cx| {
buffer_store
.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
.detach_and_log_err(cx);
})
})?;
Ok(proto::OpenBufferResponse {
buffer_id: buffer_id.to_proto(),
})
}
fn repository_for_request(
this: &Entity<Self>,
worktree_id: WorktreeId,
work_directory_id: ProjectEntryId,
cx: &mut AsyncApp,
) -> Result<Entity<Repository>> {
this.update(cx, |project, cx| {
let repository_handle = project
.git_state
.read(cx)
.all_repositories()
.into_iter()
.find(|repository_handle| {
repository_handle.read(cx).worktree_id == worktree_id
&& repository_handle
.read(cx)
.repository_entry
.work_directory_id()
== work_directory_id
})
.context("missing repository handle")?;
anyhow::Ok(repository_handle)
})?
}
}
fn prompt_to_proto(

View file

@ -1364,7 +1364,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
})
});
assert_eq!(server_branch.as_ref(), branches[2]);
assert_eq!(server_branch.name, branches[2]);
// Also try creating a new branch
cx.update(|cx| {
@ -1387,7 +1387,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
})
});
assert_eq!(server_branch.as_ref(), "totally-new-branch");
assert_eq!(server_branch.name, "totally-new-branch");
}
pub async fn init_test(