// Copyright 2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.use clap_complete::ArgValueCompleter;use indexmap::IndexSet;use itertools::Itertools as _;use jj_lib::commit::Commit;use jj_lib::commit::CommitIteratorExt as _;use jj_lib::repo::Repo as _;use jj_lib::signing::SignBehavior;use crate::cli_util::print_updated_commits;use crate::cli_util::CommandHelper;use crate::cli_util::RevisionArg;use crate::command_error::user_error_with_hint;use crate::command_error::CommandError;use crate::complete;use crate::ui::Ui;/// Cryptographically sign a revision////// This command requires configuring a [commit signing] backend.////// [commit signing]:/// https://jj-vcs.github.io/jj/latest/config/#commit-signing#[derive(clap::Args, Clone, Debug)]pub struct SignArgs { /// What revision(s) to sign /// /// If no revisions are specified, this defaults to the `revsets.sign` /// setting. /// /// Note that revisions are always re-signed. /// /// While that leads to discomfort for users, which sign with hardware /// devices, as of now we cannot reliably check if a commit is already /// signed by the user without creating a signature (see [#5786]). /// /// [#5786]: /// https://github.com/jj-vcs/jj/issues/5786 #[arg( long, short, value_name = "REVSETS", add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec<RevisionArg>, /// The key used for signing #[arg(long)] key: Option<String>,}pub fn cmd_sign(ui: &mut Ui, command: &CommandHelper, args: &SignArgs) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; if !workspace_command.repo().store().signer().can_sign() { return Err(user_error_with_hint( "No signing backend configured", "For configuring a signing backend, see https://jj-vcs.github.io/jj/latest/config/#commit-signing", )); } let revset_expression = if args.revisions.is_empty() { let revset_string = workspace_command.settings().get_string("revsets.sign")?; workspace_command.parse_revset(ui, &RevisionArg::from(revset_string))? } else { workspace_command.parse_union_revsets(ui, &args.revisions)? }; let to_sign: IndexSet<Commit> = revset_expression.evaluate_to_commits()?.try_collect()?; workspace_command.check_rewritable(to_sign.iter().ids())?; let mut tx = workspace_command.start_transaction(); let mut signed_commits = vec![]; let mut num_reparented = 0; tx.repo_mut().transform_descendants( to_sign.iter().ids().cloned().collect_vec(), |rewriter| { let old_commit = rewriter.old_commit().clone(); let mut commit_builder = rewriter.reparent(); if to_sign.contains(&old_commit) { if let Some(key) = &args.key { commit_builder = commit_builder.set_sign_key(key.clone()); } let new_commit = commit_builder .set_sign_behavior(SignBehavior::Force) .write()?; signed_commits.push(new_commit); } else { commit_builder.write()?; num_reparented += 1; } Ok(()) }, )?; if let Some(mut formatter) = ui.status_formatter() { if !signed_commits.is_empty() { writeln!(formatter, "Signed {} commits:", signed_commits.len())?; print_updated_commits( formatter.as_mut(), &tx.commit_summary_template(), &signed_commits, )?; } } let num_not_authored_by_me = signed_commits .iter() .filter(|commit| commit.author().email != tx.settings().user_email()) .count(); if num_not_authored_by_me > 0 { writeln!( ui.warning_default(), "{num_not_authored_by_me} of these commits are not authored by you", )?; } if num_reparented > 0 { writeln!(ui.status(), "Rebased {num_reparented} descendant commits")?; } let transaction_description = match &*signed_commits { [] => "".to_string(), [commit] => format!("sign commit {}", commit.id()), commits => format!( "sign commit {} and {} more", commits[0].id(), commits.len() - 1 ), }; tx.finish(ui, transaction_description)?; Ok(())}
// Copyright 2023 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use clap_complete::ArgValueCompleter;
use indexmap::IndexSet;
use itertools::Itertools as _;
use jj_lib::commit::Commit;
use jj_lib::commit::CommitIteratorExt as _;
use jj_lib::repo::Repo as _;
use jj_lib::signing::SignBehavior;
use crate::cli_util::print_updated_commits;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui;
/// Cryptographically sign a revision
///
/// This command requires configuring a [commit signing] backend.
///
/// [commit signing]:
/// https://jj-vcs.github.io/jj/latest/config/#commit-signing
#[derive(clap::Args, Clone, Debug)]
pub struct SignArgs {
/// What revision(s) to sign
///
/// If no revisions are specified, this defaults to the `revsets.sign`
/// setting.
///
/// Note that revisions are always re-signed.
///
/// While that leads to discomfort for users, which sign with hardware
/// devices, as of now we cannot reliably check if a commit is already
/// signed by the user without creating a signature (see [#5786]).
///
/// [#5786]:
/// https://github.com/jj-vcs/jj/issues/5786
#[arg(
long, short,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revisions: Vec<RevisionArg>,
/// The key used for signing
#[arg(long)]
key: Option<String>,
}
pub fn cmd_sign(ui: &mut Ui, command: &CommandHelper, args: &SignArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
if !workspace_command.repo().store().signer().can_sign() {
return Err(user_error_with_hint(
"No signing backend configured",
"For configuring a signing backend, see https://jj-vcs.github.io/jj/latest/config/#commit-signing",
));
}
let revset_expression = if args.revisions.is_empty() {
let revset_string = workspace_command.settings().get_string("revsets.sign")?;
workspace_command.parse_revset(ui, &RevisionArg::from(revset_string))?
} else {
workspace_command.parse_union_revsets(ui, &args.revisions)?
};
let to_sign: IndexSet<Commit> = revset_expression.evaluate_to_commits()?.try_collect()?;
workspace_command.check_rewritable(to_sign.iter().ids())?;
let mut tx = workspace_command.start_transaction();
let mut signed_commits = vec![];
let mut num_reparented = 0;
tx.repo_mut().transform_descendants(
to_sign.iter().ids().cloned().collect_vec(),
|rewriter| {
let old_commit = rewriter.old_commit().clone();
let mut commit_builder = rewriter.reparent();
if to_sign.contains(&old_commit) {
if let Some(key) = &args.key {
commit_builder = commit_builder.set_sign_key(key.clone());
}
let new_commit = commit_builder
.set_sign_behavior(SignBehavior::Force)
.write()?;
signed_commits.push(new_commit);
} else {
commit_builder.write()?;
num_reparented += 1;
}
Ok(())
},
)?;
if let Some(mut formatter) = ui.status_formatter() {
if !signed_commits.is_empty() {
writeln!(formatter, "Signed {} commits:", signed_commits.len())?;
print_updated_commits(
formatter.as_mut(),
&tx.commit_summary_template(),
&signed_commits,
)?;
}
}
let num_not_authored_by_me = signed_commits
.iter()
.filter(|commit| commit.author().email != tx.settings().user_email())
.count();
if num_not_authored_by_me > 0 {
writeln!(
ui.warning_default(),
"{num_not_authored_by_me} of these commits are not authored by you",
)?;
}
if num_reparented > 0 {
writeln!(ui.status(), "Rebased {num_reparented} descendant commits")?;
}
let transaction_description = match &*signed_commits {
[] => "".to_string(),
[commit] => format!("sign commit {}", commit.id()),
commits => format!(
"sign commit {} and {} more",
commits[0].id(),
commits.len() - 1
),
};
tx.finish(ui, transaction_description)?;
Ok(())
}