mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 03:26:36 +03:00
Use gix
to create signed commits with multiple headers.
This commit is contained in:
parent
ada4b17c47
commit
d16656816d
@ -53,6 +53,24 @@ impl From<CommitHeadersV1> for CommitHeadersV2 {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove CommitBuffer entirely
|
||||
impl From<CommitHeadersV2> for Vec<(BString, BString)> {
|
||||
fn from(hdr: CommitHeadersV2) -> Self {
|
||||
let mut out = vec![
|
||||
(
|
||||
BString::from(HEADERS_VERSION_HEADER),
|
||||
BString::from(V2_HEADERS_VERSION),
|
||||
),
|
||||
(V2_CHANGE_ID_HEADER.into(), hdr.change_id.clone().into()),
|
||||
];
|
||||
|
||||
if let Some(conflicted) = hdr.conflicted {
|
||||
out.push((V2_CONFLICTED_HEADER.into(), conflicted.to_string().into()));
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasCommitHeaders {
|
||||
fn gitbutler_headers(&self) -> Option<CommitHeadersV2>;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use bstr::BString;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_project::Project;
|
||||
use std::path::Path;
|
||||
@ -30,9 +29,7 @@ impl RepoCommands for Project {
|
||||
|
||||
fn check_signing_settings(&self) -> Result<bool> {
|
||||
let ctx = CommandContext::open(self)?;
|
||||
let signed = ctx
|
||||
.repository()
|
||||
.sign_buffer(&BString::new("test".into()).into());
|
||||
let signed = ctx.repository().sign_buffer(b"test");
|
||||
match signed {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => Err(e),
|
||||
|
@ -8,10 +8,11 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use bstr::{BString, ByteSlice};
|
||||
use git2::{BlameOptions, Tree};
|
||||
use gitbutler_branch::{gix_to_git2_signature, SignaturePurpose};
|
||||
use gitbutler_commit::{commit_buffer::CommitBuffer, commit_headers::CommitHeadersV2};
|
||||
use gitbutler_commit::commit_headers::CommitHeadersV2;
|
||||
use gitbutler_config::git::{GbConfig, GitConfig};
|
||||
use gitbutler_error::error::Code;
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gix::objs::WriteTo;
|
||||
use gix::status::plumbing::index_as_worktree::{Change, EntryStatus};
|
||||
use tracing::instrument;
|
||||
|
||||
@ -45,8 +46,9 @@ pub trait RepositoryExt {
|
||||
fn in_memory_repo(&self) -> Result<git2::Repository>;
|
||||
/// Fetches the workspace commit from the gitbutler/workspace branch
|
||||
fn workspace_commit(&self) -> Result<git2::Commit<'_>>;
|
||||
/// Takes a CommitBuffer and returns it after being signed by by your git signing configuration
|
||||
fn sign_buffer(&self, buffer: &CommitBuffer) -> Result<BString>;
|
||||
/// `buffer` is the commit object to sign, but in theory could be anything to compute the signature for.
|
||||
/// Returns the computed signature.
|
||||
fn sign_buffer(&self, buffer: &[u8]) -> Result<BString>;
|
||||
|
||||
fn checkout_index_builder<'a>(&'a self, index: &'a mut git2::Index) -> CheckoutIndexBuilder;
|
||||
fn checkout_index_path_builder<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
||||
@ -248,45 +250,43 @@ impl RepositoryExt for git2::Repository {
|
||||
parents: &[&git2::Commit<'_>],
|
||||
commit_headers: Option<CommitHeadersV2>,
|
||||
) -> Result<git2::Oid> {
|
||||
fn commit_buffer(
|
||||
repository: &git2::Repository,
|
||||
commit_buffer: &CommitBuffer,
|
||||
) -> Result<git2::Oid> {
|
||||
let oid = repository
|
||||
.odb()?
|
||||
.write(git2::ObjectType::Commit, &commit_buffer.as_bstring())?;
|
||||
let repo = gix::open(self.path())?;
|
||||
let mut commit = gix::objs::Commit {
|
||||
message: message.into(),
|
||||
tree: git2_to_gix_object_id(tree.id()),
|
||||
author: git2_signature_to_gix_signature(author),
|
||||
committer: git2_signature_to_gix_signature(committer),
|
||||
encoding: None,
|
||||
parents: parents
|
||||
.iter()
|
||||
.map(|commit| git2_to_gix_object_id(commit.id()))
|
||||
.collect(),
|
||||
extra_headers: commit_headers.unwrap_or_default().into(),
|
||||
};
|
||||
|
||||
Ok(oid)
|
||||
}
|
||||
|
||||
let mut buffer: CommitBuffer = self
|
||||
.commit_create_buffer(author, committer, message, tree, parents)?
|
||||
.into();
|
||||
|
||||
buffer.set_gitbutler_headers(commit_headers);
|
||||
|
||||
let oid = if self.gb_config()?.sign_commits.unwrap_or(false) {
|
||||
let signature = self.sign_buffer(&buffer);
|
||||
if self.gb_config()?.sign_commits.unwrap_or(false) {
|
||||
let mut buf = Vec::new();
|
||||
commit.write_to(&mut buf)?;
|
||||
let signature = self.sign_buffer(&buf);
|
||||
match signature {
|
||||
Ok(signature) => self
|
||||
.commit_signed(
|
||||
buffer.as_bstring().to_string().as_str(),
|
||||
signature.to_string().as_str(),
|
||||
None,
|
||||
)
|
||||
.map_err(Into::into),
|
||||
Ok(signature) => {
|
||||
commit.extra_headers.push(("gpgsig".into(), signature));
|
||||
}
|
||||
Err(e) => {
|
||||
// If signing fails, set the "gitbutler.signCommits" config to false before erroring out
|
||||
self.set_gb_config(GbConfig {
|
||||
sign_commits: Some(false),
|
||||
..GbConfig::default()
|
||||
})?;
|
||||
Err(anyhow!("Failed to sign commit: {}", e).context(Code::CommitSigningFailed))
|
||||
return Err(
|
||||
anyhow!("Failed to sign commit: {}", e).context(Code::CommitSigningFailed)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commit_buffer(self, &buffer)
|
||||
}?;
|
||||
}
|
||||
// TODO: extra-headers should be supported in `gix` directly.
|
||||
let oid = gix_to_git2_oid(repo.write_object(&commit)?);
|
||||
|
||||
// update reference
|
||||
if let Some(refname) = update_ref {
|
||||
self.reference(&refname.to_string(), oid, true, message)?;
|
||||
@ -311,7 +311,7 @@ impl RepositoryExt for git2::Repository {
|
||||
self.blame_file(path, Some(&mut opts))
|
||||
}
|
||||
|
||||
fn sign_buffer(&self, buffer: &CommitBuffer) -> Result<BString> {
|
||||
fn sign_buffer(&self, buffer: &[u8]) -> Result<BString> {
|
||||
// check git config for gpg.signingkey
|
||||
// TODO: support gpg.ssh.defaultKeyCommand to get the signing key if this value doesn't exist
|
||||
let signing_key = self.config()?.get_string("user.signingkey");
|
||||
@ -326,7 +326,7 @@ impl RepositoryExt for git2::Repository {
|
||||
if is_ssh {
|
||||
// write commit data to a temp file so we can sign it
|
||||
let mut signature_storage = tempfile::NamedTempFile::new()?;
|
||||
signature_storage.write_all(&buffer.as_bstring())?;
|
||||
signature_storage.write_all(buffer)?;
|
||||
let buffer_file_to_sign_path = signature_storage.into_temp_path();
|
||||
|
||||
let gpg_program = self.config()?.get_string("gpg.ssh.program");
|
||||
@ -421,11 +421,7 @@ impl RepositoryExt for git2::Repository {
|
||||
.context(format!("Could not execute GPG program using {:?}", cmd))
|
||||
}
|
||||
};
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.expect("configured")
|
||||
.write_all(&buffer.as_bstring())?;
|
||||
child.stdin.take().expect("configured").write_all(buffer)?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
if output.status.success() {
|
||||
@ -637,3 +633,25 @@ impl GixRepositoryExt for gix::Repository {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: expose in separate crate to deduplicate
|
||||
fn git2_to_gix_object_id(id: git2::Oid) -> gix::ObjectId {
|
||||
gix::ObjectId::try_from(id.as_bytes()).expect("git2 oid is always valid")
|
||||
}
|
||||
|
||||
// TODO: expose in separate crate to deduplicate
|
||||
fn git2_signature_to_gix_signature(input: &git2::Signature<'_>) -> gix::actor::Signature {
|
||||
gix::actor::Signature {
|
||||
name: input.name_bytes().into(),
|
||||
email: input.email_bytes().into(),
|
||||
time: gix::date::Time {
|
||||
seconds: input.when().seconds(),
|
||||
offset: input.when().offset_minutes() * 60,
|
||||
sign: input.when().offset_minutes().into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn gix_to_git2_oid(id: impl Into<gix::ObjectId>) -> git2::Oid {
|
||||
git2::Oid::from_bytes(id.into().as_bytes()).expect("always valid")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user