|
1 | | -use std::{io::Write, process::Stdio}; |
| 1 | +use std::{ |
| 2 | + borrow::Cow, |
| 3 | + io::{Read, Write}, |
| 4 | + process::Stdio, |
| 5 | +}; |
2 | 6 |
|
3 | 7 | use anyhow::{anyhow, bail, Context, Result}; |
| 8 | +use gix::{ |
| 9 | + bstr::{BStr, BString}, |
| 10 | + objs::commit::SIGNATURE_FIELD_NAME, |
| 11 | +}; |
4 | 12 |
|
5 | 13 | /// Note that this is a quick implementation of commit signature verification that ignores a lot of what |
6 | 14 | /// git does and can do, while focussing on the gist of it. |
@@ -39,6 +47,61 @@ pub fn verify(repo: gix::Repository, rev_spec: Option<&str>) -> Result<()> { |
39 | 47 | Ok(()) |
40 | 48 | } |
41 | 49 |
|
| 50 | +/// Note that this is a quick first prototype that lacks some of the features provided by `git |
| 51 | +/// verify-commit`. |
| 52 | +pub fn sign(repo: gix::Repository, rev_spec: Option<&str>, mut out: impl std::io::Write) -> Result<()> { |
| 53 | + let rev_spec = rev_spec.unwrap_or("HEAD"); |
| 54 | + let object = repo |
| 55 | + .rev_parse_single(format!("{rev_spec}^{{commit}}").as_str())? |
| 56 | + .object()?; |
| 57 | + let mut commit_ref = object.to_commit_ref(); |
| 58 | + |
| 59 | + let mut cmd: std::process::Command = gix::command::prepare("gpg").into(); |
| 60 | + cmd.args([ |
| 61 | + "--keyid-format=long", |
| 62 | + "--status-fd=2", |
| 63 | + "--detach-sign", |
| 64 | + "--sign", |
| 65 | + "--armor", |
| 66 | + ]) |
| 67 | + .stdin(Stdio::piped()) |
| 68 | + .stdout(Stdio::piped()); |
| 69 | + gix::trace::debug!("About to execute {cmd:?}"); |
| 70 | + let mut child = cmd.spawn()?; |
| 71 | + child.stdin.take().expect("to be present").write_all(&object.data)?; |
| 72 | + |
| 73 | + if !child.wait()?.success() { |
| 74 | + bail!("Command {cmd:?} failed"); |
| 75 | + } |
| 76 | + |
| 77 | + let mut signed_data = Vec::new(); |
| 78 | + child |
| 79 | + .stdout |
| 80 | + .take() |
| 81 | + .expect("to be present") |
| 82 | + .read_to_end(&mut signed_data)?; |
| 83 | + |
| 84 | + let extra_header: Cow<'_, BStr> = Cow::Owned(BString::new(signed_data)); |
| 85 | + |
| 86 | + assert!( |
| 87 | + !commit_ref |
| 88 | + .extra_headers |
| 89 | + .iter() |
| 90 | + .any(|(header_name, _)| *header_name == BStr::new(SIGNATURE_FIELD_NAME)), |
| 91 | + "Commit is already signed, doing nothing" |
| 92 | + ); |
| 93 | + |
| 94 | + commit_ref |
| 95 | + .extra_headers |
| 96 | + .push((BStr::new(SIGNATURE_FIELD_NAME), extra_header)); |
| 97 | + |
| 98 | + let signed_id = repo.write_object(&commit_ref)?; |
| 99 | + |
| 100 | + writeln!(&mut out, "{signed_id}")?; |
| 101 | + |
| 102 | + Ok(()) |
| 103 | +} |
| 104 | + |
42 | 105 | pub fn describe( |
43 | 106 | mut repo: gix::Repository, |
44 | 107 | rev_spec: Option<&str>, |
|
0 commit comments