git: Intercept signing prompt from GPG when committing (#34096)
Closes #30111 - [x] basic implementation - [x] implementation for remote projects - [x] surface error output from GPG if signing fails - [ ] ~~Windows~~ Release Notes: - git: Passphrase prompts from GPG to unlock commit signing keys are now shown in Zed.
This commit is contained in:
parent
87362c602f
commit
842ac984d5
9 changed files with 193 additions and 51 deletions
|
@ -41,9 +41,9 @@ futures.workspace = true
|
|||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
tempfile.workspace = true
|
||||
|
|
|
@ -391,8 +391,12 @@ pub trait GitRepository: Send + Sync {
|
|||
message: SharedString,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
options: CommitOptions,
|
||||
askpass: AskPassDelegate,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
// This method takes an AsyncApp to ensure it's invoked on the main thread,
|
||||
// otherwise git-credentials-manager won't work.
|
||||
cx: AsyncApp,
|
||||
) -> BoxFuture<'static, Result<()>>;
|
||||
|
||||
fn push(
|
||||
&self,
|
||||
|
@ -1193,36 +1197,68 @@ impl GitRepository for RealGitRepository {
|
|||
message: SharedString,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
options: CommitOptions,
|
||||
ask_pass: AskPassDelegate,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
cx: AsyncApp,
|
||||
) -> BoxFuture<'static, Result<()>> {
|
||||
let working_directory = self.working_directory();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let mut cmd = new_smol_command("git");
|
||||
cmd.current_dir(&working_directory?)
|
||||
.envs(env.iter())
|
||||
.args(["commit", "--quiet", "-m"])
|
||||
.arg(&message.to_string())
|
||||
.arg("--cleanup=strip");
|
||||
let executor = cx.background_executor().clone();
|
||||
async move {
|
||||
let working_directory = working_directory?;
|
||||
let have_user_git_askpass = env.contains_key("GIT_ASKPASS");
|
||||
let mut command = new_smol_command("git");
|
||||
command.current_dir(&working_directory).envs(env.iter());
|
||||
|
||||
if options.amend {
|
||||
cmd.arg("--amend");
|
||||
}
|
||||
let ask_pass = if have_user_git_askpass {
|
||||
None
|
||||
} else {
|
||||
Some(AskPassSession::new(&executor, ask_pass).await?)
|
||||
};
|
||||
|
||||
if let Some((name, email)) = name_and_email {
|
||||
cmd.arg("--author").arg(&format!("{name} <{email}>"));
|
||||
}
|
||||
if let Some(program) = ask_pass
|
||||
.as_ref()
|
||||
.and_then(|ask_pass| ask_pass.gpg_script_path())
|
||||
{
|
||||
command.arg("-c").arg(format!(
|
||||
"gpg.program={}",
|
||||
program.as_ref().to_string_lossy()
|
||||
));
|
||||
}
|
||||
|
||||
let output = cmd.output().await?;
|
||||
command
|
||||
.args(["commit", "-m"])
|
||||
.arg(message.to_string())
|
||||
.arg("--cleanup=strip")
|
||||
.stdin(smol::process::Stdio::null())
|
||||
.stdout(smol::process::Stdio::piped())
|
||||
.stderr(smol::process::Stdio::piped());
|
||||
|
||||
if options.amend {
|
||||
command.arg("--amend");
|
||||
}
|
||||
|
||||
if let Some((name, email)) = name_and_email {
|
||||
command.arg("--author").arg(&format!("{name} <{email}>"));
|
||||
}
|
||||
|
||||
if let Some(ask_pass) = ask_pass {
|
||||
command.env("GIT_ASKPASS", ask_pass.script_path());
|
||||
let git_process = command.spawn()?;
|
||||
|
||||
run_askpass_command(ask_pass, git_process).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
let git_process = command.spawn()?;
|
||||
let output = git_process.output().await?;
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"Failed to commit:\n{}",
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn push(
|
||||
|
@ -2046,12 +2082,16 @@ mod tests {
|
|||
)
|
||||
.await
|
||||
.unwrap();
|
||||
repo.commit(
|
||||
"Initial commit".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
)
|
||||
cx.spawn(|cx| {
|
||||
repo.commit(
|
||||
"Initial commit".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
AskPassDelegate::new_always_failing(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -2075,12 +2115,16 @@ mod tests {
|
|||
)
|
||||
.await
|
||||
.unwrap();
|
||||
repo.commit(
|
||||
"Commit after checkpoint".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
)
|
||||
cx.spawn(|cx| {
|
||||
repo.commit(
|
||||
"Commit after checkpoint".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
AskPassDelegate::new_always_failing(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -2213,12 +2257,16 @@ mod tests {
|
|||
)
|
||||
.await
|
||||
.unwrap();
|
||||
repo.commit(
|
||||
"Initial commit".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
)
|
||||
cx.spawn(|cx| {
|
||||
repo.commit(
|
||||
"Initial commit".into(),
|
||||
None,
|
||||
CommitOptions::default(),
|
||||
AskPassDelegate::new_always_failing(),
|
||||
Arc::new(checkpoint_author_envs()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue