Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gix/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::convert::Infallible;
/// An empty array of a type usable with the `gix::easy` API to help declaring no parents should be used
pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = [];

/// The error returned by [`commit(…)`][crate::Repository::commit()].
/// The error returned by [`commit(…)`](crate::Repository::commit()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
Expand Down
2 changes: 1 addition & 1 deletion gix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
doc = ::document_features::document_features!()
)]
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
#![deny(missing_docs, unsafe_code)]
#![allow(clippy::result_large_err)]

// Re-exports to make this a potential one-stop shop crate avoiding people from having to reference various crates themselves.
Expand Down
30 changes: 30 additions & 0 deletions gix/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ mod submodule;
mod thread_safe;
mod worktree;

///
mod new_commit {
/// The error returned by [`new_commit(…)`](crate::Repository::new_commit()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
ParseTime(#[from] crate::config::time::Error),
#[error("Committer identity is not configured")]
CommitterMissing,
#[error("Author identity is not configured")]
AuthorMissing,
#[error(transparent)]
NewCommitAs(#[from] crate::repository::new_commit_as::Error),
}
}

///
mod new_commit_as {
/// The error returned by [`new_commit_as(…)`](crate::Repository::new_commit_as()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
WriteObject(#[from] crate::object::write::Error),
#[error(transparent)]
FindCommit(#[from] crate::object::find::existing::Error),
}
}

///
#[cfg(feature = "blame")]
pub mod blame_file {
Expand Down
46 changes: 45 additions & 1 deletion gix/src/repository/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use gix_ref::{
};
use smallvec::SmallVec;

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

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

/// Create a new commit object with `message` referring to `tree` with `parents`, and write it to the object database.
/// Do not, however, update any references.
///
/// The commit is created without message encoding field, which can be assumed to be UTF-8.
/// `author` and `committer` fields are pre-set from the configuration, which can be altered
/// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
pub fn new_commit(
&self,
message: impl AsRef<str>,
tree: impl Into<ObjectId>,
parents: impl IntoIterator<Item = impl Into<ObjectId>>,
) -> Result<Commit<'_>, new_commit::Error> {
let author = self.author().ok_or(new_commit::Error::AuthorMissing)??;
let committer = self.committer().ok_or(new_commit::Error::CommitterMissing)??;
Ok(self.new_commit_as(committer, author, message, tree, parents)?)
}

/// Create a nwe commit object with `message` referring to `tree` with `parents`, using the specified
/// `committer` and `author`, and write it to the object database. Do not, however, update any references.
///
/// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.
/// The commit is created without message encoding field, which can be assumed to be UTF-8.
pub fn new_commit_as<'a, 'c>(
&self,
committer: impl Into<gix_actor::SignatureRef<'c>>,
author: impl Into<gix_actor::SignatureRef<'a>>,
message: impl AsRef<str>,
tree: impl Into<ObjectId>,
parents: impl IntoIterator<Item = impl Into<ObjectId>>,
) -> Result<Commit<'_>, new_commit_as::Error> {
let commit = gix_object::Commit {
message: message.as_ref().into(),
tree: tree.into(),
author: author.into().into(),
committer: committer.into().into(),
encoding: None,
parents: parents.into_iter().map(Into::into).collect(),
extra_headers: Default::default(),
};
let id = self.write_object(commit)?;
Ok(id.object()?.into_commit())
}

/// Return an empty tree object, suitable for [getting changes](Tree::changes()).
///
/// Note that the returned object is special and doesn't necessarily physically exist in the object database.
Expand Down
65 changes: 64 additions & 1 deletion gix/tests/gix/repository/object.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use gix_date::parse::TimeBuf;
use gix_odb::Header;
use gix_pack::Find;
use gix_testtools::tempfile;

use crate::util::named_subrepo_opts;
use crate::util::{hex_to_id, named_subrepo_opts};

mod object_database_impl {
use gix_object::{Exists, Find, FindHeader};
Expand Down Expand Up @@ -786,6 +787,68 @@ mod commit {
}
}

#[test]
fn new_commit_as() -> crate::Result {
let repo = empty_bare_in_memory_repo()?;
let empty_tree = repo.empty_tree();
let committer = gix::actor::Signature {
name: "c".into(),
email: "c@example.com".into(),
time: gix_date::parse_header("1 +0030").unwrap(),
};
let author = gix::actor::Signature {
name: "a".into(),
email: "a@example.com".into(),
time: gix_date::parse_header("3 +0100").unwrap(),
};

let commit = repo.new_commit_as(
committer.to_ref(&mut TimeBuf::default()),
author.to_ref(&mut TimeBuf::default()),
"message",
empty_tree.id,
gix::commit::NO_PARENT_IDS,
)?;

assert_eq!(
commit.id,
hex_to_id("b51277f2b2ea77676dd6fa877b5eb5ba2f7094d9"),
"The commit-id is stable as the author/committer is controlled"
);

let commit = commit.decode()?;

let mut buf = TimeBuf::default();
assert_eq!(commit.committer, committer.to_ref(&mut buf));
assert_eq!(commit.author, author.to_ref(&mut buf));
assert_eq!(commit.message, "message");
assert_eq!(commit.tree(), empty_tree.id);
assert_eq!(commit.parents.len(), 0);

assert!(repo.head()?.is_unborn(), "The head-ref wasn't touched");
Ok(())
}

#[test]
fn new_commit() -> crate::Result {
let mut repo = empty_bare_in_memory_repo()?;
let mut config = repo.config_snapshot_mut();
config.set_value(&gix::config::tree::User::NAME, "user")?;
config.set_value(&gix::config::tree::User::EMAIL, "user@example.com")?;
config.commit()?;

let empty_tree_id = repo.object_hash().empty_tree();
let commit = repo.new_commit("initial", empty_tree_id, gix::commit::NO_PARENT_IDS)?;
let commit = commit.decode()?;

assert_eq!(commit.message, "initial");
assert_eq!(commit.tree(), empty_tree_id);
assert_eq!(commit.parents.len(), 0);

assert!(repo.head()?.is_unborn(), "The head-ref wasn't touched");
Ok(())
}

fn empty_bare_in_memory_repo() -> crate::Result<gix::Repository> {
Ok(named_subrepo_opts("make_basic_repo.sh", "bare.git", gix::open::Options::isolated())?.with_object_memory())
}
Expand Down
Loading