rust-lang/git2-rs

How to correctly create GPG-signed commits?

cole-h opened this issue ยท 3 comments

Following examples around the internet (and in this very issue tracker), I have created a repo, staged modified files, and committed these files. However, I have not found a way to commit with a valid GPG signature. Below is example code that exhibits this problem:

Example code

use std::fs;

use git2::Repository;
use gpgme::{Context, Protocol, SignMode};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    fs::create_dir_all("/tmp/git2").unwrap();
    let mut ctx = Context::from_protocol(Protocol::OpenPgp)?;

    let repo = Repository::init("/tmp/git2").unwrap();
    let mut index = repo.index()?;

    index.add_all(["."].iter(), git2::IndexAddOption::DEFAULT, None)?;
    index.write()?;

    let tree_id = repo.index()?.write_tree()?;
    let sig = repo.signature()?;
    let mut parents = Vec::new();

    if let Some(parent) = repo.head().ok().map(|h| h.target().unwrap()) {
        parents.push(repo.find_commit(parent)?);
    }

    let parents = parents.iter().collect::<Vec<_>>();

    // {{COMMITS TO HEAD}}
    // let ret = repo.commit(
    //     Some("HEAD"),
    //     &sig,
    //     &sig,
    //     "test",
    //     &repo.find_tree(tree_id)?,
    //     &parents,
    // )?;

    // {{DOESN'T COMMIT TO HEAD}}
    let buf =
        repo.commit_create_buffer(&sig, &sig, "test", &repo.find_tree(tree_id)?, &parents)?;
    let contents = std::str::from_utf8(&buf).unwrap().to_string();
    let mut outbuf = Vec::new();

    ctx.set_armor(true);
    ctx.sign(SignMode::Detached, buf.as_str().unwrap(), &mut outbuf)?;

    let out = std::str::from_utf8(&outbuf).unwrap();
    let ret = repo.commit_signed(&contents, &out, None)?;

    println!("{:?}", ret);
    Ok(())
}

If I comment out repo.commit() and uncomment the related code for repo.commit_signed(), it does create a commit, but does not update HEAD (is this as-intended in libgit2?).

Did I overlook a way to manually point HEAD to an Oid? Am I doing something wrong?

EDIT: I have to actually sign the contents with GPG. Duh.

I feel that this is a workaround and should be unnecessary, but I found a way to add the commit and have it show up in git log: get the Commit object from the Oid and then create master from that. Working example code looks like the following:

use std::fs;

use git2::Repository;
use gpgme::{Context, Protocol, SignMode};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    fs::create_dir_all("/tmp/git2").unwrap();
    let mut ctx = Context::from_protocol(Protocol::OpenPgp)?;

    let repo = Repository::init("/tmp/git2").unwrap();
    let mut index = repo.index()?;

    index.add_all(["."].iter(), git2::IndexAddOption::DEFAULT, None)?;
    index.write()?;

    let tree_id = repo.index()?.write_tree()?;
    let sig = repo.signature()?;
    let mut parents = Vec::new();

    if let Some(parent) = repo.head().ok().map(|h| h.target().unwrap()) {
        parents.push(repo.find_commit(parent)?);
    }

    let parents = parents.iter().collect::<Vec<_>>();
    let buf =
        repo.commit_create_buffer(&sig, &sig, "test", &repo.find_tree(tree_id)?, &parents)?;
    let contents = std::str::from_utf8(&buf).unwrap().to_string();
    let mut outbuf = Vec::new();

    ctx.set_armor(true);
    ctx.sign(SignMode::Detached, buf.as_str().unwrap(), &mut outbuf)?;

    let out = std::str::from_utf8(&outbuf).unwrap();
    let ret = repo.commit_signed(&contents, &out, None)?;
    let commit = repo.find_commit(ret)?;
    repo.branch("master", &commit, false)?; // :-)

    println!("{:?}", ret);
    Ok(())
}

@cole-h Sorry for bringing up an old issue, but were you able to resolve the issue of updating HEAD when using commit_signed? Like you, I do not see an option or mention of that in this crate's code. When you said "I have to actually sign the contents with GPG.", did doing so resolve HEAD updating? If so, do you have advice or an example of how to sign the contents properly? I am doing it similarly to your example above and I can see the commit created, but I guess I'm not sure if it is being created properly to update HEAD or if that is something I need to manage.

Basically, have a look at my commit function here: https://github.com/cole-h/passrs/blob/ad2da30c29b129bebb4e4de9c640633939893cf0/src/util.rs#L445. Specifically, what I did for GPG-signing commits is a little further down at https://github.com/cole-h/passrs/blob/ad2da30c29b129bebb4e4de9c640633939893cf0/src/util.rs#L502, and for updating the branch, I did https://github.com/cole-h/passrs/blob/ad2da30c29b129bebb4e4de9c640633939893cf0/src/util.rs#L533.

So the process is twofold:

  1. Create the signed commit using GPG
  2. Update the HEAD reference manually

Hope this helps.