diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index f73631bb19c80a463ed38b78031dd0fe4d452681..a3bd93db5fc177a60bb450b469335dc6b9e6ce3d 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -453,6 +453,7 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 97cd13d185817453c369356bdc60cbc1517bf1e1..c9a41243aa641318026db208d78a64429cfeb1ab 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, bail}; use collections::{HashMap, HashSet}; use futures::future::{self, BoxFuture, join_all}; use git::{ - Oid, + Oid, RunHook, blame::Blame, repository::{ AskPassDelegate, Branch, CommitDetails, CommitOptions, FetchOptions, GitRepository, @@ -532,6 +532,14 @@ impl GitRepository for FakeGitRepository { unimplemented!() } + fn run_hook( + &self, + _hook: RunHook, + _env: Arc>, + ) -> BoxFuture<'_, Result<()>> { + unimplemented!() + } + fn push( &self, _branch: String, diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index 50a1e1234ba3caeff729d37b6fa3022336b54e96..4dc2f0a8a93cec82da4df4d3b4431dbf6f4d3862 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -225,3 +225,28 @@ impl From for usize { u64::from_ne_bytes(u64_bytes) as usize } } + +#[repr(i32)] +#[derive(Copy, Clone, Debug)] +pub enum RunHook { + PreCommit, +} + +impl RunHook { + pub fn as_str(&self) -> &str { + match self { + Self::PreCommit => "pre-commit", + } + } + + pub fn to_proto(&self) -> i32 { + *self as i32 + } + + pub fn from_proto(value: i32) -> Option { + match value { + 0 => Some(Self::PreCommit), + _ => None, + } + } +} diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 2c9189962492daa75dba86e9e2ebd247ad85254e..9beb3d838382d9267afdb081211647139f85b75e 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1,7 +1,7 @@ use crate::commit::parse_git_diff_name_status; use crate::stash::GitStash; use crate::status::{DiffTreeType, GitStatus, StatusCode, TreeDiff}; -use crate::{Oid, SHORT_SHA_LENGTH}; +use crate::{Oid, RunHook, SHORT_SHA_LENGTH}; use anyhow::{Context as _, Result, anyhow, bail}; use collections::HashMap; use futures::future::BoxFuture; @@ -485,6 +485,12 @@ pub trait GitRepository: Send + Sync { env: Arc>, ) -> BoxFuture<'_, Result<()>>; + fn run_hook( + &self, + hook: RunHook, + env: Arc>, + ) -> BoxFuture<'_, Result<()>>; + fn commit( &self, message: SharedString, @@ -1643,6 +1649,7 @@ impl GitRepository for RealGitRepository { .args(["commit", "--quiet", "-m"]) .arg(&message.to_string()) .arg("--cleanup=strip") + .arg("--no-verify") .stdout(smol::process::Stdio::piped()) .stderr(smol::process::Stdio::piped()); @@ -2037,6 +2044,26 @@ impl GitRepository for RealGitRepository { }) .boxed() } + + fn run_hook( + &self, + hook: RunHook, + env: Arc>, + ) -> BoxFuture<'_, Result<()>> { + let working_directory = self.working_directory(); + let git_binary_path = self.any_git_binary_path.clone(); + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let git = GitBinary::new(git_binary_path, working_directory, executor) + .envs(HashMap::clone(&env)); + git.run(&["hook", "run", "--ignore-missing", hook.as_str()]) + .await?; + Ok(()) + }) + .boxed() + } } fn git_status_args(path_prefixes: &[RepoPath]) -> Vec { diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 40ef0daa29390e229ab03eb840c39900163d4b6a..bde9261fa28b8ed0d6c6a79fd02b90177e52a98e 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -25,7 +25,7 @@ use futures::{ stream::FuturesOrdered, }; use git::{ - BuildPermalinkParams, GitHostingProviderRegistry, Oid, + BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook, blame::Blame, parse_git_remote_url, repository::{ @@ -433,6 +433,7 @@ impl GitStore { client.add_entity_request_handler(Self::handle_stash_apply); client.add_entity_request_handler(Self::handle_stash_drop); client.add_entity_request_handler(Self::handle_commit); + client.add_entity_request_handler(Self::handle_run_hook); client.add_entity_request_handler(Self::handle_reset); client.add_entity_request_handler(Self::handle_show); client.add_entity_request_handler(Self::handle_load_commit_diff); @@ -1982,6 +1983,22 @@ impl GitStore { Ok(proto::Ack {}) } + async fn handle_run_hook( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let repository_id = RepositoryId::from_proto(envelope.payload.repository_id); + let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?; + let hook = RunHook::from_proto(envelope.payload.hook).context("invalid hook")?; + repository_handle + .update(&mut cx, |repository_handle, cx| { + repository_handle.run_hook(hook, cx) + })? + .await??; + Ok(proto::Ack {}) + } + async fn handle_commit( this: Entity, envelope: TypedEnvelope, @@ -4262,19 +4279,49 @@ impl Repository { }) } + pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver> { + let id = self.id; + self.send_job( + Some(format!("git hook {}", hook.as_str()).into()), + move |git_repo, _cx| async move { + match git_repo { + RepositoryState::Local { + backend, + environment, + } => backend.run_hook(hook, environment.clone()).await, + RepositoryState::Remote { project_id, client } => { + client + .request(proto::RunGitHook { + project_id: project_id.0, + repository_id: id.to_proto(), + hook: hook.to_proto(), + }) + .await?; + + Ok(()) + } + } + }, + ) + } + pub fn commit( &mut self, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, options: CommitOptions, askpass: AskPassDelegate, - _cx: &mut App, + cx: &mut App, ) -> oneshot::Receiver> { let id = self.id; let askpass_delegates = self.askpass_delegates.clone(); let askpass_id = util::post_inc(&mut self.latest_askpass_id); + let rx = self.run_hook(RunHook::PreCommit, cx); + self.send_job(Some("git commit".into()), move |git_repo, _cx| async move { + rx.await??; + match git_repo { RepositoryState::Local { backend, diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index efbd7f616f9e75c4e0409f4dc73c67f9eb1836e0..07fab2065b98994547121f19bab8f0fda50b2a59 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -531,3 +531,13 @@ message GitCreateWorktree { string directory = 4; optional string commit = 5; } + +message RunGitHook { + enum GitHook { + PRE_COMMIT = 0; + } + + uint64 project_id = 1; + uint64 repository_id = 2; + GitHook hook = 3; +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 6ecea916ca5143ecd75678cd2e21587087f67b51..7a2c86887fcd0ffa8f64e5362568b7bd0e12ec7b 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -437,7 +437,9 @@ message Envelope { OpenImageResponse open_image_response = 392; CreateImageForPeer create_image_for_peer = 393; - ExternalExtensionAgentsUpdated external_extension_agents_updated = 394; // current max + ExternalExtensionAgentsUpdated external_extension_agents_updated = 394; + + RunGitHook run_git_hook = 395; // current max } reserved 87 to 88; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index fa6af5c3899da3519ce13d772bdc61fb78194d19..bcffe5ae01616c469bde2e730feff3e8e777e572 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -49,6 +49,7 @@ messages!( (ChannelMessageUpdate, Foreground), (CloseBuffer, Foreground), (Commit, Background), + (RunGitHook, Background), (CopyProjectEntry, Foreground), (CreateBufferForPeer, Foreground), (CreateImageForPeer, Foreground), @@ -349,6 +350,7 @@ request_messages!( (Call, Ack), (CancelCall, Ack), (Commit, Ack), + (RunGitHook, Ack), (CopyProjectEntry, ProjectEntryResponse), (CreateChannel, CreateChannelResponse), (CreateProjectEntry, ProjectEntryResponse), @@ -547,6 +549,7 @@ entity_messages!( BufferSaved, CloseBuffer, Commit, + RunGitHook, GetColorPresentation, CopyProjectEntry, CreateBufferForPeer,