// 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::revset::RevsetIteratorExt as _; use jj_lib::signing::SignBehavior; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; use crate::cli_util::print_updated_commits; use crate::command_error::CommandError; use crate::complete; use crate::ui::Ui; /// Drop a cryptographic signature /// /// See also [commit signing] docs. /// /// [commit signing]: /// https://jj-vcs.github.io/jj/latest/config/#commit-signing #[derive(clap::Args, Clone, Debug)] pub struct UnsignArgs { /// What revision(s) to unsign #[arg( long, short, value_name = "REVSETS", add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, } pub fn cmd_unsign( ui: &mut Ui, command: &CommandHelper, args: &UnsignArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; let target_expr = workspace_command .parse_union_revsets(ui, &args.revisions)? .resolve()?; workspace_command.check_rewritable_expr(&target_expr)?; let commits: IndexSet = target_expr .evaluate(workspace_command.repo().as_ref())? .iter() .commits(workspace_command.repo().store()) .try_collect()?; let to_unsign: IndexSet = commits .into_iter() .filter(|commit| commit.is_signed()) .collect(); let mut tx = workspace_command.start_transaction(); let mut unsigned_commits = vec![]; let mut num_reparented = 0; tx.repo_mut().transform_descendants( to_unsign.iter().ids().cloned().collect_vec(), async |rewriter| { let old_commit = rewriter.old_commit().clone(); let commit_builder = rewriter.reparent(); if to_unsign.contains(&old_commit) { let new_commit = commit_builder .set_sign_behavior(SignBehavior::Drop) .write()?; unsigned_commits.push(new_commit); } else { commit_builder.write()?; num_reparented += 1; } Ok(()) }, )?; if let Some(mut formatter) = ui.status_formatter() && !unsigned_commits.is_empty() { writeln!(formatter, "Unsigned {} commits:", unsigned_commits.len())?; print_updated_commits( formatter.as_mut(), &tx.commit_summary_template(), &unsigned_commits, )?; } let num_not_authored_by_me = unsigned_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 &*unsigned_commits { [] => "".to_string(), [commit] => format!("unsign commit {}", commit.id()), commits => format!( "unsign commit {} and {} more", commits[0].id(), commits.len() - 1 ), }; tx.finish(ui, transaction_description)?; Ok(()) }