commit_builder: add is_empty(), is_discardable(), and .abandon()

It's convenient to test if rewriter.reparent()-ed commit is empty than
implementing the same check based on rewriter.new_parents(). CommitRewriter
is an intermediate state, and it doesn't know whether the commit will be rebased
or reparented.
This commit is contained in:
Yuya Nishihara 2024-12-20 12:15:01 +09:00
parent f7fd523dd8
commit bb6b551a4c
3 changed files with 63 additions and 6 deletions

View file

@ -126,12 +126,7 @@ impl Commit {
/// Returns whether commit's content is empty. Commit description is not
/// taken into consideration.
pub fn is_empty(&self, repo: &dyn Repo) -> BackendResult<bool> {
let parents: Vec<_> = self.parents().try_collect()?;
if let [parent] = &parents[..] {
return Ok(parent.tree_id() == self.tree_id());
}
let parent_tree = merge_commit_trees(repo, &parents)?;
Ok(*self.tree_id() == parent_tree.id())
is_backend_commit_empty(repo, &self.store, &self.data)
}
pub fn has_conflict(&self) -> BackendResult<bool> {
@ -183,6 +178,23 @@ impl Commit {
}
}
pub(crate) fn is_backend_commit_empty(
repo: &dyn Repo,
store: &Arc<Store>,
commit: &backend::Commit,
) -> BackendResult<bool> {
if let [parent_id] = &*commit.parents {
return Ok(commit.root_tree == *store.get_commit(parent_id)?.tree_id());
}
let parents: Vec<_> = commit
.parents
.iter()
.map(|id| store.get_commit(id))
.try_collect()?;
let parent_tree = merge_commit_trees(repo, &parents)?;
Ok(commit.root_tree == parent_tree.id())
}
pub trait CommitIteratorExt<'c, I> {
fn ids(self) -> impl Iterator<Item = &'c CommitId>;
}

View file

@ -24,6 +24,7 @@ use crate::backend::ChangeId;
use crate::backend::CommitId;
use crate::backend::MergedTreeId;
use crate::backend::Signature;
use crate::commit::is_backend_commit_empty;
use crate::commit::Commit;
use crate::repo::MutableRepo;
use crate::repo::Repo;
@ -73,6 +74,11 @@ impl CommitBuilder<'_> {
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()
}
@ -114,6 +120,11 @@ impl CommitBuilder<'_> {
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()
}
@ -131,6 +142,12 @@ impl CommitBuilder<'_> {
pub fn write(self) -> BackendResult<Commit> {
self.inner.write(self.mut_repo)
}
/// 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`.
@ -254,6 +271,11 @@ impl DetachedCommitBuilder {
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
}
@ -295,6 +317,11 @@ impl DetachedCommitBuilder {
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
}
@ -328,6 +355,22 @@ impl DetachedCommitBuilder {
pub fn write_hidden(&self) -> BackendResult<Commit> {
write_to_store(&self.store, self.commit.clone(), &self.sign_settings)
}
/// 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 {
if rewrite_source.change_id() == &commit.change_id {
mut_repo.record_abandoned_commit_with_parents(
rewrite_source.id().clone(),
commit.parents,
);
}
}
}
}
fn write_to_store(

View file

@ -210,6 +210,8 @@ impl<'repo> CommitRewriter<'repo> {
}
/// Records the old commit as abandoned with the new parents.
///
/// This is equivalent to `reparent(settings).abandon()`, but is cheaper.
pub fn abandon(self) {
let old_commit_id = self.old_commit.id().clone();
let new_parents = self.new_parents;