// Copyright 2020 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. #![expect(missing_docs)] use std::sync::Arc; use crate::backend;use crate::backend::BackendError;use crate::backend::BackendResult;use crate::backend::ChangeId;use crate::backend::CommitId;use crate::backend::Signature;use crate::backend::TreeId;use crate::commit::Commit;use crate::commit::is_backend_commit_empty;use crate::conflict_labels::ConflictLabels;use crate::merge::Merge;use crate::merged_tree::MergedTree;use crate::repo::MutableRepo;use crate::repo::Repo;use crate::settings::JJRng;use crate::settings::SignSettings;use crate::settings::UserSettings;use crate::signing::SignBehavior;use crate::store::Store; #[must_use]pub struct CommitBuilder<'repo> { mut_repo: &'repo mut MutableRepo, inner: DetachedCommitBuilder,} impl CommitBuilder<'_> { /// Detaches from `&'repo mut` lifetime. The returned builder can be used in /// order to obtain a temporary commit object. pub fn detach(self) -> DetachedCommitBuilder { self.inner } /// Clears the source commit to not record new commit as rewritten from it. /// /// The caller should also assign new change id to not create divergence. pub fn clear_rewrite_source(mut self) -> Self { self.inner.clear_rewrite_source(); self } pub fn parents(&self) -> &[CommitId] { self.inner.parents() } pub fn set_parents(mut self, parents: Vec<CommitId>) -> Self { self.inner.set_parents(parents); self } pub fn predecessors(&self) -> &[CommitId] { self.inner.predecessors() } pub fn set_predecessors(mut self, predecessors: Vec<CommitId>) -> Self { self.inner.set_predecessors(predecessors); self } pub fn tree(&self) -> MergedTree { self.inner.tree() } pub fn tree_ids(&self) -> &Merge<TreeId> { self.inner.tree_ids() } pub fn set_tree(mut self, tree: MergedTree) -> Self { self.inner.set_tree(tree); self } /// [`Commit::is_empty()`] for the new commit. pub fn is_empty(&self) -> BackendResult<bool> { self.inner.is_empty(self.mut_repo) } pub fn change_id(&self) -> &ChangeId { self.inner.change_id() } pub fn set_change_id(mut self, change_id: ChangeId) -> Self { self.inner.set_change_id(change_id); self } pub fn generate_new_change_id(mut self) -> Self { self.inner.generate_new_change_id(); self } pub fn description(&self) -> &str { self.inner.description() } pub fn set_description(mut self, description: impl Into<String>) -> Self { self.inner.set_description(description); self } pub fn author(&self) -> &Signature { self.inner.author() } pub fn set_author(mut self, author: Signature) -> Self { self.inner.set_author(author); self } pub fn committer(&self) -> &Signature { self.inner.committer() } pub fn set_committer(mut self, committer: Signature) -> Self { self.inner.set_committer(committer); self } /// [`Commit::is_discardable()`] for the new commit. pub fn is_discardable(&self) -> BackendResult<bool> { self.inner.is_discardable(self.mut_repo) } pub fn sign_settings(&self) -> &SignSettings { self.inner.sign_settings() } pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self { self.inner.set_sign_behavior(sign_behavior); self } pub fn set_sign_key(mut self, sign_key: String) -> Self { self.inner.set_sign_key(sign_key); self } pub fn clear_sign_key(mut self) -> Self { self.inner.clear_sign_key(); self } pub async fn write(self) -> BackendResult<Commit> { self.inner.write(self.mut_repo).await } /// Records the old commit as abandoned instead of writing new commit. This /// is noop for the builder created by [`MutableRepo::new_commit()`]. pub fn abandon(self) { self.inner.abandon(self.mut_repo); }} /// Like `CommitBuilder`, but doesn't mutably borrow `MutableRepo`.#[derive(Debug)]pub struct DetachedCommitBuilder { store: Arc<Store>, rng: Arc<JJRng>, commit: backend::Commit, predecessors: Vec<CommitId>, rewrite_source: Option<Commit>, sign_settings: SignSettings, record_predecessors_in_commit: bool,} impl DetachedCommitBuilder { /// Only called from [`MutableRepo::new_commit`]. Use that function instead. pub(crate) fn for_new_commit( repo: &dyn Repo, settings: &UserSettings, parents: Vec<CommitId>, tree: MergedTree, ) -> Self { let store = repo.store().clone(); let signature = settings.signature(); assert!(!parents.is_empty()); let rng = settings.get_rng(); let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels(); let change_id = rng.new_change_id(store.change_id_length()); let commit = backend::Commit { parents, predecessors: vec![], root_tree, conflict_labels: conflict_labels.into_merge(), change_id, description: String::new(), author: signature.clone(), committer: signature, secure_sig: None, }; let record_predecessors_in_commit = settings .get_bool("experimental.record-predecessors-in-commit") .unwrap(); Self { store, rng, commit, rewrite_source: None, predecessors: vec![], sign_settings: settings.sign_settings(), record_predecessors_in_commit, } } /// Only called from [`MutableRepo::rewrite_commit`]. Use that function /// instead. pub(crate) fn for_rewrite_from( repo: &dyn Repo, settings: &UserSettings, predecessor: &Commit, ) -> Self { let store = repo.store().clone(); let mut commit = backend::Commit::clone(predecessor.store_commit()); commit.predecessors = vec![]; commit.committer = settings.signature(); // If the user had not configured a name and email before but now they have, // update the author fields with the new information. if commit.author.name.is_empty() { commit.author.name.clone_from(&commit.committer.name); } if commit.author.email.is_empty() { commit.author.email.clone_from(&commit.committer.email); } // Reset author timestamp on discardable commits if the author is the // committer. While it's unlikely we'll have somebody else's commit // with no description in our repo, we'd like to be extra safe. if commit.author.name == commit.committer.name && commit.author.email == commit.committer.email && predecessor.is_discardable(repo).unwrap_or_default() { commit.author.timestamp = commit.committer.timestamp; } let record_predecessors_in_commit = settings .get_bool("experimental.record-predecessors-in-commit") .unwrap(); Self { store, commit, rng: settings.get_rng(), rewrite_source: Some(predecessor.clone()), predecessors: vec![predecessor.id().clone()], sign_settings: settings.sign_settings(), record_predecessors_in_commit, } } /// Attaches the underlying `mut_repo`. pub fn attach(self, mut_repo: &mut MutableRepo) -> CommitBuilder<'_> { assert!(Arc::ptr_eq(&self.store, mut_repo.store())); CommitBuilder { mut_repo, inner: self, } } /// Clears the source commit to not record new commit as rewritten from it. /// /// The caller should also assign new change id to not create divergence. pub fn clear_rewrite_source(&mut self) { self.rewrite_source = None; } pub fn parents(&self) -> &[CommitId] { &self.commit.parents } pub fn set_parents(&mut self, parents: Vec<CommitId>) -> &mut Self { assert!(!parents.is_empty()); self.commit.parents = parents; self } pub fn predecessors(&self) -> &[CommitId] { &self.predecessors } pub fn set_predecessors(&mut self, predecessors: Vec<CommitId>) -> &mut Self { self.predecessors = predecessors; self } pub fn tree(&self) -> MergedTree { MergedTree::new( self.store.clone(), self.commit.root_tree.clone(), ConflictLabels::from_merge(self.commit.conflict_labels.clone()), ) } pub fn tree_ids(&self) -> &Merge<TreeId> { &self.commit.root_tree } pub fn set_tree(&mut self, tree: MergedTree) -> &mut Self { assert!(Arc::ptr_eq(tree.store(), &self.store)); let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels(); self.commit.root_tree = root_tree; self.commit.conflict_labels = conflict_labels.into_merge(); self } /// [`Commit::is_empty()`] for the new commit. pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> { is_backend_commit_empty(repo, &self.store, &self.commit) } pub fn change_id(&self) -> &ChangeId { &self.commit.change_id } pub fn set_change_id(&mut self, change_id: ChangeId) -> &mut Self { self.commit.change_id = change_id; self } pub fn generate_new_change_id(&mut self) -> &mut Self { self.commit.change_id = self.rng.new_change_id(self.store.change_id_length()); self } pub fn description(&self) -> &str { &self.commit.description } pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self { self.commit.description = description.into(); self } pub fn author(&self) -> &Signature { &self.commit.author } pub fn set_author(&mut self, author: Signature) -> &mut Self { self.commit.author = author; self } pub fn committer(&self) -> &Signature { &self.commit.committer } pub fn set_committer(&mut self, committer: Signature) -> &mut Self { self.commit.committer = committer; self } /// [`Commit::is_discardable()`] for the new commit. pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> { Ok(self.description().is_empty() && self.is_empty(repo)?) } pub fn sign_settings(&self) -> &SignSettings { &self.sign_settings } pub fn set_sign_behavior(&mut self, sign_behavior: SignBehavior) -> &mut Self { self.sign_settings.behavior = sign_behavior; self } pub fn set_sign_key(&mut self, sign_key: String) -> &mut Self { self.sign_settings.key = Some(sign_key); self } pub fn clear_sign_key(&mut self) -> &mut Self { self.sign_settings.key = None; self } /// Writes new commit and makes it visible in the `mut_repo`. pub async fn write(mut self, mut_repo: &mut MutableRepo) -> BackendResult<Commit> { if self.record_predecessors_in_commit { self.commit.predecessors = self.predecessors.clone(); } let commit = write_to_store(&self.store, self.commit, &self.sign_settings).await?; // FIXME: Google's index.has_id() always returns true. if mut_repo.is_backed_by_default_index() && mut_repo .index() .has_id(commit.id()) // TODO: indexing error shouldn't be a "BackendError" .map_err(|err| BackendError::Other(err.into()))? { // Recording existing commit as new would create cycle in // predecessors/parent mappings within the current transaction, and // in predecessors graph globally. return Err(BackendError::Other( format!("Newly-created commit {id} already exists", id = commit.id()).into(), )); } mut_repo.add_head(&commit)?; mut_repo.set_predecessors(commit.id().clone(), self.predecessors); if let Some(rewrite_source) = self.rewrite_source { mut_repo.set_rewritten_commit(rewrite_source.id().clone(), commit.id().clone()); } Ok(commit) } /// Writes new commit without making it visible in the repo. /// /// This does not consume the builder, so you can reuse the current /// configuration to create another commit later. pub async fn write_hidden(&self) -> BackendResult<Commit> { let mut commit = self.commit.clone(); if self.record_predecessors_in_commit { commit.predecessors = self.predecessors.clone(); } write_to_store(&self.store, commit, &self.sign_settings).await } /// Records the old commit as abandoned in the `mut_repo`. /// /// This is noop if there's no old commit that would be rewritten to the new /// commit by `write()`. pub fn abandon(self, mut_repo: &mut MutableRepo) { let commit = self.commit; if let Some(rewrite_source) = &self.rewrite_source { mut_repo .record_abandoned_commit_with_parents(rewrite_source.id().clone(), commit.parents); } }} async fn write_to_store( store: &Arc<Store>, mut commit: backend::Commit, sign_settings: &SignSettings,) -> BackendResult<Commit> { let should_sign = store.signer().can_sign() && sign_settings.should_sign(&commit); let sign_fn = |data: &[u8]| store.signer().sign(data, sign_settings.key.as_deref()); // Commit backend doesn't use secure_sig for writing and enforces it with an // assert, but sign_settings.should_sign check above will want to know // if we're rewriting a signed commit commit.secure_sig = None; store .write_commit(commit, should_sign.then_some(&mut &sign_fn)) .await}
// Copyright 2020 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.
#![expect(missing_docs)]
use std::sync::Arc;
use crate::backend;
use crate::backend::BackendError;
use crate::backend::BackendResult;
use crate::backend::ChangeId;
use crate::backend::CommitId;
use crate::backend::Signature;
use crate::backend::TreeId;
use crate::commit::Commit;
use crate::commit::is_backend_commit_empty;
use crate::conflict_labels::ConflictLabels;
use crate::merge::Merge;
use crate::merged_tree::MergedTree;
use crate::repo::MutableRepo;
use crate::repo::Repo;
use crate::settings::JJRng;
use crate::settings::SignSettings;
use crate::settings::UserSettings;
use crate::signing::SignBehavior;
use crate::store::Store;
#[must_use]
pub struct CommitBuilder<'repo> {
mut_repo: &'repo mut MutableRepo,
inner: DetachedCommitBuilder,
}
impl CommitBuilder<'_> {
/// Detaches from `&'repo mut` lifetime. The returned builder can be used in
/// order to obtain a temporary commit object.
pub fn detach(self) -> DetachedCommitBuilder {
self.inner
}
/// Clears the source commit to not record new commit as rewritten from it.
///
/// The caller should also assign new change id to not create divergence.
pub fn clear_rewrite_source(mut self) -> Self {
self.inner.clear_rewrite_source();
self
}
pub fn parents(&self) -> &[CommitId] {
self.inner.parents()
}
pub fn set_parents(mut self, parents: Vec<CommitId>) -> Self {
self.inner.set_parents(parents);
self
}
pub fn predecessors(&self) -> &[CommitId] {
self.inner.predecessors()
}
pub fn set_predecessors(mut self, predecessors: Vec<CommitId>) -> Self {
self.inner.set_predecessors(predecessors);
self
}
pub fn tree(&self) -> MergedTree {
self.inner.tree()
}
pub fn tree_ids(&self) -> &Merge<TreeId> {
self.inner.tree_ids()
}
pub fn set_tree(mut self, tree: MergedTree) -> Self {
self.inner.set_tree(tree);
self
}
/// [`Commit::is_empty()`] for the new commit.
pub fn is_empty(&self) -> BackendResult<bool> {
self.inner.is_empty(self.mut_repo)
}
pub fn change_id(&self) -> &ChangeId {
self.inner.change_id()
}
pub fn set_change_id(mut self, change_id: ChangeId) -> Self {
self.inner.set_change_id(change_id);
self
}
pub fn generate_new_change_id(mut self) -> Self {
self.inner.generate_new_change_id();
self
}
pub fn description(&self) -> &str {
self.inner.description()
}
pub fn set_description(mut self, description: impl Into<String>) -> Self {
self.inner.set_description(description);
self
}
pub fn author(&self) -> &Signature {
self.inner.author()
}
pub fn set_author(mut self, author: Signature) -> Self {
self.inner.set_author(author);
self
}
pub fn committer(&self) -> &Signature {
self.inner.committer()
}
pub fn set_committer(mut self, committer: Signature) -> Self {
self.inner.set_committer(committer);
self
}
/// [`Commit::is_discardable()`] for the new commit.
pub fn is_discardable(&self) -> BackendResult<bool> {
self.inner.is_discardable(self.mut_repo)
}
pub fn sign_settings(&self) -> &SignSettings {
self.inner.sign_settings()
}
pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self {
self.inner.set_sign_behavior(sign_behavior);
self
}
pub fn set_sign_key(mut self, sign_key: String) -> Self {
self.inner.set_sign_key(sign_key);
self
}
pub fn clear_sign_key(mut self) -> Self {
self.inner.clear_sign_key();
self
}
pub async fn write(self) -> BackendResult<Commit> {
self.inner.write(self.mut_repo).await
}
/// Records the old commit as abandoned instead of writing new commit. This
/// is noop for the builder created by [`MutableRepo::new_commit()`].
pub fn abandon(self) {
self.inner.abandon(self.mut_repo);
}
}
/// Like `CommitBuilder`, but doesn't mutably borrow `MutableRepo`.
#[derive(Debug)]
pub struct DetachedCommitBuilder {
store: Arc<Store>,
rng: Arc<JJRng>,
commit: backend::Commit,
predecessors: Vec<CommitId>,
rewrite_source: Option<Commit>,
sign_settings: SignSettings,
record_predecessors_in_commit: bool,
}
impl DetachedCommitBuilder {
/// Only called from [`MutableRepo::new_commit`]. Use that function instead.
pub(crate) fn for_new_commit(
repo: &dyn Repo,
settings: &UserSettings,
parents: Vec<CommitId>,
tree: MergedTree,
) -> Self {
let store = repo.store().clone();
let signature = settings.signature();
assert!(!parents.is_empty());
let rng = settings.get_rng();
let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels();
let change_id = rng.new_change_id(store.change_id_length());
let commit = backend::Commit {
parents,
predecessors: vec![],
root_tree,
conflict_labels: conflict_labels.into_merge(),
change_id,
description: String::new(),
author: signature.clone(),
committer: signature,
secure_sig: None,
};
let record_predecessors_in_commit = settings
.get_bool("experimental.record-predecessors-in-commit")
.unwrap();
Self {
store,
rng,
commit,
rewrite_source: None,
predecessors: vec![],
sign_settings: settings.sign_settings(),
record_predecessors_in_commit,
}
}
/// Only called from [`MutableRepo::rewrite_commit`]. Use that function
/// instead.
pub(crate) fn for_rewrite_from(
repo: &dyn Repo,
settings: &UserSettings,
predecessor: &Commit,
) -> Self {
let store = repo.store().clone();
let mut commit = backend::Commit::clone(predecessor.store_commit());
commit.predecessors = vec![];
commit.committer = settings.signature();
// If the user had not configured a name and email before but now they have,
// update the author fields with the new information.
if commit.author.name.is_empty() {
commit.author.name.clone_from(&commit.committer.name);
}
if commit.author.email.is_empty() {
commit.author.email.clone_from(&commit.committer.email);
}
// Reset author timestamp on discardable commits if the author is the
// committer. While it's unlikely we'll have somebody else's commit
// with no description in our repo, we'd like to be extra safe.
if commit.author.name == commit.committer.name
&& commit.author.email == commit.committer.email
&& predecessor.is_discardable(repo).unwrap_or_default()
{
commit.author.timestamp = commit.committer.timestamp;
}
let record_predecessors_in_commit = settings
.get_bool("experimental.record-predecessors-in-commit")
.unwrap();
Self {
store,
commit,
rng: settings.get_rng(),
rewrite_source: Some(predecessor.clone()),
predecessors: vec![predecessor.id().clone()],
sign_settings: settings.sign_settings(),
record_predecessors_in_commit,
}
}
/// Attaches the underlying `mut_repo`.
pub fn attach(self, mut_repo: &mut MutableRepo) -> CommitBuilder<'_> {
assert!(Arc::ptr_eq(&self.store, mut_repo.store()));
CommitBuilder {
mut_repo,
inner: self,
}
}
/// Clears the source commit to not record new commit as rewritten from it.
///
/// The caller should also assign new change id to not create divergence.
pub fn clear_rewrite_source(&mut self) {
self.rewrite_source = None;
}
pub fn parents(&self) -> &[CommitId] {
&self.commit.parents
}
pub fn set_parents(&mut self, parents: Vec<CommitId>) -> &mut Self {
assert!(!parents.is_empty());
self.commit.parents = parents;
self
}
pub fn predecessors(&self) -> &[CommitId] {
&self.predecessors
}
pub fn set_predecessors(&mut self, predecessors: Vec<CommitId>) -> &mut Self {
self.predecessors = predecessors;
self
}
pub fn tree(&self) -> MergedTree {
MergedTree::new(
self.store.clone(),
self.commit.root_tree.clone(),
ConflictLabels::from_merge(self.commit.conflict_labels.clone()),
)
}
pub fn tree_ids(&self) -> &Merge<TreeId> {
&self.commit.root_tree
}
pub fn set_tree(&mut self, tree: MergedTree) -> &mut Self {
assert!(Arc::ptr_eq(tree.store(), &self.store));
let (root_tree, conflict_labels) = tree.into_tree_ids_and_labels();
self.commit.root_tree = root_tree;
self.commit.conflict_labels = conflict_labels.into_merge();
self
}
/// [`Commit::is_empty()`] for the new commit.
pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
is_backend_commit_empty(repo, &self.store, &self.commit)
}
pub fn change_id(&self) -> &ChangeId {
&self.commit.change_id
}
pub fn set_change_id(&mut self, change_id: ChangeId) -> &mut Self {
self.commit.change_id = change_id;
self
}
pub fn generate_new_change_id(&mut self) -> &mut Self {
self.commit.change_id = self.rng.new_change_id(self.store.change_id_length());
self
}
pub fn description(&self) -> &str {
&self.commit.description
}
pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
self.commit.description = description.into();
self
}
pub fn author(&self) -> &Signature {
&self.commit.author
}
pub fn set_author(&mut self, author: Signature) -> &mut Self {
self.commit.author = author;
self
}
pub fn committer(&self) -> &Signature {
&self.commit.committer
}
pub fn set_committer(&mut self, committer: Signature) -> &mut Self {
self.commit.committer = committer;
self
}
/// [`Commit::is_discardable()`] for the new commit.
pub fn is_discardable(&self, repo: &dyn Repo) -> BackendResult<bool> {
Ok(self.description().is_empty() && self.is_empty(repo)?)
}
pub fn sign_settings(&self) -> &SignSettings {
&self.sign_settings
}
pub fn set_sign_behavior(&mut self, sign_behavior: SignBehavior) -> &mut Self {
self.sign_settings.behavior = sign_behavior;
self
}
pub fn set_sign_key(&mut self, sign_key: String) -> &mut Self {
self.sign_settings.key = Some(sign_key);
self
}
pub fn clear_sign_key(&mut self) -> &mut Self {
self.sign_settings.key = None;
self
}
/// Writes new commit and makes it visible in the `mut_repo`.
pub async fn write(mut self, mut_repo: &mut MutableRepo) -> BackendResult<Commit> {
if self.record_predecessors_in_commit {
self.commit.predecessors = self.predecessors.clone();
}
let commit = write_to_store(&self.store, self.commit, &self.sign_settings).await?;
// FIXME: Google's index.has_id() always returns true.
if mut_repo.is_backed_by_default_index()
&& mut_repo
.index()
.has_id(commit.id())
// TODO: indexing error shouldn't be a "BackendError"
.map_err(|err| BackendError::Other(err.into()))?
{
// Recording existing commit as new would create cycle in
// predecessors/parent mappings within the current transaction, and
// in predecessors graph globally.
return Err(BackendError::Other(
format!("Newly-created commit {id} already exists", id = commit.id()).into(),
));
}
mut_repo.add_head(&commit)?;
mut_repo.set_predecessors(commit.id().clone(), self.predecessors);
if let Some(rewrite_source) = self.rewrite_source {
mut_repo.set_rewritten_commit(rewrite_source.id().clone(), commit.id().clone());
}
Ok(commit)
}
/// Writes new commit without making it visible in the repo.
///
/// This does not consume the builder, so you can reuse the current
/// configuration to create another commit later.
pub async fn write_hidden(&self) -> BackendResult<Commit> {
let mut commit = self.commit.clone();
if self.record_predecessors_in_commit {
commit.predecessors = self.predecessors.clone();
}
write_to_store(&self.store, commit, &self.sign_settings).await
}
/// Records the old commit as abandoned in the `mut_repo`.
///
/// This is noop if there's no old commit that would be rewritten to the new
/// commit by `write()`.
pub fn abandon(self, mut_repo: &mut MutableRepo) {
let commit = self.commit;
if let Some(rewrite_source) = &self.rewrite_source {
mut_repo
.record_abandoned_commit_with_parents(rewrite_source.id().clone(), commit.parents);
}
}
}
async fn write_to_store(
store: &Arc<Store>,
mut commit: backend::Commit,
sign_settings: &SignSettings,
) -> BackendResult<Commit> {
let should_sign = store.signer().can_sign() && sign_settings.should_sign(&commit);
let sign_fn = |data: &[u8]| store.signer().sign(data, sign_settings.key.as_deref());
// Commit backend doesn't use secure_sig for writing and enforces it with an
// assert, but sign_settings.should_sign check above will want to know
// if we're rewriting a signed commit
commit.secure_sig = None;
store
.write_commit(commit, should_sign.then_some(&mut &sign_fn))
.await
}