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
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5264,9 +5264,11 @@ dependencies = [
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"git",
|
"git",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
"picker",
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
|
"rpc",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
|
|
@ -394,6 +394,7 @@ impl Server {
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
|
.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::Unstage>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||||
.add_message_handler(update_context)
|
.add_message_handler(update_context)
|
||||||
.add_request_handler({
|
.add_request_handler({
|
||||||
|
|
|
@ -12060,6 +12060,10 @@ impl Editor {
|
||||||
self.buffer.read(cx).read(cx).text()
|
self.buffer.read(cx).read(cx).text()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self, cx: &App) -> bool {
|
||||||
|
self.buffer.read(cx).read(cx).is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text_option(&self, cx: &App) -> Option<String> {
|
pub fn text_option(&self, cx: &App) -> Option<String> {
|
||||||
let text = self.text(cx);
|
let text = self.text(cx);
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::status::FileStatus;
|
use crate::status::FileStatus;
|
||||||
use crate::GitHostingProviderRegistry;
|
|
||||||
use crate::{blame::Blame, status::GitStatus};
|
use crate::{blame::Blame, status::GitStatus};
|
||||||
|
use crate::{GitHostingProviderRegistry, COMMIT_MESSAGE};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use git2::BranchType;
|
use git2::BranchType;
|
||||||
|
@ -62,7 +62,7 @@ pub trait GitRepository: Send + Sync {
|
||||||
/// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index.
|
/// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index.
|
||||||
fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()>;
|
fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()>;
|
||||||
|
|
||||||
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()>;
|
fn commit(&self, name_and_email: Option<(&str, &str)>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for dyn GitRepository {
|
impl std::fmt::Debug for dyn GitRepository {
|
||||||
|
@ -283,14 +283,22 @@ impl GitRepository for RealGitRepository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()> {
|
fn commit(&self, name_and_email: Option<(&str, &str)>) -> Result<()> {
|
||||||
let working_directory = self
|
let working_directory = self
|
||||||
.repository
|
.repository
|
||||||
.lock()
|
.lock()
|
||||||
.workdir()
|
.workdir()
|
||||||
.context("failed to read git work directory")?
|
.context("failed to read git work directory")?
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
let mut args = vec!["commit", "--quiet", "-m", message];
|
let commit_file = self.dot_git_dir().join(*COMMIT_MESSAGE);
|
||||||
|
let commit_file_path = commit_file.to_string_lossy();
|
||||||
|
let mut args = vec![
|
||||||
|
"commit",
|
||||||
|
"--quiet",
|
||||||
|
"-F",
|
||||||
|
commit_file_path.as_ref(),
|
||||||
|
"--cleanup=strip",
|
||||||
|
];
|
||||||
let author = name_and_email.map(|(name, email)| format!("{name} <{email}>"));
|
let author = name_and_email.map(|(name, email)| format!("{name} <{email}>"));
|
||||||
if let Some(author) = author.as_deref() {
|
if let Some(author) = author.as_deref() {
|
||||||
args.push("--author");
|
args.push("--author");
|
||||||
|
@ -450,7 +458,7 @@ impl GitRepository for FakeGitRepository {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&self, _message: &str, _name_and_email: Option<(&str, &str)>) -> Result<()> {
|
fn commit(&self, _name_and_email: Option<(&str, &str)>) -> Result<()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ editor.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
git.workspace = true
|
git.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
language.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
rpc.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
|
|
|
@ -3,20 +3,24 @@ use crate::repository_selector::RepositorySelectorPopoverMenu;
|
||||||
use crate::{
|
use crate::{
|
||||||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{Context as _, Result};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::actions::MoveToEnd;
|
use editor::actions::MoveToEnd;
|
||||||
use editor::scroll::ScrollbarAutoHide;
|
use editor::scroll::ScrollbarAutoHide;
|
||||||
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use futures::StreamExt as _;
|
use futures::{SinkExt, StreamExt as _};
|
||||||
use git::repository::RepoPath;
|
use git::repository::RepoPath;
|
||||||
use git::status::FileStatus;
|
use git::status::FileStatus;
|
||||||
use git::{CommitAllChanges, CommitChanges, RevertAll, StageAll, ToggleStaged, UnstageAll};
|
use git::{
|
||||||
|
CommitAllChanges, CommitChanges, RevertAll, StageAll, ToggleStaged, UnstageAll, COMMIT_MESSAGE,
|
||||||
|
};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
|
use language::{Buffer, BufferId};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
use project::git::RepositoryHandle;
|
use project::git::{GitRepo, RepositoryHandle};
|
||||||
use project::{Fs, Project, ProjectPath};
|
use project::{CreateOptions, Fs, Project, ProjectPath};
|
||||||
|
use rpc::proto;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::{collections::HashSet, ops::Range, path::PathBuf, sync::Arc, time::Duration, usize};
|
use std::{collections::HashSet, ops::Range, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||||
|
@ -30,7 +34,7 @@ use workspace::notifications::{DetachAndPromptErr, NotificationId};
|
||||||
use workspace::Toast;
|
use workspace::Toast;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
Workspace,
|
Item, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
|
@ -101,10 +105,69 @@ pub struct GitPanel {
|
||||||
all_staged: Option<bool>,
|
all_staged: Option<bool>,
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
err_sender: mpsc::Sender<anyhow::Error>,
|
err_sender: mpsc::Sender<anyhow::Error>,
|
||||||
|
commit_task: Task<()>,
|
||||||
|
commit_pending: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_message_buffer(
|
||||||
|
project: &Entity<Project>,
|
||||||
|
active_repository: &RepositoryHandle,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<Entity<Buffer>>> {
|
||||||
|
match &active_repository.git_repo {
|
||||||
|
GitRepo::Local(repo) => {
|
||||||
|
let commit_message_file = repo.dot_git_dir().join(*COMMIT_MESSAGE);
|
||||||
|
let fs = project.read(cx).fs().clone();
|
||||||
|
let project = project.downgrade();
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
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 buffer = project
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.open_local_buffer(&commit_message_file, cx)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!("opening commit message buffer at {commit_message_file:?}",)
|
||||||
|
})?;
|
||||||
|
Ok(buffer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
GitRepo::Remote {
|
||||||
|
project_id,
|
||||||
|
client,
|
||||||
|
worktree_id,
|
||||||
|
work_directory_id,
|
||||||
|
} => {
|
||||||
|
let request = client.request(proto::OpenCommitMessageBuffer {
|
||||||
|
project_id: project_id.0,
|
||||||
|
worktree_id: worktree_id.to_proto(),
|
||||||
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
|
});
|
||||||
|
let project = project.downgrade();
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
let response = request.await.context("requesting to open commit buffer")?;
|
||||||
|
let buffer_id = BufferId::new(response.buffer_id)?;
|
||||||
|
let buffer = project
|
||||||
|
.update(&mut cx, {
|
||||||
|
|project, cx| project.wait_for_remote_buffer(buffer_id, cx)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
Ok(buffer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_message_editor(
|
fn commit_message_editor(
|
||||||
active_repository: Option<&RepositoryHandle>,
|
commit_message_buffer: Option<Entity<Buffer>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<'_, Editor>,
|
cx: &mut Context<'_, Editor>,
|
||||||
) -> Editor {
|
) -> Editor {
|
||||||
|
@ -121,8 +184,8 @@ fn commit_message_editor(
|
||||||
};
|
};
|
||||||
text_style.refine(&refinement);
|
text_style.refine(&refinement);
|
||||||
|
|
||||||
let mut commit_editor = if let Some(active_repository) = active_repository.as_ref() {
|
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(active_repository.commit_message(), cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
|
||||||
Editor::new(
|
Editor::new(
|
||||||
EditorMode::AutoHeight { max_lines: 10 },
|
EditorMode::AutoHeight { max_lines: 10 },
|
||||||
buffer,
|
buffer,
|
||||||
|
@ -148,12 +211,31 @@ impl GitPanel {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
cx: AsyncWindowContext,
|
cx: AsyncWindowContext,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
) -> Task<Result<Entity<Self>>> {
|
||||||
cx.spawn(|mut cx| async move { workspace.update_in(&mut cx, Self::new) })
|
cx.spawn(|mut cx| async move {
|
||||||
|
let commit_message_buffer = workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let project = workspace.project();
|
||||||
|
let active_repository = project.read(cx).active_repository(cx);
|
||||||
|
active_repository
|
||||||
|
.map(|active_repository| commit_message_buffer(project, &active_repository, cx))
|
||||||
|
})?;
|
||||||
|
let commit_message_buffer = match commit_message_buffer {
|
||||||
|
Some(commit_message_buffer) => Some(
|
||||||
|
commit_message_buffer
|
||||||
|
.await
|
||||||
|
.context("opening commit buffer")?,
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||||
|
Self::new(workspace, window, commit_message_buffer, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
commit_message_buffer: Option<Entity<Buffer>>,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
let fs = workspace.app_state().fs.clone();
|
let fs = workspace.app_state().fs.clone();
|
||||||
|
@ -172,7 +254,10 @@ impl GitPanel {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let commit_editor =
|
let commit_editor =
|
||||||
cx.new(|cx| commit_message_editor(active_repository.as_ref(), window, cx));
|
cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
|
||||||
|
commit_editor.update(cx, |editor, cx| {
|
||||||
|
editor.clear(window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
let scroll_handle = UniformListScrollHandle::new();
|
let scroll_handle = UniformListScrollHandle::new();
|
||||||
|
|
||||||
|
@ -207,6 +292,8 @@ impl GitPanel {
|
||||||
show_scrollbar: false,
|
show_scrollbar: false,
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
update_visible_entries_task: Task::ready(()),
|
update_visible_entries_task: Task::ready(()),
|
||||||
|
commit_task: Task::ready(()),
|
||||||
|
commit_pending: false,
|
||||||
active_repository,
|
active_repository,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
fs,
|
fs,
|
||||||
|
@ -586,16 +673,49 @@ impl GitPanel {
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &git::CommitChanges,
|
_: &git::CommitChanges,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
_window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(active_repository) = self.active_repository.as_ref() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !active_repository.can_commit(false, cx) {
|
if !active_repository.can_commit(false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
active_repository.commit(name_and_email, self.err_sender.clone(), cx);
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.commit_pending = true;
|
||||||
|
let save_task = self.commit_editor.update(cx, |editor, cx| {
|
||||||
|
editor.save(false, self.project.clone(), window, cx)
|
||||||
|
});
|
||||||
|
let mut err_sender = self.err_sender.clone();
|
||||||
|
let commit_editor = self.commit_editor.clone();
|
||||||
|
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move {
|
||||||
|
match save_task.await {
|
||||||
|
Ok(()) => {
|
||||||
|
if let Some(Ok(())) = cx
|
||||||
|
.update(|_, cx| {
|
||||||
|
active_repository.commit(name_and_email, err_sender.clone(), cx)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
{
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
commit_editor.update(cx, |editor, cx| editor.clear(window, cx));
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
err_sender.send(e).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
git_panel
|
||||||
|
.update(&mut cx, |git_panel, _| {
|
||||||
|
git_panel.commit_pending = false;
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit all changes, regardless of whether they are staged or not
|
/// Commit all changes, regardless of whether they are staged or not
|
||||||
|
@ -603,16 +723,49 @@ impl GitPanel {
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &git::CommitAllChanges,
|
_: &git::CommitAllChanges,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
_window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(active_repository) = self.active_repository.as_ref() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !active_repository.can_commit(true, cx) {
|
if !active_repository.can_commit(true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
active_repository.commit_all(name_and_email, self.err_sender.clone(), cx);
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.commit_pending = true;
|
||||||
|
let save_task = self.commit_editor.update(cx, |editor, cx| {
|
||||||
|
editor.save(false, self.project.clone(), window, cx)
|
||||||
|
});
|
||||||
|
let mut err_sender = self.err_sender.clone();
|
||||||
|
let commit_editor = self.commit_editor.clone();
|
||||||
|
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move {
|
||||||
|
match save_task.await {
|
||||||
|
Ok(()) => {
|
||||||
|
if let Some(Ok(())) = cx
|
||||||
|
.update(|_, cx| {
|
||||||
|
active_repository.commit_all(name_and_email, err_sender.clone(), cx)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
{
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
commit_editor.update(cx, |editor, cx| editor.clear(window, cx));
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
err_sender.send(e).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
git_panel
|
||||||
|
.update(&mut cx, |git_panel, _| {
|
||||||
|
git_panel.commit_pending = false;
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
|
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
@ -714,17 +867,40 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule_update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn schedule_update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let project = self.project.clone();
|
||||||
let handle = cx.entity().downgrade();
|
let handle = cx.entity().downgrade();
|
||||||
self.update_visible_entries_task = cx.spawn_in(window, |_, mut cx| async move {
|
self.update_visible_entries_task = cx.spawn_in(window, |_, mut cx| async move {
|
||||||
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
|
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
|
||||||
if let Some(this) = handle.upgrade() {
|
if let Some(git_panel) = handle.upgrade() {
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
let Ok(commit_message_buffer) = git_panel.update_in(&mut cx, |git_panel, _, cx| {
|
||||||
this.update_visible_entries(cx);
|
git_panel
|
||||||
let active_repository = this.active_repository.as_ref();
|
.active_repository
|
||||||
this.commit_editor =
|
.as_ref()
|
||||||
cx.new(|cx| commit_message_editor(active_repository, window, cx));
|
.map(|active_repository| {
|
||||||
})
|
commit_message_buffer(&project, active_repository, cx)
|
||||||
.ok();
|
})
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let commit_message_buffer = match commit_message_buffer {
|
||||||
|
Some(commit_message_buffer) => match commit_message_buffer
|
||||||
|
.await
|
||||||
|
.context("opening commit buffer on repo update")
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
Some(buffer) => Some(buffer),
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
git_panel
|
||||||
|
.update_in(&mut cx, |git_panel, window, cx| {
|
||||||
|
git_panel.update_visible_entries(cx);
|
||||||
|
git_panel.commit_editor =
|
||||||
|
cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -963,14 +1139,15 @@ impl GitPanel {
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let editor = self.commit_editor.clone();
|
let editor = self.commit_editor.clone();
|
||||||
|
let can_commit = can_commit && !editor.read(cx).is_empty(cx);
|
||||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||||
let (can_commit, can_commit_all) =
|
let (can_commit, can_commit_all) =
|
||||||
self.active_repository
|
self.active_repository
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or((false, false), |active_repository| {
|
.map_or((false, false), |active_repository| {
|
||||||
(
|
(
|
||||||
can_commit && active_repository.can_commit(false, cx),
|
can_commit && active_repository.can_commit(false),
|
||||||
can_commit && active_repository.can_commit(true, cx),
|
can_commit && active_repository.can_commit(true),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1306,6 +1483,7 @@ impl Render for GitPanel {
|
||||||
}
|
}
|
||||||
None => (has_write_access, None),
|
None => (has_write_access, None),
|
||||||
};
|
};
|
||||||
|
let can_commit = !self.commit_pending && can_commit;
|
||||||
|
|
||||||
let has_co_authors = can_commit
|
let has_co_authors = can_commit
|
||||||
&& has_write_access
|
&& has_write_access
|
||||||
|
|
|
@ -8,14 +8,10 @@ use git::{
|
||||||
repository::{GitRepository, RepoPath},
|
repository::{GitRepository, RepoPath},
|
||||||
status::{GitSummary, TrackedSummary},
|
status::{GitSummary, TrackedSummary},
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{App, Context, Entity, EventEmitter, SharedString, Subscription, WeakEntity};
|
||||||
App, AppContext as _, Context, Entity, EventEmitter, SharedString, Subscription, WeakEntity,
|
|
||||||
};
|
|
||||||
use language::{Buffer, LanguageRegistry};
|
|
||||||
use rpc::{proto, AnyProtoClient};
|
use rpc::{proto, AnyProtoClient};
|
||||||
use settings::WorktreeId;
|
use settings::WorktreeId;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use text::Rope;
|
|
||||||
use util::maybe;
|
use util::maybe;
|
||||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
|
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
|
||||||
|
|
||||||
|
@ -25,7 +21,6 @@ pub struct GitState {
|
||||||
repositories: Vec<RepositoryHandle>,
|
repositories: Vec<RepositoryHandle>,
|
||||||
active_index: Option<usize>,
|
active_index: Option<usize>,
|
||||||
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
||||||
languages: Arc<LanguageRegistry>,
|
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +29,12 @@ pub struct RepositoryHandle {
|
||||||
git_state: WeakEntity<GitState>,
|
git_state: WeakEntity<GitState>,
|
||||||
pub worktree_id: WorktreeId,
|
pub worktree_id: WorktreeId,
|
||||||
pub repository_entry: RepositoryEntry,
|
pub repository_entry: RepositoryEntry,
|
||||||
git_repo: Option<GitRepo>,
|
pub git_repo: GitRepo,
|
||||||
commit_message: Entity<Buffer>,
|
|
||||||
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
update_sender: mpsc::UnboundedSender<(Message, mpsc::Sender<anyhow::Error>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum GitRepo {
|
pub enum GitRepo {
|
||||||
Local(Arc<dyn GitRepository>),
|
Local(Arc<dyn GitRepository>),
|
||||||
Remote {
|
Remote {
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
|
@ -70,12 +64,10 @@ enum Message {
|
||||||
StageAndCommit {
|
StageAndCommit {
|
||||||
git_repo: GitRepo,
|
git_repo: GitRepo,
|
||||||
paths: Vec<RepoPath>,
|
paths: Vec<RepoPath>,
|
||||||
message: Rope,
|
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
},
|
},
|
||||||
Commit {
|
Commit {
|
||||||
git_repo: GitRepo,
|
git_repo: GitRepo,
|
||||||
message: Rope,
|
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
},
|
},
|
||||||
Stage(GitRepo, Vec<RepoPath>),
|
Stage(GitRepo, Vec<RepoPath>),
|
||||||
|
@ -91,7 +83,6 @@ impl EventEmitter<Event> for GitState {}
|
||||||
impl GitState {
|
impl GitState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
worktree_store: &Entity<WorktreeStore>,
|
worktree_store: &Entity<WorktreeStore>,
|
||||||
languages: Arc<LanguageRegistry>,
|
|
||||||
client: Option<AnyProtoClient>,
|
client: Option<AnyProtoClient>,
|
||||||
project_id: Option<ProjectId>,
|
project_id: Option<ProjectId>,
|
||||||
cx: &mut Context<'_, Self>,
|
cx: &mut Context<'_, Self>,
|
||||||
|
@ -100,150 +91,140 @@ impl GitState {
|
||||||
mpsc::unbounded::<(Message, mpsc::Sender<anyhow::Error>)>();
|
mpsc::unbounded::<(Message, mpsc::Sender<anyhow::Error>)>();
|
||||||
cx.spawn(|_, cx| async move {
|
cx.spawn(|_, cx| async move {
|
||||||
while let Some((msg, mut err_sender)) = update_receiver.next().await {
|
while let Some((msg, mut err_sender)) = update_receiver.next().await {
|
||||||
let result = cx
|
let result =
|
||||||
.background_executor()
|
cx.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
match msg {
|
match msg {
|
||||||
Message::StageAndCommit {
|
Message::StageAndCommit {
|
||||||
git_repo,
|
git_repo,
|
||||||
message,
|
name_and_email,
|
||||||
name_and_email,
|
paths,
|
||||||
paths,
|
} => {
|
||||||
} => {
|
match git_repo {
|
||||||
match git_repo {
|
GitRepo::Local(repo) => {
|
||||||
GitRepo::Local(repo) => {
|
repo.stage_paths(&paths)?;
|
||||||
repo.stage_paths(&paths)?;
|
repo.commit(name_and_email.as_ref().map(
|
||||||
repo.commit(
|
|(name, email)| (name.as_ref(), email.as_ref()),
|
||||||
&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,
|
||||||
|
} => {
|
||||||
|
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(())
|
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(())
|
Message::Stage(repo, paths) => {
|
||||||
}
|
match repo {
|
||||||
Message::Unstage(repo, paths) => {
|
GitRepo::Local(repo) => repo.stage_paths(&paths)?,
|
||||||
match repo {
|
GitRepo::Remote {
|
||||||
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
project_id,
|
||||||
GitRepo::Remote {
|
client,
|
||||||
project_id,
|
worktree_id,
|
||||||
client,
|
work_directory_id,
|
||||||
worktree_id,
|
} => {
|
||||||
work_directory_id,
|
client
|
||||||
} => {
|
.request(proto::Stage {
|
||||||
client
|
project_id: project_id.0,
|
||||||
.request(proto::Unstage {
|
worktree_id: worktree_id.to_proto(),
|
||||||
project_id: project_id.0,
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
worktree_id: worktree_id.to_proto(),
|
paths: paths
|
||||||
work_directory_id: work_directory_id.to_proto(),
|
.into_iter()
|
||||||
paths: paths
|
.map(|repo_path| repo_path.to_proto())
|
||||||
.into_iter()
|
.collect(),
|
||||||
.map(|repo_path| repo_path.to_proto())
|
})
|
||||||
.collect(),
|
.await
|
||||||
})
|
.context("sending stage request")?;
|
||||||
.await
|
}
|
||||||
.context("sending unstage request")?;
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
Message::Unstage(repo, paths) => {
|
||||||
}
|
match repo {
|
||||||
Message::Commit {
|
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
||||||
git_repo,
|
GitRepo::Remote {
|
||||||
message,
|
project_id,
|
||||||
name_and_email,
|
client,
|
||||||
} => {
|
worktree_id,
|
||||||
match git_repo {
|
work_directory_id,
|
||||||
GitRepo::Local(repo) => repo.commit(
|
} => {
|
||||||
&message.to_string(),
|
client
|
||||||
name_and_email
|
.request(proto::Unstage {
|
||||||
.as_ref()
|
project_id: project_id.0,
|
||||||
.map(|(name, email)| (name.as_ref(), email.as_ref())),
|
worktree_id: worktree_id.to_proto(),
|
||||||
)?,
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
GitRepo::Remote {
|
paths: paths
|
||||||
project_id,
|
.into_iter()
|
||||||
client,
|
.map(|repo_path| repo_path.to_proto())
|
||||||
worktree_id,
|
.collect(),
|
||||||
work_directory_id,
|
})
|
||||||
} => {
|
.await
|
||||||
let (name, email) = name_and_email.unzip();
|
.context("sending unstage request")?;
|
||||||
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")?;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if let Err(e) = result {
|
||||||
err_sender.send(e).await.ok();
|
err_sender.send(e).await.ok();
|
||||||
}
|
}
|
||||||
|
@ -255,7 +236,6 @@ impl GitState {
|
||||||
|
|
||||||
GitState {
|
GitState {
|
||||||
project_id,
|
project_id,
|
||||||
languages,
|
|
||||||
client,
|
client,
|
||||||
repositories: Vec::new(),
|
repositories: Vec::new(),
|
||||||
active_index: None,
|
active_index: None,
|
||||||
|
@ -285,7 +265,7 @@ impl GitState {
|
||||||
|
|
||||||
worktree_store.update(cx, |worktree_store, cx| {
|
worktree_store.update(cx, |worktree_store, cx| {
|
||||||
for worktree in worktree_store.worktrees() {
|
for worktree in worktree_store.worktrees() {
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, _| {
|
||||||
let snapshot = worktree.snapshot();
|
let snapshot = worktree.snapshot();
|
||||||
for repo in snapshot.repositories().iter() {
|
for repo in snapshot.repositories().iter() {
|
||||||
let git_repo = worktree
|
let git_repo = worktree
|
||||||
|
@ -303,6 +283,9 @@ impl GitState {
|
||||||
work_directory_id: repo.work_directory_id(),
|
work_directory_id: repo.work_directory_id(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
let Some(git_repo) = git_repo else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let existing = self
|
let existing = self
|
||||||
.repositories
|
.repositories
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -317,25 +300,11 @@ impl GitState {
|
||||||
existing_handle.repository_entry = repo.clone();
|
existing_handle.repository_entry = repo.clone();
|
||||||
existing_handle
|
existing_handle
|
||||||
} else {
|
} 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 {
|
RepositoryHandle {
|
||||||
git_state: this.clone(),
|
git_state: this.clone(),
|
||||||
worktree_id: worktree.id(),
|
worktree_id: worktree.id(),
|
||||||
repository_entry: repo.clone(),
|
repository_entry: repo.clone(),
|
||||||
git_repo,
|
git_repo,
|
||||||
commit_message,
|
|
||||||
update_sender: self.update_sender.clone(),
|
update_sender: self.update_sender.clone(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -403,10 +372,6 @@ impl RepositoryHandle {
|
||||||
Some((self.worktree_id, path).into())
|
Some((self.worktree_id, path).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit_message(&self) -> Entity<Buffer> {
|
|
||||||
self.commit_message.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stage_entries(
|
pub fn stage_entries(
|
||||||
&self,
|
&self,
|
||||||
entries: Vec<RepoPath>,
|
entries: Vec<RepoPath>,
|
||||||
|
@ -415,11 +380,8 @@ impl RepositoryHandle {
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Some(git_repo) = self.git_repo.clone() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
self.update_sender
|
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"))?;
|
.map_err(|_| anyhow!("Failed to submit stage operation"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -432,11 +394,8 @@ impl RepositoryHandle {
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Some(git_repo) = self.git_repo.clone() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
self.update_sender
|
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"))?;
|
.map_err(|_| anyhow!("Failed to submit unstage operation"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -477,14 +436,8 @@ impl RepositoryHandle {
|
||||||
self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
|
self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_commit(&self, commit_all: bool, cx: &App) -> bool {
|
pub fn can_commit(&self, commit_all: bool) -> bool {
|
||||||
return self
|
return self.have_changes() && (commit_all || self.have_staged_changes());
|
||||||
.commit_message
|
|
||||||
.read(cx)
|
|
||||||
.chars()
|
|
||||||
.any(|c| !c.is_ascii_whitespace())
|
|
||||||
&& self.have_changes()
|
|
||||||
&& (commit_all || self.have_staged_changes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(
|
pub fn commit(
|
||||||
|
@ -492,15 +445,10 @@ impl RepositoryHandle {
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
mut err_sender: mpsc::Sender<anyhow::Error>,
|
mut err_sender: mpsc::Sender<anyhow::Error>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) -> anyhow::Result<()> {
|
||||||
let Some(git_repo) = self.git_repo.clone() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let message = self.commit_message.read(cx).as_rope().clone();
|
|
||||||
let result = self.update_sender.unbounded_send((
|
let result = self.update_sender.unbounded_send((
|
||||||
Message::Commit {
|
Message::Commit {
|
||||||
git_repo,
|
git_repo: self.git_repo.clone(),
|
||||||
message,
|
|
||||||
name_and_email,
|
name_and_email,
|
||||||
},
|
},
|
||||||
err_sender.clone(),
|
err_sender.clone(),
|
||||||
|
@ -513,32 +461,10 @@ impl RepositoryHandle {
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.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(
|
pub fn commit_all(
|
||||||
|
@ -546,22 +472,17 @@ impl RepositoryHandle {
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
mut err_sender: mpsc::Sender<anyhow::Error>,
|
mut err_sender: mpsc::Sender<anyhow::Error>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) -> anyhow::Result<()> {
|
||||||
let Some(git_repo) = self.git_repo.clone() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let to_stage = self
|
let to_stage = self
|
||||||
.repository_entry
|
.repository_entry
|
||||||
.status()
|
.status()
|
||||||
.filter(|entry| !entry.status.is_staged().unwrap_or(false))
|
.filter(|entry| !entry.status.is_staged().unwrap_or(false))
|
||||||
.map(|entry| entry.repo_path.clone())
|
.map(|entry| entry.repo_path.clone())
|
||||||
.collect();
|
.collect();
|
||||||
let message = self.commit_message.read(cx).as_rope().clone();
|
|
||||||
let result = self.update_sender.unbounded_send((
|
let result = self.update_sender.unbounded_send((
|
||||||
Message::StageAndCommit {
|
Message::StageAndCommit {
|
||||||
git_repo,
|
git_repo: self.git_repo.clone(),
|
||||||
paths: to_stage,
|
paths: to_stage,
|
||||||
message,
|
|
||||||
name_and_email,
|
name_and_email,
|
||||||
},
|
},
|
||||||
err_sender.clone(),
|
err_sender.clone(),
|
||||||
|
@ -574,10 +495,9 @@ impl RepositoryHandle {
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.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,
|
blame::Blame,
|
||||||
repository::{Branch, GitRepository, RepoPath},
|
repository::{Branch, GitRepository, RepoPath},
|
||||||
status::FileStatus,
|
status::FileStatus,
|
||||||
|
COMMIT_MESSAGE,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyEntity, App, AppContext as _, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter,
|
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_stage);
|
||||||
client.add_model_request_handler(Self::handle_unstage);
|
client.add_model_request_handler(Self::handle_unstage);
|
||||||
client.add_model_request_handler(Self::handle_commit);
|
client.add_model_request_handler(Self::handle_commit);
|
||||||
|
client.add_model_request_handler(Self::handle_open_commit_message_buffer);
|
||||||
|
|
||||||
WorktreeStore::init(&client);
|
WorktreeStore::init(&client);
|
||||||
BufferStore::init(&client);
|
BufferStore::init(&client);
|
||||||
|
@ -699,9 +701,7 @@ impl Project {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let git_state = Some(
|
let git_state = Some(cx.new(|cx| GitState::new(&worktree_store, None, None, cx)));
|
||||||
cx.new(|cx| GitState::new(&worktree_store, languages.clone(), None, None, cx)),
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||||
|
|
||||||
|
@ -824,7 +824,6 @@ impl Project {
|
||||||
let git_state = Some(cx.new(|cx| {
|
let git_state = Some(cx.new(|cx| {
|
||||||
GitState::new(
|
GitState::new(
|
||||||
&worktree_store,
|
&worktree_store,
|
||||||
languages.clone(),
|
|
||||||
Some(ssh_proto.clone()),
|
Some(ssh_proto.clone()),
|
||||||
Some(ProjectId(SSH_PROJECT_ID)),
|
Some(ProjectId(SSH_PROJECT_ID)),
|
||||||
cx,
|
cx,
|
||||||
|
@ -1030,7 +1029,6 @@ impl Project {
|
||||||
let git_state = Some(cx.new(|cx| {
|
let git_state = Some(cx.new(|cx| {
|
||||||
GitState::new(
|
GitState::new(
|
||||||
&worktree_store,
|
&worktree_store,
|
||||||
languages.clone(),
|
|
||||||
Some(client.clone().into()),
|
Some(client.clone().into()),
|
||||||
Some(ProjectId(remote_id)),
|
Some(ProjectId(remote_id)),
|
||||||
cx,
|
cx,
|
||||||
|
@ -3974,21 +3972,8 @@ impl Project {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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 =
|
||||||
let repository_handle = project
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
.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 entries = envelope
|
let entries = envelope
|
||||||
.payload
|
.payload
|
||||||
|
@ -4015,21 +4000,8 @@ impl Project {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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 =
|
||||||
let repository_handle = project
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
.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 entries = envelope
|
let entries = envelope
|
||||||
.payload
|
.payload
|
||||||
|
@ -4056,7 +4028,93 @@ impl Project {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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
|
let repository_handle = project
|
||||||
.git_state()
|
.git_state()
|
||||||
.context("missing git state")?
|
.context("missing git state")?
|
||||||
|
@ -4070,20 +4128,7 @@ impl Project {
|
||||||
})
|
})
|
||||||
.context("missing repository handle")?;
|
.context("missing repository handle")?;
|
||||||
anyhow::Ok(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(
|
fn respond_to_open_buffer_request(
|
||||||
|
@ -4122,7 +4167,7 @@ impl Project {
|
||||||
buffer.read(cx).remote_id()
|
buffer.read(cx).remote_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_for_remote_buffer(
|
pub fn wait_for_remote_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: BufferId,
|
id: BufferId,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -311,7 +311,8 @@ message Envelope {
|
||||||
|
|
||||||
Stage stage = 293;
|
Stage stage = 293;
|
||||||
Unstage unstage = 294;
|
Unstage unstage = 294;
|
||||||
Commit commit = 295; // current max
|
Commit commit = 295;
|
||||||
|
OpenCommitMessageBuffer open_commit_message_buffer = 296; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
@ -2655,7 +2656,12 @@ message Commit {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 worktree_id = 2;
|
||||||
uint64 work_directory_id = 3;
|
uint64 work_directory_id = 3;
|
||||||
string message = 4;
|
optional string name = 4;
|
||||||
optional string name = 5;
|
optional string email = 5;
|
||||||
optional string email = 6;
|
}
|
||||||
|
|
||||||
|
message OpenCommitMessageBuffer {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
uint64 work_directory_id = 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,6 +249,7 @@ messages!(
|
||||||
(OpenBufferForSymbol, Background),
|
(OpenBufferForSymbol, Background),
|
||||||
(OpenBufferForSymbolResponse, Background),
|
(OpenBufferForSymbolResponse, Background),
|
||||||
(OpenBufferResponse, Background),
|
(OpenBufferResponse, Background),
|
||||||
|
(OpenCommitMessageBuffer, Background),
|
||||||
(PerformRename, Background),
|
(PerformRename, Background),
|
||||||
(PerformRenameResponse, Background),
|
(PerformRenameResponse, Background),
|
||||||
(Ping, Foreground),
|
(Ping, Foreground),
|
||||||
|
@ -443,6 +444,7 @@ request_messages!(
|
||||||
(OpenBufferById, OpenBufferResponse),
|
(OpenBufferById, OpenBufferResponse),
|
||||||
(OpenBufferByPath, OpenBufferResponse),
|
(OpenBufferByPath, OpenBufferResponse),
|
||||||
(OpenBufferForSymbol, OpenBufferForSymbolResponse),
|
(OpenBufferForSymbol, OpenBufferForSymbolResponse),
|
||||||
|
(OpenCommitMessageBuffer, OpenBufferResponse),
|
||||||
(OpenNewBuffer, OpenBufferResponse),
|
(OpenNewBuffer, OpenBufferResponse),
|
||||||
(PerformRename, PerformRenameResponse),
|
(PerformRename, PerformRenameResponse),
|
||||||
(Ping, Ack),
|
(Ping, Ack),
|
||||||
|
@ -554,6 +556,7 @@ entity_messages!(
|
||||||
OpenBufferById,
|
OpenBufferById,
|
||||||
OpenBufferByPath,
|
OpenBufferByPath,
|
||||||
OpenBufferForSymbol,
|
OpenBufferForSymbol,
|
||||||
|
OpenCommitMessageBuffer,
|
||||||
PerformRename,
|
PerformRename,
|
||||||
PrepareRename,
|
PrepareRename,
|
||||||
RefreshInlayHints,
|
RefreshInlayHints,
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use extension::ExtensionHostProxy;
|
use extension::ExtensionHostProxy;
|
||||||
use extension_host::headless_host::HeadlessExtensionStore;
|
use extension_host::headless_host::HeadlessExtensionStore;
|
||||||
use fs::Fs;
|
use fs::{CreateOptions, Fs};
|
||||||
use futures::channel::mpsc;
|
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 gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel, SharedString};
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
|
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use project::{
|
use project::{
|
||||||
buffer_store::{BufferStore, BufferStoreEvent},
|
buffer_store::{BufferStore, BufferStoreEvent},
|
||||||
git::GitState,
|
git::{GitRepo, GitState, RepositoryHandle},
|
||||||
project_settings::SettingsObserver,
|
project_settings::SettingsObserver,
|
||||||
search::SearchQuery,
|
search::SearchQuery,
|
||||||
task_store::TaskStore,
|
task_store::TaskStore,
|
||||||
|
@ -83,8 +83,7 @@ impl HeadlessProject {
|
||||||
store
|
store
|
||||||
});
|
});
|
||||||
|
|
||||||
let git_state =
|
let git_state = cx.new(|cx| GitState::new(&worktree_store, None, None, cx));
|
||||||
cx.new(|cx| GitState::new(&worktree_store, languages.clone(), None, None, cx));
|
|
||||||
|
|
||||||
let buffer_store = cx.new(|cx| {
|
let buffer_store = cx.new(|cx| {
|
||||||
let mut buffer_store = BufferStore::local(worktree_store.clone(), 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_stage);
|
||||||
client.add_model_request_handler(Self::handle_unstage);
|
client.add_model_request_handler(Self::handle_unstage);
|
||||||
client.add_model_request_handler(Self::handle_commit);
|
client.add_model_request_handler(Self::handle_commit);
|
||||||
|
client.add_model_request_handler(Self::handle_open_commit_message_buffer);
|
||||||
|
|
||||||
client.add_request_handler(
|
client.add_request_handler(
|
||||||
extensions.clone().downgrade(),
|
extensions.clone().downgrade(),
|
||||||
|
@ -625,20 +625,8 @@ impl HeadlessProject {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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 =
|
||||||
let repository_handle = project
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
.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 entries = envelope
|
let entries = envelope
|
||||||
.payload
|
.payload
|
||||||
|
@ -665,20 +653,8 @@ impl HeadlessProject {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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 =
|
||||||
let repository_handle = project
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
.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 entries = envelope
|
let entries = envelope
|
||||||
.payload
|
.payload
|
||||||
|
@ -705,7 +681,106 @@ impl HeadlessProject {
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_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
|
let repository_handle = project
|
||||||
.git_state
|
.git_state
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -718,20 +793,7 @@ impl HeadlessProject {
|
||||||
})
|
})
|
||||||
.context("missing repository handle")?;
|
.context("missing repository handle")?;
|
||||||
anyhow::Ok(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