Enable collaborating editing of the commit message input inside the git panel (#24130)
https://github.com/user-attachments/assets/200b88b8-249a-4841-97cd-fda8365efd00 Now all users in the collab/ssh session can edit the commit input collaboratively, observing each others' changes live. A real `.git/COMMIT_EDITMSG` file is opened, which automatically enables its syntax highlight, but its original context is never used or saved on disk — this way we avoid stale commit messages from previous commits that git places there. A caveat: previous version put some effort into preserving unfinished commit messages on repo swtiches, but this version would not do that — instead, it will be blank on startup, and use whatever `.git/COMMIT_EDITMSG` contents on repo switch Release Notes: - N/A --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
6b48a6e690
commit
a864168c27
11 changed files with 595 additions and 364 deletions
|
@ -1,16 +1,16 @@
|
|||
use anyhow::{anyhow, Context as _, Result};
|
||||
use extension::ExtensionHostProxy;
|
||||
use extension_host::headless_host::HeadlessExtensionStore;
|
||||
use fs::Fs;
|
||||
use fs::{CreateOptions, Fs};
|
||||
use futures::channel::mpsc;
|
||||
use git::repository::RepoPath;
|
||||
use git::{repository::RepoPath, COMMIT_MESSAGE};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel, SharedString};
|
||||
use http_client::HttpClient;
|
||||
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{
|
||||
buffer_store::{BufferStore, BufferStoreEvent},
|
||||
git::GitState,
|
||||
git::{GitRepo, GitState, RepositoryHandle},
|
||||
project_settings::SettingsObserver,
|
||||
search::SearchQuery,
|
||||
task_store::TaskStore,
|
||||
|
@ -83,8 +83,7 @@ impl HeadlessProject {
|
|||
store
|
||||
});
|
||||
|
||||
let git_state =
|
||||
cx.new(|cx| GitState::new(&worktree_store, languages.clone(), None, None, cx));
|
||||
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);
|
||||
|
@ -201,6 +200,7 @@ impl HeadlessProject {
|
|||
client.add_model_request_handler(Self::handle_stage);
|
||||
client.add_model_request_handler(Self::handle_unstage);
|
||||
client.add_model_request_handler(Self::handle_commit);
|
||||
client.add_model_request_handler(Self::handle_open_commit_message_buffer);
|
||||
|
||||
client.add_request_handler(
|
||||
extensions.clone().downgrade(),
|
||||
|
@ -625,20 +625,8 @@ impl HeadlessProject {
|
|||
) -> 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 = this.update(&mut cx, |project, cx| {
|
||||
let repository_handle = project
|
||||
.git_state
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find(|repository_handle| {
|
||||
repository_handle.worktree_id == worktree_id
|
||||
&& repository_handle.repository_entry.work_directory_id()
|
||||
== work_directory_id
|
||||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})??;
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
|
@ -665,20 +653,8 @@ impl HeadlessProject {
|
|||
) -> 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 = this.update(&mut cx, |project, cx| {
|
||||
let repository_handle = project
|
||||
.git_state
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find(|repository_handle| {
|
||||
repository_handle.worktree_id == worktree_id
|
||||
&& repository_handle.repository_entry.work_directory_id()
|
||||
== work_directory_id
|
||||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})??;
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
|
@ -705,7 +681,106 @@ impl HeadlessProject {
|
|||
) -> 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 = this.update(&mut cx, |project, cx| {
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let name = envelope.payload.name.map(SharedString::from);
|
||||
let email = envelope.payload.email.map(SharedString::from);
|
||||
let (err_sender, mut err_receiver) = mpsc::channel(1);
|
||||
cx.update(|cx| {
|
||||
repository_handle
|
||||
.commit(name.zip(email), err_sender, cx)
|
||||
.context("unstaging entries")
|
||||
})??;
|
||||
if let Some(error) = err_receiver.next().await {
|
||||
Err(error.context("error during unstaging"))
|
||||
} else {
|
||||
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_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let git_repository = match &repository_handle.git_repo {
|
||||
GitRepo::Local(git_repository) => git_repository.clone(),
|
||||
GitRepo::Remote { .. } => {
|
||||
anyhow::bail!("Cannot handle open commit message buffer for remote git repo")
|
||||
}
|
||||
};
|
||||
let commit_message_file = git_repository.dot_git_dir().join(*COMMIT_MESSAGE);
|
||||
let fs = this.update(&mut cx, |headless_project, _| headless_project.fs.clone())?;
|
||||
fs.create_file(
|
||||
&commit_message_file,
|
||||
CreateOptions {
|
||||
overwrite: false,
|
||||
ignore_if_exists: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("creating commit message file {commit_message_file:?}"))?;
|
||||
|
||||
let (worktree, relative_path) = this
|
||||
.update(&mut cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.worktree_store
|
||||
.update(cx, |worktree_store, cx| {
|
||||
worktree_store.find_or_create_worktree(&commit_message_file, false, cx)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("deriving worktree for commit message file {commit_message_file:?}")
|
||||
})?;
|
||||
|
||||
let buffer = this
|
||||
.update(&mut cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.buffer_store
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Arc::from(relative_path),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.with_context(|| {
|
||||
format!("opening buffer for commit message file {commit_message_file:?}")
|
||||
})?
|
||||
.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<RepositoryHandle> {
|
||||
this.update(cx, |project, cx| {
|
||||
let repository_handle = project
|
||||
.git_state
|
||||
.read(cx)
|
||||
|
@ -718,20 +793,7 @@ impl HeadlessProject {
|
|||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})??;
|
||||
|
||||
let commit_message = envelope.payload.message;
|
||||
let name = envelope.payload.name.map(SharedString::from);
|
||||
let email = envelope.payload.email.map(SharedString::from);
|
||||
let (err_sender, mut err_receiver) = mpsc::channel(1);
|
||||
repository_handle
|
||||
.commit_with_message(commit_message, name.zip(email), err_sender)
|
||||
.context("unstaging entries")?;
|
||||
if let Some(error) = err_receiver.next().await {
|
||||
Err(error.context("error during unstaging"))
|
||||
} else {
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue