Skip to content

Commit f891c37

Browse files
authored
Merge pull request #2168 from GitoxideLabs/copilot/fix-01a02b99-91ef-4e27-b90f-19af7d0d252c
Add `commit_raw` and `commit_as_raw` methods for creating commits without reference updates
2 parents 1a4c84d + d4c2542 commit f891c37

File tree

5 files changed

+141
-4
lines changed

5 files changed

+141
-4
lines changed

gix/src/commit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::convert::Infallible;
66
/// An empty array of a type usable with the `gix::easy` API to help declaring no parents should be used
77
pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = [];
88

9-
/// The error returned by [`commit(…)`][crate::Repository::commit()].
9+
/// The error returned by [`commit(…)`](crate::Repository::commit()).
1010
#[derive(Debug, thiserror::Error)]
1111
#[allow(missing_docs)]
1212
pub enum Error {

gix/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
doc = ::document_features::document_features!()
8787
)]
8888
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
89-
#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
89+
#![deny(missing_docs, unsafe_code)]
9090
#![allow(clippy::result_large_err)]
9191

9292
// Re-exports to make this a potential one-stop shop crate avoiding people from having to reference various crates themselves.

gix/src/repository/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ mod submodule;
6363
mod thread_safe;
6464
mod worktree;
6565

66+
///
67+
mod new_commit {
68+
/// The error returned by [`new_commit(…)`](crate::Repository::new_commit()).
69+
#[derive(Debug, thiserror::Error)]
70+
#[allow(missing_docs)]
71+
pub enum Error {
72+
#[error(transparent)]
73+
ParseTime(#[from] crate::config::time::Error),
74+
#[error("Committer identity is not configured")]
75+
CommitterMissing,
76+
#[error("Author identity is not configured")]
77+
AuthorMissing,
78+
#[error(transparent)]
79+
NewCommitAs(#[from] crate::repository::new_commit_as::Error),
80+
}
81+
}
82+
83+
///
84+
mod new_commit_as {
85+
/// The error returned by [`new_commit_as(…)`](crate::Repository::new_commit_as()).
86+
#[derive(Debug, thiserror::Error)]
87+
#[allow(missing_docs)]
88+
pub enum Error {
89+
#[error(transparent)]
90+
WriteObject(#[from] crate::object::write::Error),
91+
#[error(transparent)]
92+
FindCommit(#[from] crate::object::find::existing::Error),
93+
}
94+
}
95+
6696
///
6797
#[cfg(feature = "blame")]
6898
pub mod blame_file {

gix/src/repository/object.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use gix_ref::{
1010
};
1111
use smallvec::SmallVec;
1212

13+
use crate::repository::{new_commit, new_commit_as};
1314
use crate::{commit, ext::ObjectIdExt, object, tag, Blob, Commit, Id, Object, Reference, Tag, Tree};
1415

1516
/// Tree editing
@@ -264,7 +265,7 @@ impl crate::Repository {
264265
/// Create a tag reference named `name` (without `refs/tags/` prefix) pointing to a newly created tag object
265266
/// which in turn points to `target` and return the newly created reference.
266267
///
267-
/// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist]
268+
/// It will be created with `constraint` which is most commonly to [only create it](PreviousValue::MustNotExist)
268269
/// or to [force overwriting a possibly existing tag](PreviousValue::Any).
269270
pub fn tag(
270271
&self,
@@ -406,6 +407,49 @@ impl crate::Repository {
406407
self.commit_as(committer, author, reference, message, tree, parents)
407408
}
408409

410+
/// Create a new commit object with `message` referring to `tree` with `parents`, and write it to the object database.
411+
/// Do not, however, update any references.
412+
///
413+
/// The commit is created without message encoding field, which can be assumed to be UTF-8.
414+
/// `author` and `committer` fields are pre-set from the configuration, which can be altered
415+
/// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
416+
pub fn new_commit(
417+
&self,
418+
message: impl AsRef<str>,
419+
tree: impl Into<ObjectId>,
420+
parents: impl IntoIterator<Item = impl Into<ObjectId>>,
421+
) -> Result<Commit<'_>, new_commit::Error> {
422+
let author = self.author().ok_or(new_commit::Error::AuthorMissing)??;
423+
let committer = self.committer().ok_or(new_commit::Error::CommitterMissing)??;
424+
Ok(self.new_commit_as(committer, author, message, tree, parents)?)
425+
}
426+
427+
/// Create a nwe commit object with `message` referring to `tree` with `parents`, using the specified
428+
/// `committer` and `author`, and write it to the object database. Do not, however, update any references.
429+
///
430+
/// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.
431+
/// The commit is created without message encoding field, which can be assumed to be UTF-8.
432+
pub fn new_commit_as<'a, 'c>(
433+
&self,
434+
committer: impl Into<gix_actor::SignatureRef<'c>>,
435+
author: impl Into<gix_actor::SignatureRef<'a>>,
436+
message: impl AsRef<str>,
437+
tree: impl Into<ObjectId>,
438+
parents: impl IntoIterator<Item = impl Into<ObjectId>>,
439+
) -> Result<Commit<'_>, new_commit_as::Error> {
440+
let commit = gix_object::Commit {
441+
message: message.as_ref().into(),
442+
tree: tree.into(),
443+
author: author.into().into(),
444+
committer: committer.into().into(),
445+
encoding: None,
446+
parents: parents.into_iter().map(Into::into).collect(),
447+
extra_headers: Default::default(),
448+
};
449+
let id = self.write_object(commit)?;
450+
Ok(id.object()?.into_commit())
451+
}
452+
409453
/// Return an empty tree object, suitable for [getting changes](Tree::changes()).
410454
///
411455
/// Note that the returned object is special and doesn't necessarily physically exist in the object database.

gix/tests/gix/repository/object.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use gix_date::parse::TimeBuf;
12
use gix_odb::Header;
23
use gix_pack::Find;
34
use gix_testtools::tempfile;
45

5-
use crate::util::named_subrepo_opts;
6+
use crate::util::{hex_to_id, named_subrepo_opts};
67

78
mod object_database_impl {
89
use gix_object::{Exists, Find, FindHeader};
@@ -786,6 +787,68 @@ mod commit {
786787
}
787788
}
788789

790+
#[test]
791+
fn new_commit_as() -> crate::Result {
792+
let repo = empty_bare_in_memory_repo()?;
793+
let empty_tree = repo.empty_tree();
794+
let committer = gix::actor::Signature {
795+
name: "c".into(),
796+
email: "[email protected]".into(),
797+
time: gix_date::parse_header("1 +0030").unwrap(),
798+
};
799+
let author = gix::actor::Signature {
800+
name: "a".into(),
801+
email: "[email protected]".into(),
802+
time: gix_date::parse_header("3 +0100").unwrap(),
803+
};
804+
805+
let commit = repo.new_commit_as(
806+
committer.to_ref(&mut TimeBuf::default()),
807+
author.to_ref(&mut TimeBuf::default()),
808+
"message",
809+
empty_tree.id,
810+
gix::commit::NO_PARENT_IDS,
811+
)?;
812+
813+
assert_eq!(
814+
commit.id,
815+
hex_to_id("b51277f2b2ea77676dd6fa877b5eb5ba2f7094d9"),
816+
"The commit-id is stable as the author/committer is controlled"
817+
);
818+
819+
let commit = commit.decode()?;
820+
821+
let mut buf = TimeBuf::default();
822+
assert_eq!(commit.committer, committer.to_ref(&mut buf));
823+
assert_eq!(commit.author, author.to_ref(&mut buf));
824+
assert_eq!(commit.message, "message");
825+
assert_eq!(commit.tree(), empty_tree.id);
826+
assert_eq!(commit.parents.len(), 0);
827+
828+
assert!(repo.head()?.is_unborn(), "The head-ref wasn't touched");
829+
Ok(())
830+
}
831+
832+
#[test]
833+
fn new_commit() -> crate::Result {
834+
let mut repo = empty_bare_in_memory_repo()?;
835+
let mut config = repo.config_snapshot_mut();
836+
config.set_value(&gix::config::tree::User::NAME, "user")?;
837+
config.set_value(&gix::config::tree::User::EMAIL, "[email protected]")?;
838+
config.commit()?;
839+
840+
let empty_tree_id = repo.object_hash().empty_tree();
841+
let commit = repo.new_commit("initial", empty_tree_id, gix::commit::NO_PARENT_IDS)?;
842+
let commit = commit.decode()?;
843+
844+
assert_eq!(commit.message, "initial");
845+
assert_eq!(commit.tree(), empty_tree_id);
846+
assert_eq!(commit.parents.len(), 0);
847+
848+
assert!(repo.head()?.is_unborn(), "The head-ref wasn't touched");
849+
Ok(())
850+
}
851+
789852
fn empty_bare_in_memory_repo() -> crate::Result<gix::Repository> {
790853
Ok(named_subrepo_opts("make_basic_repo.sh", "bare.git", gix::open::Options::isolated())?.with_object_memory())
791854
}

0 commit comments

Comments
 (0)