Skip to content

Commit 4bdf7e1

Browse files
mhammerlytommyip
andcommitted
feat: support signing commits with gpg, ssh, x509
Originally implemented by Thomas Ip in arxanas#966. Updated by Matt. Co-authored-by: Thomas Ip <[email protected]>
1 parent 8f679dd commit 4bdf7e1

File tree

20 files changed

+397
-68
lines changed

20 files changed

+397
-68
lines changed

Cargo.lock

Lines changed: 64 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ git-branchless-submit = { version = "0.10.0", path = "git-branchless-submit" }
7070
git-branchless-test = { version = "0.10.0", path = "git-branchless-test" }
7171
git-branchless-undo = { version = "0.10.0", path = "git-branchless-undo" }
7272
git2 = { version = "0.20.0", default-features = false }
73+
# TODO: using a local clone of git2-ext that updates its git2 and git-fixture deps
74+
git2-ext = { path = "../git2-ext" }
7375
glob = "0.3.2"
7476
indexmap = "2.7.1"
7577
indicatif = { version = "0.17.11", features = ["improved_unicode"] }

git-branchless-lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ eden_dag = { workspace = true }
5656
eyre = { workspace = true }
5757
futures = { workspace = true }
5858
git2 = { workspace = true }
59+
git2-ext = { workspace = true }
5960
indicatif = { workspace = true }
6061
itertools = { workspace = true }
6162
lazy_static = { workspace = true }

git-branchless-lib/src/core/rewrite/execute.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::core::formatting::Pluralize;
1515
use crate::core::repo_ext::RepoExt;
1616
use crate::git::{
1717
BranchType, CategorizedReferenceName, GitRunInfo, MaybeZeroOid, NonZeroOid, ReferenceName,
18-
Repo, ResolvedReferenceInfo,
18+
Repo, ResolvedReferenceInfo, SignOption,
1919
};
2020
use crate::util::{ExitCode, EyreExitOr};
2121

@@ -436,8 +436,8 @@ mod in_memory {
436436
use crate::core::rewrite::move_branches;
437437
use crate::core::rewrite::plan::{OidOrLabel, RebaseCommand, RebasePlan};
438438
use crate::git::{
439-
AmendFastOptions, CherryPickFastOptions, CreateCommitFastError, GitRunInfo, MaybeZeroOid,
440-
NonZeroOid, Repo,
439+
self, AmendFastOptions, CherryPickFastOptions, CreateCommitFastError, GitRunInfo,
440+
MaybeZeroOid, NonZeroOid, Repo,
441441
};
442442
use crate::util::EyreExitOr;
443443

@@ -500,6 +500,7 @@ mod in_memory {
500500
force_on_disk: _,
501501
resolve_merge_conflicts: _, // May be needed once we can resolve merge conflicts in memory.
502502
check_out_commit_options: _, // Caller is responsible for checking out to new HEAD.
503+
sign_option,
503504
} = options;
504505

505506
let mut current_oid = rebase_plan.first_dest_oid;
@@ -537,6 +538,8 @@ mod in_memory {
537538
.count();
538539
let (effects, progress) = effects.start_operation(OperationType::RebaseCommits);
539540

541+
let signer = git::get_signer(repo, sign_option)?;
542+
540543
for command in rebase_plan.commands.iter() {
541544
match command {
542545
RebaseCommand::CreateLabel { label_name } => {
@@ -670,12 +673,12 @@ mod in_memory {
670673
);
671674
rebased_commit_oid = Some(
672675
repo.create_commit(
673-
None,
674676
&commit_author,
675677
&committer_signature,
676678
commit_message,
677679
&commit_tree,
678680
vec![&current_commit],
681+
signer.as_deref(),
679682
)
680683
.wrap_err("Applying rebased commit")?,
681684
);
@@ -802,12 +805,12 @@ mod in_memory {
802805
};
803806
let rebased_commit_oid = repo
804807
.create_commit(
805-
None,
806808
&replacement_commit.get_author(),
807809
&committer_signature,
808810
replacement_commit_message,
809811
&replacement_tree,
810812
parents.iter().collect(),
813+
signer.as_deref(),
811814
)
812815
.wrap_err("Applying rebased commit")?;
813816

@@ -911,6 +914,7 @@ mod in_memory {
911914
force_on_disk: _,
912915
resolve_merge_conflicts: _,
913916
check_out_commit_options,
917+
sign_option: _,
914918
} = options;
915919

916920
for new_oid in rewritten_oids.values() {
@@ -996,6 +1000,7 @@ mod on_disk {
9961000
force_on_disk: _,
9971001
resolve_merge_conflicts: _,
9981002
check_out_commit_options: _, // Checkout happens after rebase has concluded.
1003+
sign_option,
9991004
} = options;
10001005

10011006
let (effects, _progress) = effects.start_operation(OperationType::InitializeRebase);
@@ -1113,6 +1118,16 @@ mod on_disk {
11131118
)
11141119
})?;
11151120

1121+
let gpg_sign_opt_path = rebase_state_dir.join("gpg_sign_opt");
1122+
if let Some(sign_flag) = sign_option.as_rebase_flag(repo)? {
1123+
std::fs::write(&gpg_sign_opt_path, sign_flag).wrap_err_with(|| {
1124+
format!(
1125+
"Writing `gpg_sign_opt` to: {:?}",
1126+
gpg_sign_opt_path.as_path()
1127+
)
1128+
})?;
1129+
}
1130+
11161131
let end_file_path = rebase_state_dir.join("end");
11171132
std::fs::write(
11181133
end_file_path.as_path(),
@@ -1172,6 +1187,7 @@ mod on_disk {
11721187
force_on_disk: _,
11731188
resolve_merge_conflicts: _,
11741189
check_out_commit_options: _, // Checkout happens after rebase has concluded.
1190+
sign_option: _,
11751191
} = options;
11761192

11771193
match write_rebase_state_to_disk(effects, git_run_info, repo, rebase_plan, options)? {
@@ -1216,6 +1232,9 @@ pub struct ExecuteRebasePlanOptions {
12161232

12171233
/// If `HEAD` was moved, the options for checking out the new `HEAD` commit.
12181234
pub check_out_commit_options: CheckOutCommitOptions,
1235+
1236+
/// GPG-sign commits.
1237+
pub sign_option: SignOption,
12191238
}
12201239

12211240
/// The result of executing a rebase plan.
@@ -1261,6 +1280,7 @@ pub fn execute_rebase_plan(
12611280
force_on_disk,
12621281
resolve_merge_conflicts,
12631282
check_out_commit_options: _,
1283+
sign_option: _,
12641284
} = options;
12651285

12661286
if !force_on_disk {

git-branchless-lib/src/git/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod oid;
88
mod reference;
99
mod repo;
1010
mod run;
11+
mod sign;
1112
mod snapshot;
1213
mod status;
1314
mod test;
@@ -27,6 +28,7 @@ pub use repo::{
2728
Result as RepoResult, Time,
2829
};
2930
pub use run::{GitRunInfo, GitRunOpts, GitRunResult};
31+
pub use sign::{get_signer, SignOption};
3032
pub use snapshot::{WorkingCopyChangesType, WorkingCopySnapshot};
3133
pub use status::{FileMode, FileStatus, StatusEntry};
3234
pub use test::{

git-branchless-lib/src/git/object.rs

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ use bstr::{BString, ByteSlice};
44
use cursive::theme::BaseColor;
55
use cursive::utils::markup::StyledString;
66
use git2::message_trailers_bytes;
7-
use tracing::instrument;
7+
use git2_ext::ops::Sign;
8+
use itertools::Itertools;
9+
use tracing::{error, instrument};
810

911
use crate::core::formatting::{Glyphs, StyledStringBuilder};
1012
use crate::core::node_descriptors::{
1113
render_node_descriptors, CommitMessageDescriptor, CommitOidDescriptor, NodeObject, Redactor,
1214
};
1315
use crate::git::oid::make_non_zero_oid;
14-
use crate::git::repo::{Error, Result, Signature};
16+
use crate::git::repo::{Error, Repo, Result, Signature};
1517
use crate::git::{NonZeroOid, Time, Tree};
1618

1719
use super::MaybeZeroOid;
@@ -291,26 +293,59 @@ impl<'repo> Commit<'repo> {
291293

292294
/// Amend this existing commit.
293295
/// Returns the OID of the resulting new commit.
294-
#[instrument]
296+
#[instrument(skip(signer))]
295297
pub fn amend_commit(
296298
&self,
297-
update_ref: Option<&str>,
299+
repo: &'repo Repo,
298300
author: Option<&Signature>,
299301
committer: Option<&Signature>,
300302
message: Option<&str>,
301303
tree: Option<&Tree>,
304+
signer: Option<&dyn Sign>,
302305
) -> Result<NonZeroOid> {
303-
let oid = self
304-
.inner
305-
.amend(
306-
update_ref,
307-
author.map(|author| &author.inner),
308-
committer.map(|committer| &committer.inner),
309-
None,
310-
message,
311-
tree.map(|tree| &tree.inner),
312-
)
313-
.map_err(Error::Amend)?;
306+
let parents = self.get_parents();
307+
let parents = parents.iter().map(|parent| &parent.inner).collect_vec();
308+
309+
let new_author = match author {
310+
Some(author) => &author.inner,
311+
None => &self.inner.author(),
312+
};
313+
let new_committer = match committer {
314+
Some(committer) => &committer.inner,
315+
None => &self.inner.committer(),
316+
};
317+
let new_message = match message {
318+
Some(message) => message,
319+
None => match std::str::from_utf8(self.inner.message_bytes()) {
320+
Ok(message) => message,
321+
Err(e) => {
322+
error!(
323+
?message,
324+
?e,
325+
"failed to decode git commit message, not valid UTF-8"
326+
);
327+
return Err(Error::DecodeUtf8 { item: "message" });
328+
}
329+
},
330+
};
331+
let new_tree = match tree {
332+
Some(tree) => &tree.inner,
333+
None => &self.inner.tree().map_err(|err| Error::FindTree {
334+
source: err,
335+
oid: self.inner.tree_id().into(),
336+
})?,
337+
};
338+
339+
let oid = git2_ext::ops::commit(
340+
&repo.inner,
341+
new_author,
342+
new_committer,
343+
new_message,
344+
new_tree,
345+
parents.as_slice(),
346+
signer,
347+
)
348+
.map_err(Error::Amend)?;
314349
Ok(make_non_zero_oid(oid))
315350
}
316351
}

0 commit comments

Comments
 (0)