From 31b6afa76937de56b5a055991394be3211ab15a3 Mon Sep 17 00:00:00 2001 From: Caleb Owens Date: Fri, 5 Dec 2025 17:20:29 +0100 Subject: [PATCH] Reimplement insert blank commit --- crates/but-rebase/src/graph_rebase/commit.rs | 33 +++- .../src/commit/insert_blank_commit.rs | 58 +++++++ crates/but-workspace/src/commit/mod.rs | 2 + .../insert-blank-commit-three-commits copy.sh | 30 ++++ .../workspace/commit/insert_blank_commit.rs | 141 ++++++++++++++++++ .../tests/workspace/commit/mod.rs | 1 + 6 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 crates/but-workspace/src/commit/insert_blank_commit.rs create mode 100644 crates/but-workspace/tests/fixtures/scenario/insert-blank-commit-three-commits copy.sh create mode 100644 crates/but-workspace/tests/workspace/commit/insert_blank_commit.rs diff --git a/crates/but-rebase/src/graph_rebase/commit.rs b/crates/but-rebase/src/graph_rebase/commit.rs index 2af466a1fd..4d32e60536 100644 --- a/crates/but-rebase/src/graph_rebase/commit.rs +++ b/crates/but-rebase/src/graph_rebase/commit.rs @@ -1,6 +1,6 @@ //! Provides some slightly higher level tools to help with manipulating commits, in preparation for use in the editor. -use anyhow::Result; +use anyhow::{Context as _, Result}; use gix::prelude::ObjectIdExt; use crate::{ @@ -22,4 +22,35 @@ impl Editor { ) -> Result { create(&self.repo, commit.inner, date_mode) } + + /// Creates a commit with only the signature and author set correctly. + /// + /// The ID of the commit is all zeros & the commit hasn't been written into any ODB + pub fn empty_commit(&self) -> Result> { + let kind = gix::hash::Kind::Sha1; + let committer = dbg!(self.repo.committer()) + .transpose()? + .context("Need committer to be configured when creating a new commit")? + .into(); + let author = self + .repo + .committer() + .transpose()? + .context("Need author to be configured when creating a new commit")? + .into(); + let obj = gix::objs::Commit { + tree: gix::ObjectId::empty_tree(kind), + parents: vec![].into(), + committer, + author, + encoding: None, + message: b"".into(), + extra_headers: vec![], + }; + + Ok(but_core::Commit::<'_> { + id: gix::ObjectId::null(kind).attach(&self.repo), + inner: obj, + }) + } } diff --git a/crates/but-workspace/src/commit/insert_blank_commit.rs b/crates/but-workspace/src/commit/insert_blank_commit.rs new file mode 100644 index 0000000000..d23baae21a --- /dev/null +++ b/crates/but-workspace/src/commit/insert_blank_commit.rs @@ -0,0 +1,58 @@ +//! Insertion of a blank commit + +use std::collections::HashMap; + +/// Describes where the blank commit should be inserted relative to. +#[derive(Debug, Clone)] +pub enum RelativeTo<'a> { + /// Relative to a commit + Commit(gix::ObjectId), + /// Relative to a reference + Reference(&'a gix::refs::FullNameRef), +} + +/// Describes the outcome of the rebase +#[derive(Debug, Clone)] +pub struct InsertCommitOutcome { + /// The blank commit that was creatd + pub blank_commit_id: gix::ObjectId, + /// Any commits that were mapped. + pub commit_mapping: HashMap, +} + +pub(crate) mod function { + use anyhow::Result; + use but_rebase::{ + commit::DateMode, + graph_rebase::{GraphExt, Step, mutate::InsertSide}, + }; + + use crate::commit::insert_blank_commit::{InsertCommitOutcome, RelativeTo}; + + /// Inserts a blank commit relative to either a reference or a commit + pub fn insert_blank_commit( + graph: &but_graph::Graph, + repo: &gix::Repository, + side: InsertSide, + relative_to: RelativeTo, + ) -> Result { + let mut editor = graph.to_editor(repo)?; + let target_selector = match relative_to { + RelativeTo::Commit(id) => editor.select_commit(id)?, + RelativeTo::Reference(r) => editor.select_reference(r)?, + }; + + let commit = editor.empty_commit()?; + let new_id = editor.write_commit(commit, DateMode::CommitterUpdateAuthorUpdate)?; + + editor.insert(&target_selector, Step::new_pick(new_id), side); + + let outcome = editor.rebase()?; + let mat_output = outcome.materialize()?; + + Ok(InsertCommitOutcome { + blank_commit_id: new_id, + commit_mapping: mat_output.commit_mapping, + }) + } +} diff --git a/crates/but-workspace/src/commit/mod.rs b/crates/but-workspace/src/commit/mod.rs index 84e9ad0bf8..5f02c18673 100644 --- a/crates/but-workspace/src/commit/mod.rs +++ b/crates/but-workspace/src/commit/mod.rs @@ -6,6 +6,8 @@ use crate::WorkspaceCommit; pub mod reword; pub use reword::function::reword; +pub mod insert_blank_commit; +pub use insert_blank_commit::function::insert_blank_commit; /// A minimal stack for use by [WorkspaceCommit::new_from_stacks()]. #[derive(Clone)] diff --git a/crates/but-workspace/tests/fixtures/scenario/insert-blank-commit-three-commits copy.sh b/crates/but-workspace/tests/fixtures/scenario/insert-blank-commit-three-commits copy.sh new file mode 100644 index 0000000000..8753861fa6 --- /dev/null +++ b/crates/but-workspace/tests/fixtures/scenario/insert-blank-commit-three-commits copy.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +echo "/remote/" > .gitignore +mkdir remote +pushd remote +git init +popd + +git init + +git remote add origin ./remote + +git checkout -b one +echo "foo" > one.txt +git add . +git commit -m "commit one" + +git checkout -b two +echo "foo" > two.txt +git add . +git commit -m "commit two" + +git push -u origin two + +git checkout -b three +echo "foo" > three.txt +git add . +git commit -m "commit three" diff --git a/crates/but-workspace/tests/workspace/commit/insert_blank_commit.rs b/crates/but-workspace/tests/workspace/commit/insert_blank_commit.rs new file mode 100644 index 0000000000..fb290e1d97 --- /dev/null +++ b/crates/but-workspace/tests/workspace/commit/insert_blank_commit.rs @@ -0,0 +1,141 @@ +use anyhow::Result; +use but_rebase::graph_rebase::mutate::InsertSide; +use but_testsupport::{assure_stable_env, visualize_commit_graph_all}; +use but_workspace::commit::insert_blank_commit; +use but_workspace::commit::insert_blank_commit::RelativeTo; + +use crate::ref_info::with_workspace_commit::utils::named_writable_scenario_with_description_and_graph as writable_scenario; + +#[test] +fn insert_below_commit() -> Result<()> { + assure_stable_env(); + let (_tmp, graph, repo, mut _meta, _description) = + writable_scenario("reword-three-commits", |_| {})?; + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * c9f444c (HEAD -> three) commit three + * 16fd221 (origin/two, two) commit two + * 8b426d0 (one) commit one + "); + + let head_tree = repo.head_tree_id()?; + let id = repo.rev_parse_single("two")?; + + insert_blank_commit( + &graph, + &repo, + InsertSide::Below, + RelativeTo::Commit(id.detach()), + )?; + + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * 31dbfdc (HEAD -> three) commit three + * b65c813 (two) commit two + * d2b480d + | * 16fd221 (origin/two) commit two + |/ + * 8b426d0 (one) commit one + "); + + assert_eq!(head_tree, repo.head_tree_id()?); + + Ok(()) +} + +#[test] +fn insert_above_commit() -> Result<()> { + assure_stable_env(); + let (_tmp, graph, repo, mut _meta, _description) = + writable_scenario("reword-three-commits", |_| {})?; + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * c9f444c (HEAD -> three) commit three + * 16fd221 (origin/two, two) commit two + * 8b426d0 (one) commit one + "); + + let head_tree = repo.head_tree_id()?; + let id = repo.rev_parse_single("two")?; + + insert_blank_commit( + &graph, + &repo, + InsertSide::Above, + RelativeTo::Commit(id.detach()), + )?; + + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * 923c9cd (HEAD -> three) commit three + * 8bf04f0 (two) + * 16fd221 (origin/two) commit two + * 8b426d0 (one) commit one + "); + + assert_eq!(head_tree, repo.head_tree_id()?); + + Ok(()) +} + +#[test] +fn insert_below_reference() -> Result<()> { + assure_stable_env(); + let (_tmp, graph, repo, mut _meta, _description) = + writable_scenario("reword-three-commits", |_| {})?; + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * c9f444c (HEAD -> three) commit three + * 16fd221 (origin/two, two) commit two + * 8b426d0 (one) commit one + "); + + let head_tree = repo.head_tree_id()?; + let reference = repo.find_reference("two")?; + + insert_blank_commit( + &graph, + &repo, + InsertSide::Below, + RelativeTo::Reference(reference.name()), + )?; + + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * 923c9cd (HEAD -> three) commit three + * 8bf04f0 (two) + * 16fd221 (origin/two) commit two + * 8b426d0 (one) commit one + "); + + assert_eq!(head_tree, repo.head_tree_id()?); + + Ok(()) +} + +#[test] +fn insert_above_reference() -> Result<()> { + assure_stable_env(); + let (_tmp, graph, repo, mut _meta, _description) = + writable_scenario("reword-three-commits", |_| {})?; + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * c9f444c (HEAD -> three) commit three + * 16fd221 (origin/two, two) commit two + * 8b426d0 (one) commit one + "); + + let head_tree = repo.head_tree_id()?; + let reference = repo.find_reference("two")?; + + insert_blank_commit( + &graph, + &repo, + InsertSide::Above, + RelativeTo::Reference(reference.name()), + )?; + + insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r" + * 923c9cd (HEAD -> three) commit three + * 8bf04f0 + * 16fd221 (origin/two, two) commit two + * 8b426d0 (one) commit one + "); + + assert_eq!(head_tree, repo.head_tree_id()?); + + Ok(()) +} diff --git a/crates/but-workspace/tests/workspace/commit/mod.rs b/crates/but-workspace/tests/workspace/commit/mod.rs index cbaa1c7560..6c79129c42 100644 --- a/crates/but-workspace/tests/workspace/commit/mod.rs +++ b/crates/but-workspace/tests/workspace/commit/mod.rs @@ -1,3 +1,4 @@ +mod insert_blank_commit; mod reword; mod from_new_merge_with_metadata {