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
|
@ -8,14 +8,10 @@ use git::{
|
|||
repository::{GitRepository, RepoPath},
|
||||
status::{GitSummary, TrackedSummary},
|
||||
};
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Entity, EventEmitter, SharedString, Subscription, WeakEntity,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use gpui::{App, Context, Entity, EventEmitter, SharedString, Subscription, WeakEntity};
|
||||
use rpc::{proto, AnyProtoClient};
|
||||
use settings::WorktreeId;
|
||||
use std::sync::Arc;
|
||||
use text::Rope;
|
||||
use util::maybe;
|
||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
|
||||
|
||||
|
@ -25,7 +21,6 @@ pub struct GitState {
|
|||
repositories: Vec<RepositoryHandle>,
|
||||
active_index: Option<usize>,
|
||||
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
|
@ -34,13 +29,12 @@ pub struct RepositoryHandle {
|
|||
git_state: WeakEntity<GitState>,
|
||||
pub worktree_id: WorktreeId,
|
||||
pub repository_entry: RepositoryEntry,
|
||||
git_repo: Option<GitRepo>,
|
||||
commit_message: Entity<Buffer>,
|
||||
pub git_repo: GitRepo,
|
||||
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum GitRepo {
|
||||
pub enum GitRepo {
|
||||
Local(Arc<dyn GitRepository>),
|
||||
Remote {
|
||||
project_id: ProjectId,
|
||||
|
@ -70,12 +64,10 @@ enum Message {
|
|||
StageAndCommit {
|
||||
git_repo: GitRepo,
|
||||
paths: Vec<RepoPath>,
|
||||
message: Rope,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
},
|
||||
Commit {
|
||||
git_repo: GitRepo,
|
||||
message: Rope,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
},
|
||||
Stage(GitRepo, Vec<RepoPath>),
|
||||
|
@ -91,7 +83,6 @@ impl EventEmitter<Event> for GitState {}
|
|||
impl GitState {
|
||||
pub fn new(
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
client: Option<AnyProtoClient>,
|
||||
project_id: Option<ProjectId>,
|
||||
cx: &mut Context<'_, Self>,
|
||||
|
@ -100,150 +91,140 @@ impl GitState {
|
|||
mpsc::unbounded::<(Message, mpsc::Sender<anyhow::Error>)>();
|
||||
cx.spawn(|_, cx| async move {
|
||||
while let Some((msg, mut err_sender)) = update_receiver.next().await {
|
||||
let result = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
match msg {
|
||||
Message::StageAndCommit {
|
||||
git_repo,
|
||||
message,
|
||||
name_and_email,
|
||||
paths,
|
||||
} => {
|
||||
match git_repo {
|
||||
GitRepo::Local(repo) => {
|
||||
repo.stage_paths(&paths)?;
|
||||
repo.commit(
|
||||
&message.to_string(),
|
||||
name_and_email.as_ref().map(|(name, email)| {
|
||||
(name.as_ref(), email.as_ref())
|
||||
}),
|
||||
)?;
|
||||
let result =
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
match msg {
|
||||
Message::StageAndCommit {
|
||||
git_repo,
|
||||
name_and_email,
|
||||
paths,
|
||||
} => {
|
||||
match git_repo {
|
||||
GitRepo::Local(repo) => {
|
||||
repo.stage_paths(&paths)?;
|
||||
repo.commit(name_and_email.as_ref().map(
|
||||
|(name, email)| (name.as_ref(), email.as_ref()),
|
||||
))?;
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Stage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending stage request")?;
|
||||
let (name, email) = name_and_email.unzip();
|
||||
client
|
||||
.request(proto::Commit {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
name: name.map(String::from),
|
||||
email: email.map(String::from),
|
||||
})
|
||||
.await
|
||||
.context("sending commit request")?;
|
||||
}
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Stage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending stage request")?;
|
||||
let (name, email) = name_and_email.unzip();
|
||||
client
|
||||
.request(proto::Commit {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
message: message.to_string(),
|
||||
name: name.map(String::from),
|
||||
email: email.map(String::from),
|
||||
})
|
||||
.await
|
||||
.context("sending commit request")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Message::Stage(repo, paths) => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.stage_paths(&paths)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Stage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending stage request")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Message::Unstage(repo, paths) => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Unstage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending unstage request")?;
|
||||
Message::Stage(repo, paths) => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.stage_paths(&paths)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Stage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending stage request")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Message::Commit {
|
||||
git_repo,
|
||||
message,
|
||||
name_and_email,
|
||||
} => {
|
||||
match git_repo {
|
||||
GitRepo::Local(repo) => repo.commit(
|
||||
&message.to_string(),
|
||||
name_and_email
|
||||
.as_ref()
|
||||
.map(|(name, email)| (name.as_ref(), email.as_ref())),
|
||||
)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
let (name, email) = name_and_email.unzip();
|
||||
client
|
||||
.request(proto::Commit {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
// TODO implement collaborative commit message buffer instead and use it
|
||||
// If it works, remove `commit_with_message` method.
|
||||
message: message.to_string(),
|
||||
name: name.map(String::from),
|
||||
email: email.map(String::from),
|
||||
})
|
||||
.await
|
||||
.context("sending commit request")?;
|
||||
Message::Unstage(repo, paths) => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::Unstage {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
.context("sending unstage request")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Message::Commit {
|
||||
git_repo,
|
||||
name_and_email,
|
||||
} => {
|
||||
match git_repo {
|
||||
GitRepo::Local(repo) => {
|
||||
repo.commit(name_and_email.as_ref().map(
|
||||
|(name, email)| (name.as_ref(), email.as_ref()),
|
||||
))?
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
let (name, email) = name_and_email.unzip();
|
||||
client
|
||||
.request(proto::Commit {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
name: name.map(String::from),
|
||||
email: email.map(String::from),
|
||||
})
|
||||
.await
|
||||
.context("sending commit request")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
})
|
||||
.await;
|
||||
if let Err(e) = result {
|
||||
err_sender.send(e).await.ok();
|
||||
}
|
||||
|
@ -255,7 +236,6 @@ impl GitState {
|
|||
|
||||
GitState {
|
||||
project_id,
|
||||
languages,
|
||||
client,
|
||||
repositories: Vec::new(),
|
||||
active_index: None,
|
||||
|
@ -285,7 +265,7 @@ impl GitState {
|
|||
|
||||
worktree_store.update(cx, |worktree_store, cx| {
|
||||
for worktree in worktree_store.worktrees() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree.update(cx, |worktree, _| {
|
||||
let snapshot = worktree.snapshot();
|
||||
for repo in snapshot.repositories().iter() {
|
||||
let git_repo = worktree
|
||||
|
@ -303,6 +283,9 @@ impl GitState {
|
|||
work_directory_id: repo.work_directory_id(),
|
||||
})
|
||||
});
|
||||
let Some(git_repo) = git_repo else {
|
||||
continue;
|
||||
};
|
||||
let existing = self
|
||||
.repositories
|
||||
.iter()
|
||||
|
@ -317,25 +300,11 @@ impl GitState {
|
|||
existing_handle.repository_entry = repo.clone();
|
||||
existing_handle
|
||||
} else {
|
||||
let commit_message = cx.new(|cx| Buffer::local("", cx));
|
||||
cx.spawn({
|
||||
let commit_message = commit_message.downgrade();
|
||||
let languages = self.languages.clone();
|
||||
|_, mut cx| async move {
|
||||
let markdown = languages.language_for_name("Markdown").await?;
|
||||
commit_message.update(&mut cx, |commit_message, cx| {
|
||||
commit_message.set_language(Some(markdown), cx);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
RepositoryHandle {
|
||||
git_state: this.clone(),
|
||||
worktree_id: worktree.id(),
|
||||
repository_entry: repo.clone(),
|
||||
git_repo,
|
||||
commit_message,
|
||||
update_sender: self.update_sender.clone(),
|
||||
}
|
||||
};
|
||||
|
@ -403,10 +372,6 @@ impl RepositoryHandle {
|
|||
Some((self.worktree_id, path).into())
|
||||
}
|
||||
|
||||
pub fn commit_message(&self) -> Entity<Buffer> {
|
||||
self.commit_message.clone()
|
||||
}
|
||||
|
||||
pub fn stage_entries(
|
||||
&self,
|
||||
entries: Vec<RepoPath>,
|
||||
|
@ -415,11 +380,8 @@ impl RepositoryHandle {
|
|||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(git_repo) = self.git_repo.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.update_sender
|
||||
.unbounded_send((Message::Stage(git_repo, entries), err_sender))
|
||||
.unbounded_send((Message::Stage(self.git_repo.clone(), entries), err_sender))
|
||||
.map_err(|_| anyhow!("Failed to submit stage operation"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -432,11 +394,8 @@ impl RepositoryHandle {
|
|||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(git_repo) = self.git_repo.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.update_sender
|
||||
.unbounded_send((Message::Unstage(git_repo, entries), err_sender))
|
||||
.unbounded_send((Message::Unstage(self.git_repo.clone(), entries), err_sender))
|
||||
.map_err(|_| anyhow!("Failed to submit unstage operation"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -477,14 +436,8 @@ impl RepositoryHandle {
|
|||
self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
|
||||
}
|
||||
|
||||
pub fn can_commit(&self, commit_all: bool, cx: &App) -> bool {
|
||||
return self
|
||||
.commit_message
|
||||
.read(cx)
|
||||
.chars()
|
||||
.any(|c| !c.is_ascii_whitespace())
|
||||
&& self.have_changes()
|
||||
&& (commit_all || self.have_staged_changes());
|
||||
pub fn can_commit(&self, commit_all: bool) -> bool {
|
||||
return self.have_changes() && (commit_all || self.have_staged_changes());
|
||||
}
|
||||
|
||||
pub fn commit(
|
||||
|
@ -492,15 +445,10 @@ impl RepositoryHandle {
|
|||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
mut err_sender: mpsc::Sender<anyhow::Error>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(git_repo) = self.git_repo.clone() else {
|
||||
return;
|
||||
};
|
||||
let message = self.commit_message.read(cx).as_rope().clone();
|
||||
) -> anyhow::Result<()> {
|
||||
let result = self.update_sender.unbounded_send((
|
||||
Message::Commit {
|
||||
git_repo,
|
||||
message,
|
||||
git_repo: self.git_repo.clone(),
|
||||
name_and_email,
|
||||
},
|
||||
err_sender.clone(),
|
||||
|
@ -513,32 +461,10 @@ impl RepositoryHandle {
|
|||
.ok();
|
||||
})
|
||||
.detach();
|
||||
return;
|
||||
anyhow::bail!("Failed to submit commit operation");
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
self.commit_message.update(cx, |commit_message, cx| {
|
||||
commit_message.set_text("", cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn commit_with_message(
|
||||
&self,
|
||||
message: String,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
err_sender: mpsc::Sender<anyhow::Error>,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(git_repo) = self.git_repo.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
let result = self.update_sender.unbounded_send((
|
||||
Message::Commit {
|
||||
git_repo,
|
||||
message: message.into(),
|
||||
name_and_email,
|
||||
},
|
||||
err_sender,
|
||||
));
|
||||
anyhow::ensure!(result.is_ok(), "Failed to submit commit operation");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commit_all(
|
||||
|
@ -546,22 +472,17 @@ impl RepositoryHandle {
|
|||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
mut err_sender: mpsc::Sender<anyhow::Error>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(git_repo) = self.git_repo.clone() else {
|
||||
return;
|
||||
};
|
||||
) -> anyhow::Result<()> {
|
||||
let to_stage = self
|
||||
.repository_entry
|
||||
.status()
|
||||
.filter(|entry| !entry.status.is_staged().unwrap_or(false))
|
||||
.map(|entry| entry.repo_path.clone())
|
||||
.collect();
|
||||
let message = self.commit_message.read(cx).as_rope().clone();
|
||||
let result = self.update_sender.unbounded_send((
|
||||
Message::StageAndCommit {
|
||||
git_repo,
|
||||
git_repo: self.git_repo.clone(),
|
||||
paths: to_stage,
|
||||
message,
|
||||
name_and_email,
|
||||
},
|
||||
err_sender.clone(),
|
||||
|
@ -574,10 +495,9 @@ impl RepositoryHandle {
|
|||
.ok();
|
||||
})
|
||||
.detach();
|
||||
return;
|
||||
anyhow::bail!("Failed to submit commit all operation");
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
self.commit_message.update(cx, |commit_message, cx| {
|
||||
commit_message.set_text("", cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ use ::git::{
|
|||
blame::Blame,
|
||||
repository::{Branch, GitRepository, RepoPath},
|
||||
status::FileStatus,
|
||||
COMMIT_MESSAGE,
|
||||
};
|
||||
use gpui::{
|
||||
AnyEntity, App, AppContext as _, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter,
|
||||
|
@ -609,6 +610,7 @@ impl Project {
|
|||
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);
|
||||
|
||||
WorktreeStore::init(&client);
|
||||
BufferStore::init(&client);
|
||||
|
@ -699,9 +701,7 @@ impl Project {
|
|||
)
|
||||
});
|
||||
|
||||
let git_state = Some(
|
||||
cx.new(|cx| GitState::new(&worktree_store, languages.clone(), None, None, cx)),
|
||||
);
|
||||
let git_state = Some(cx.new(|cx| GitState::new(&worktree_store, None, None, cx)));
|
||||
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
|
||||
|
@ -824,7 +824,6 @@ impl Project {
|
|||
let git_state = Some(cx.new(|cx| {
|
||||
GitState::new(
|
||||
&worktree_store,
|
||||
languages.clone(),
|
||||
Some(ssh_proto.clone()),
|
||||
Some(ProjectId(SSH_PROJECT_ID)),
|
||||
cx,
|
||||
|
@ -1030,7 +1029,6 @@ impl Project {
|
|||
let git_state = Some(cx.new(|cx| {
|
||||
GitState::new(
|
||||
&worktree_store,
|
||||
languages.clone(),
|
||||
Some(client.clone().into()),
|
||||
Some(ProjectId(remote_id)),
|
||||
cx,
|
||||
|
@ -3974,21 +3972,8 @@ impl Project {
|
|||
) -> 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()
|
||||
.context("missing 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
|
||||
|
@ -4015,21 +4000,8 @@ impl Project {
|
|||
) -> 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()
|
||||
.context("missing 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
|
||||
|
@ -4056,7 +4028,93 @@ impl Project {
|
|||
) -> 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 {
|
||||
git::GitRepo::Local(git_repository) => git_repository.clone(),
|
||||
git::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, |project, _| 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 peer_id = envelope.original_sender_id()?;
|
||||
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
|
||||
}
|
||||
|
||||
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()
|
||||
.context("missing git state")?
|
||||
|
@ -4070,20 +4128,7 @@ impl Project {
|
|||
})
|
||||
.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 {})
|
||||
}
|
||||
})?
|
||||
}
|
||||
|
||||
fn respond_to_open_buffer_request(
|
||||
|
@ -4122,7 +4167,7 @@ impl Project {
|
|||
buffer.read(cx).remote_id()
|
||||
}
|
||||
|
||||
fn wait_for_remote_buffer(
|
||||
pub fn wait_for_remote_buffer(
|
||||
&mut self,
|
||||
id: BufferId,
|
||||
cx: &mut Context<Self>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue