Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to correctly create GPG-signed commits? #507

Closed
cole-h opened this issue Dec 22, 2019 · 3 comments
Closed

How to correctly create GPG-signed commits? #507

cole-h opened this issue Dec 22, 2019 · 3 comments

Comments

@cole-h
Copy link

cole-h commented Dec 22, 2019

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.

@cole-h
Copy link
Author

cole-h commented Dec 26, 2019

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 cole-h closed this as completed Dec 26, 2019
@grantbdev
Copy link

@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.

@cole-h
Copy link
Author

cole-h commented Jul 29, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants