git: Handle git pre-commit hooks separately (#43285)

We now run git pre-commit hooks before we commit. This ensures we don't
run into timeout issues with askpass delegate and report invalid error
to the user.

Closes #43157

Release Notes:

- Fixed long running pre-commit hooks causing committing from Zed to
fail.

Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Jakub Konka
2025-11-22 00:33:32 +01:00
committed by GitHub
co-authored by Cole Miller
parent 279b76d440
commit d07193cdf2
8 changed files with 128 additions and 5 deletions
+49 -2
View File
@@ -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<Self>,
envelope: TypedEnvelope<proto::RunGitHook>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
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<Self>,
envelope: TypedEnvelope<proto::Commit>,
@@ -4262,19 +4279,49 @@ impl Repository {
})
}
pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver<Result<()>> {
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<Result<()>> {
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,