From e9cd4293a719d524f640f845292d06408ec14297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Tue, 7 Oct 2025 19:17:31 +0200 Subject: [PATCH 1/2] Use constant instead of hardcoded value --- gix-object/src/commit/ref_iter.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gix-object/src/commit/ref_iter.rs b/gix-object/src/commit/ref_iter.rs index df953c1da1e..6a7eb2c778a 100644 --- a/gix-object/src/commit/ref_iter.rs +++ b/gix-object/src/commit/ref_iter.rs @@ -11,9 +11,8 @@ use winnow::{ use crate::{ bstr::ByteSlice, - commit::{decode, SignedData}, - parse, - parse::NL, + commit::{decode, SignedData, SIGNATURE_FIELD_NAME}, + parse::{self, NL}, CommitRefIter, }; @@ -65,7 +64,7 @@ impl<'a> CommitRefIter<'a> { for token in raw_tokens { let token = token?; if let Token::ExtraHeader((name, value)) = &token.token { - if *name == "gpgsig" { + if *name == SIGNATURE_FIELD_NAME { // keep track of the signature range alongside the signature data, // because all but the signature is the signed data. signature_and_range = Some((value.clone(), token.token_range)); From aaeea746dcb479d5422cf618e783a7d0ad9bc575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Tue, 7 Oct 2025 19:18:14 +0200 Subject: [PATCH 2/2] Prototype `sign()` --- gitoxide-core/src/repository/commit.rs | 64 +++++++++++++++++++++++++- src/plumbing/main.rs | 11 +++++ src/plumbing/options/mod.rs | 5 ++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/gitoxide-core/src/repository/commit.rs b/gitoxide-core/src/repository/commit.rs index e7b920a357b..00e38a2357d 100644 --- a/gitoxide-core/src/repository/commit.rs +++ b/gitoxide-core/src/repository/commit.rs @@ -1,6 +1,14 @@ -use std::{io::Write, process::Stdio}; +use std::{ + borrow::Cow, + io::{Read, Write}, + process::Stdio, +}; use anyhow::{anyhow, bail, Context, Result}; +use gix::{ + bstr::{BStr, ByteSlice}, + objs::commit::SIGNATURE_FIELD_NAME, +}; /// Note that this is a quick implementation of commit signature verification that ignores a lot of what /// git does and can do, while focussing on the gist of it. @@ -39,6 +47,60 @@ pub fn verify(repo: gix::Repository, rev_spec: Option<&str>) -> Result<()> { Ok(()) } +pub fn sign(repo: gix::Repository, rev_spec: Option<&str>) -> Result<()> { + let rev_spec = rev_spec.unwrap_or("HEAD"); + let commit = repo + .rev_parse_single(format!("{rev_spec}^{{commit}}").as_str())? + .object()? + .into_commit(); + + let mut cmd: std::process::Command = gix::command::prepare("gpg").into(); + cmd.args([ + "--keyid-format=long", + "--status-fd=2", + "--detach-sign", + "--sign", + "--armor", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()); + gix::trace::debug!("About to execute {cmd:?}"); + let mut child = cmd.spawn()?; + child + .stdin + .take() + .expect("to be present") + .write_all(commit.data.as_ref())?; + + if !child.wait()?.success() { + bail!("Command {cmd:?} failed"); + } + + let mut signed_data = Vec::new(); + child + .stdout + .take() + .expect("to be present") + .read_to_end(&mut signed_data)?; + + let object = repo + .rev_parse_single(format!("{rev_spec}^{{commit}}").as_str())? + .object()?; + let mut commit_ref = object.to_commit_ref(); + + let extra_header: Cow<'_, BStr> = Cow::Borrowed(signed_data.as_bstr()); + + // TODO: + // What do we want to do when there is already a signature? + commit_ref + .extra_headers + .push((BStr::new(SIGNATURE_FIELD_NAME), extra_header)); + + eprintln!("{commit_ref:?}"); + + Ok(()) +} + pub fn describe( mut repo: gix::Repository, rev_spec: Option<&str>, diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 1d0bb96343c..e914340a1f8 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -1287,6 +1287,17 @@ pub fn main() -> Result<()> { core::repository::commit::verify(repository(Mode::Lenient)?, rev_spec.as_deref()) }, ), + commit::Subcommands::Sign { rev_spec } => prepare_and_run( + "commit-sign", + trace, + auto_verbose, + progress, + progress_keep_open, + None, + move |_progress, _out, _err| { + core::repository::commit::sign(repository(Mode::Lenient)?, rev_spec.as_deref()) + }, + ), commit::Subcommands::Describe { annotated_tags, all_refs, diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 597c7b7aaea..49bc178e4f9 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -912,6 +912,11 @@ pub mod commit { /// A specification of the revision to verify, or the current `HEAD` if unset. rev_spec: Option, }, + /// TODO: add description. + Sign { + /// A specification of the revision to sign, or the current `HEAD` if unset. + rev_spec: Option, + }, /// Describe the current commit or the given one using the name of the closest annotated tag in its ancestry. Describe { /// Use annotated tag references only, not all tags.