Skip to content

Support git commit signing using OpenPGP #1544

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

Merged
merged 14 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added
* sign commits using openpgp; implement `Sign` trait to implement more methods

## [0.25.2] - 2024-03-22

### Fixes
Expand Down
20 changes: 20 additions & 0 deletions asyncgit/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ pub enum Error {
///
#[error("git hook error: {0}")]
Hooks(#[from] git2_hooks::HooksError),

///
#[error("sign builder error: {0}")]
SignBuilder(#[from] crate::sync::sign::SignBuilderError),

///
#[error("sign error: {0}")]
Sign(#[from] crate::sync::sign::SignError),

///
#[error("amend error: config commit.gpgsign=true detected.\ngpg signing is not supported for amending non-last commits")]
SignAmendNonLastCommit,

///
#[error("reword error: config commit.gpgsign=true detected.\ngpg signing is not supported for rewording non-last commits")]
SignRewordNonLastCommit,

///
#[error("reword error: config commit.gpgsign=true detected.\ngpg signing is not supported for rewording commits with staged changes\ntry unstaging or stashing your changes")]
SignRewordLastCommitStaged,
}

///
Expand Down
72 changes: 67 additions & 5 deletions asyncgit/src/sync/commit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Git Api for Commits
use super::{CommitId, RepoPath};
use crate::sync::sign::{SignBuilder, SignError};
use crate::{
error::Result,
error::{Error, Result},
sync::{repository::repo, utils::get_head_repo},
};
use git2::{
Expand All @@ -18,12 +19,27 @@ pub fn amend(
scope_time!("amend");

let repo = repo(repo_path)?;
let config = repo.config()?;

let commit = repo.find_commit(id.into())?;

let mut index = repo.index()?;
let tree_id = index.write_tree()?;
let tree = repo.find_tree(tree_id)?;

if config.get_bool("commit.gpgsign").unwrap_or(false) {
// HACK: we undo the last commit and create a new one
use crate::sync::utils::undo_last_commit;

let head = get_head_repo(&repo)?;
if head == commit.id().into() {
undo_last_commit(repo_path)?;
return self::commit(repo_path, msg);
}

return Err(Error::SignAmendNonLastCommit);
}

let new_id = commit.amend(
Some("HEAD"),
None,
Expand Down Expand Up @@ -68,7 +84,7 @@ pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
scope_time!("commit");

let repo = repo(repo_path)?;

let config = repo.config()?;
let signature = signature_allow_undefined_name(&repo)?;
let mut index = repo.index()?;
let tree_id = index.write_tree()?;
Expand All @@ -82,16 +98,62 @@ pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {

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

Ok(repo
.commit(
let commit_id = if config
.get_bool("commit.gpgsign")
.unwrap_or(false)
{
use crate::sync::sign::Sign;

let buffer = repo.commit_create_buffer(
&signature,
&signature,
msg,
&tree,
parents.as_slice(),
)?;

let commit = std::str::from_utf8(&buffer).map_err(|_e| {
SignError::Shellout("utf8 conversion error".to_string())
})?;

let sign = SignBuilder::from_gitconfig(&repo, &config)?;
let (signature, signature_field) = sign.sign(&buffer)?;
let commit_id = repo.commit_signed(
commit,
&signature,
Some(&signature_field),
)?;

// manually advance to the new commit ID
// repo.commit does that on its own, repo.commit_signed does not
// if there is no head, read default branch or defaul to "master"
if let Ok(mut head) = repo.head() {
head.set_target(commit_id, msg)?;
} else {
let default_branch_name = config
.get_str("init.defaultBranch")
.unwrap_or("master");
repo.reference(
&format!("refs/heads/{default_branch_name}"),
commit_id,
true,
msg,
)?;
}

commit_id
} else {
repo.commit(
Some("HEAD"),
&signature,
&signature,
msg,
&tree,
parents.as_slice(),
)?
.into())
};

Ok(commit_id.into())
}

/// Tag a commit.
Expand Down
1 change: 1 addition & 0 deletions asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod remotes;
mod repository;
mod reset;
mod reword;
pub mod sign;
mod staging;
mod stash;
mod state;
Expand Down
28 changes: 27 additions & 1 deletion asyncgit/src/sync/reword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use git2::{Oid, RebaseOptions, Repository};
use super::{
commit::signature_allow_undefined_name,
repo,
utils::{bytes2string, get_head_refname},
utils::{bytes2string, get_head_refname, get_head_repo},
CommitId, RepoPath,
};
use crate::error::{Error, Result};
Expand All @@ -15,6 +15,32 @@ pub fn reword(
message: &str,
) -> Result<CommitId> {
let repo = repo(repo_path)?;
let config = repo.config()?;

if config.get_bool("commit.gpgsign").unwrap_or(false) {
// HACK: we undo the last commit and create a new one
use crate::sync::utils::undo_last_commit;

let head = get_head_repo(&repo)?;
if head == commit {
// Check if there are any staged changes
let parent = repo.find_commit(head.into())?;
let tree = parent.tree()?;
if repo
.diff_tree_to_index(Some(&tree), None, None)?
.deltas()
.len() == 0
{
undo_last_commit(repo_path)?;
return super::commit(repo_path, message);
}

return Err(Error::SignRewordLastCommitStaged);
}

return Err(Error::SignRewordNonLastCommit);
}

let cur_branch_ref = get_head_refname(&repo)?;

match reword_internal(&repo, commit.get_oid(), message) {
Expand Down
Loading