Skip to content

Commit

Permalink
Merge pull request #1837 from GitoxideLabs/improvements
Browse files Browse the repository at this point in the history
improvements
  • Loading branch information
Byron authored Feb 14, 2025
2 parents b310c16 + de4375a commit b4fe425
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 28 deletions.
21 changes: 20 additions & 1 deletion gix-index/src/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,8 @@ impl State {

/// Physically remove all entries for which `should_remove(idx, path, entry)` returns `true`, traversing them from first to last.
///
/// Note that the memory used for the removed entries paths is not freed, as it's append-only.
/// Note that the memory used for the removed entries paths is not freed, as it's append-only, and
/// that some extensions might refer to paths which are now deleted.
///
/// ### Performance
///
Expand All @@ -534,6 +535,16 @@ impl State {
res
});
}

/// Physically remove the entry at `index`, or panic if the entry didn't exist.
///
/// This call is typically made after looking up `index`, so it's clear that it will not panic.
///
/// Note that the memory used for the removed entries paths is not freed, as it's append-only, and
/// that some extensions might refer to paths which are now deleted.
pub fn remove_entry_at_index(&mut self, index: usize) -> Entry {
self.entries.remove(index)
}
}

/// Extensions
Expand All @@ -542,6 +553,10 @@ impl State {
pub fn tree(&self) -> Option<&extension::Tree> {
self.tree.as_ref()
}
/// Remove the `tree` extension.
pub fn remove_tree(&mut self) -> Option<extension::Tree> {
self.tree.take()
}
/// Access the `link` extension.
pub fn link(&self) -> Option<&extension::Link> {
self.link.as_ref()
Expand All @@ -550,6 +565,10 @@ impl State {
pub fn resolve_undo(&self) -> Option<&extension::resolve_undo::Paths> {
self.resolve_undo.as_ref()
}
/// Remove the resolve-undo extension.
pub fn remove_resolve_undo(&mut self) -> Option<extension::resolve_undo::Paths> {
self.resolve_undo.take()
}
/// Obtain the untracked extension.
pub fn untracked(&self) -> Option<&extension::UntrackedCache> {
self.untracked.as_ref()
Expand Down
16 changes: 14 additions & 2 deletions gix-index/src/entry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,21 @@ mod access {
mod _impls {
use std::cmp::Ordering;

use crate::{entry, Entry, State};
use bstr::BStr;

use crate::{Entry, State};
use gix_object::tree::EntryKind;

impl From<EntryKind> for entry::Mode {
fn from(value: EntryKind) -> Self {
match value {
EntryKind::Tree => entry::Mode::DIR,
EntryKind::Blob => entry::Mode::FILE,
EntryKind::BlobExecutable => entry::Mode::FILE_EXECUTABLE,
EntryKind::Link => entry::Mode::SYMLINK,
EntryKind::Commit => entry::Mode::COMMIT,
}
}
}

impl Entry {
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
Expand Down
12 changes: 12 additions & 0 deletions gix-index/tests/index/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,18 @@ fn remove_entries() {
file.remove_entries(|_, _, _| unreachable!("should not be called"));
}

#[test]
fn remove_entry_at_index() {
let mut file = Fixture::Loose("conflicting-file").open();

file.remove_entry_at_index(0);
assert_eq!(file.entries().len(), 2);
file.remove_entry_at_index(0);
assert_eq!(file.entries().len(), 1);
file.remove_entry_at_index(0);
assert_eq!(file.entries().len(), 0);
}

#[test]
fn sort_entries() {
let mut file = Fixture::Generated("v4_more_files_IEOT").open();
Expand Down
5 changes: 5 additions & 0 deletions gix-object/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ impl BlobRef<'_> {
pub fn from_bytes(data: &[u8]) -> Result<BlobRef<'_>, Infallible> {
Ok(BlobRef { data })
}

/// Clone the data in this instance by allocating a new vector for a fully owned blob.
pub fn into_owned(self) -> Blob {
self.into()
}
}
30 changes: 28 additions & 2 deletions gix-object/src/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use winnow::prelude::*;

use crate::{Commit, CommitRef, TagRef};

/// The well-known field name for gpg signatures.
pub const SIGNATURE_FIELD_NAME: &str = "gpgsig";

mod decode;
///
pub mod message;
Expand Down Expand Up @@ -84,7 +87,7 @@ impl<'a> CommitRef<'a> {
}

/// Returns a convenient iterator over all extra headers.
pub fn extra_headers(&self) -> crate::commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))
}

Expand Down Expand Up @@ -113,6 +116,19 @@ impl<'a> CommitRef<'a> {
}
}

/// Conversion
impl CommitRef<'_> {
/// Copy all fields of this instance into a fully owned commit, consuming this instance.
pub fn into_owned(self) -> Commit {
self.into()
}

/// Copy all fields of this instance into a fully owned commit, internally cloning this instance.
pub fn to_owned(self) -> Commit {
self.clone().into()
}
}

impl Commit {
/// Returns a convenient iterator over all extra headers.
pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
Expand All @@ -134,16 +150,26 @@ where
pub fn new(iter: I) -> Self {
ExtraHeaders { inner: iter }
}

/// Find the _value_ of the _first_ header with the given `name`.
pub fn find(mut self, name: &str) -> Option<&'a BStr> {
self.inner
.find_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
}

/// Find the entry index with the given name, or return `None` if unavailable.
pub fn find_pos(self, name: &str) -> Option<usize> {
self.inner
.enumerate()
.find_map(|(pos, (field, _value))| (field == name).then_some(pos))
}

/// Return an iterator over all _values_ of headers with the given `name`.
pub fn find_all(self, name: &'a str) -> impl Iterator<Item = &'a BStr> {
self.inner
.filter_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
}

/// Return an iterator over all git mergetags.
///
/// A merge tag is a tag object embedded within the respective header field of a commit, making
Expand All @@ -154,6 +180,6 @@ where

/// Return the cryptographic signature provided by gpg/pgp verbatim.
pub fn pgp_signature(self) -> Option<&'a BStr> {
self.find("gpgsig")
self.find(SIGNATURE_FIELD_NAME)
}
}
32 changes: 16 additions & 16 deletions gix-object/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,23 @@ pub enum Kind {
Commit,
Tag,
}
/// A chunk of any [`data`][BlobRef::data].
/// A chunk of any [`data`](BlobRef::data).
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobRef<'a> {
/// The bytes themselves.
pub data: &'a [u8],
}

/// A mutable chunk of any [`data`][Blob::data].
/// A mutable chunk of any [`data`](Blob::data).
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Blob {
/// The data itself.
pub data: Vec<u8>,
}

/// A git commit parsed using [`from_bytes()`][CommitRef::from_bytes()].
/// A git commit parsed using [`from_bytes()`](CommitRef::from_bytes()).
///
/// A commit encapsulates information about a point in time at which the state of the repository is recorded, usually after a
/// change which is documented in the commit `message`.
Expand All @@ -83,18 +83,18 @@ pub struct Blob {
pub struct CommitRef<'a> {
/// HEX hash of tree object we point to. Usually 40 bytes long.
///
/// Use [`tree()`][CommitRef::tree()] to obtain a decoded version of it.
/// Use [`tree()`](CommitRef::tree()) to obtain a decoded version of it.
#[cfg_attr(feature = "serde", serde(borrow))]
pub tree: &'a BStr,
/// HEX hash of each parent commit. Empty for first commit in repository.
pub parents: SmallVec<[&'a BStr; 1]>,
/// Who wrote this commit. Name and email might contain whitespace and are not trimmed to ensure round-tripping.
///
/// Use the [`author()`][CommitRef::author()] method to received a trimmed version of it.
/// Use the [`author()`](CommitRef::author()) method to received a trimmed version of it.
pub author: gix_actor::SignatureRef<'a>,
/// Who committed this commit. Name and email might contain whitespace and are not trimmed to ensure round-tripping.
///
/// Use the [`committer()`][CommitRef::committer()] method to received a trimmed version of it.
/// Use the [`committer()`](CommitRef::committer()) method to received a trimmed version of it.
///
/// This may be different from the `author` in case the author couldn't write to the repository themselves and
/// is commonly encountered with contributed commits.
Expand All @@ -103,7 +103,7 @@ pub struct CommitRef<'a> {
pub encoding: Option<&'a BStr>,
/// The commit message documenting the change.
pub message: &'a BStr,
/// Extra header fields, in order of them being encountered, made accessible with the iterator returned by [`extra_headers()`][CommitRef::extra_headers()].
/// Extra header fields, in order of them being encountered, made accessible with the iterator returned by [`extra_headers()`](CommitRef::extra_headers()).
pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>,
}

Expand Down Expand Up @@ -135,15 +135,15 @@ pub struct Commit {
/// The commit message documenting the change.
pub message: BString,
/// Extra header fields, in order of them being encountered, made accessible with the iterator returned
/// by [`extra_headers()`][Commit::extra_headers()].
/// by [`extra_headers()`](Commit::extra_headers()).
pub extra_headers: Vec<(BString, BString)>,
}

/// Represents a git tag, commonly indicating a software release.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TagRef<'a> {
/// The hash in hexadecimal being the object this tag points to. Use [`target()`][TagRef::target()] to obtain a byte representation.
/// The hash in hexadecimal being the object this tag points to. Use [`target()`](TagRef::target()) to obtain a byte representation.
#[cfg_attr(feature = "serde", serde(borrow))]
pub target: &'a BStr,
/// The kind of object that `target` points to.
Expand Down Expand Up @@ -184,13 +184,13 @@ pub struct Tag {
pub pgp_signature: Option<BString>,
}

/// Immutable objects are read-only structures referencing most data from [a byte slice][crate::ObjectRef::from_bytes()].
/// Immutable objects are read-only structures referencing most data from [a byte slice](ObjectRef::from_bytes()).
///
/// Immutable objects are expected to be deserialized from bytes that acts as backing store, and they
/// cannot be mutated or serialized. Instead, one will [convert][crate::ObjectRef::into_owned()] them into their [`mutable`][Object] counterparts
/// cannot be mutated or serialized. Instead, one will [convert](ObjectRef::into_owned()) them into their [`mutable`](Object) counterparts
/// which support mutation and serialization.
///
/// An `ObjectRef` is representing [`Trees`][TreeRef], [`Blobs`][BlobRef], [`Commits`][CommitRef], or [`Tags`][TagRef].
/// An `ObjectRef` is representing [`Trees`](TreeRef), [`Blobs`](BlobRef), [`Commits`](CommitRef), or [`Tags`](TagRef).
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)]
Expand All @@ -206,10 +206,10 @@ pub enum ObjectRef<'a> {
///
/// Mutable objects are Commits, Trees, Blobs and Tags that can be changed and serialized.
///
/// They either created using object [construction][Object] or by [deserializing existing objects][ObjectRef::from_bytes()]
/// and converting these [into mutable copies][ObjectRef::into_owned()] for adjustments.
/// They either created using object [construction](Object) or by [deserializing existing objects](ObjectRef::from_bytes())
/// and converting these [into mutable copies](ObjectRef::into_owned()) for adjustments.
///
/// An `Object` is representing [`Trees`][Tree], [`Blobs`][Blob], [`Commits`][Commit] or [`Tags`][Tag].
/// An `Object` is representing [`Trees`](Tree), [`Blobs`](Blob), [`Commits`](Commit), or [`Tags`](Tag).
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(clippy::large_enum_variant, missing_docs)]
Expand Down
5 changes: 5 additions & 0 deletions gix-object/src/tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ impl<'a> TagRef<'a> {
pub fn target(&self) -> gix_hash::ObjectId {
gix_hash::ObjectId::from_hex(self.target).expect("prior validation")
}

/// Copy all data into a fully-owned instance.
pub fn into_owned(self) -> crate::Tag {
self.into()
}
}
16 changes: 10 additions & 6 deletions gix-object/tests/object/commit/from_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use gix_actor::SignatureRef;
use gix_date::{time::Sign, Time};
use gix_object::{bstr::ByteSlice, commit::message::body::TrailerRef, CommitRef};
use smallvec::SmallVec;

use crate::{
commit::{LONG_MESSAGE, MERGE_TAG, SIGNATURE},
fixture_name, linus_signature, signature,
};
use gix_actor::SignatureRef;
use gix_date::{time::Sign, Time};
use gix_object::{bstr::ByteSlice, commit::message::body::TrailerRef, CommitRef};
use smallvec::SmallVec;

#[test]
fn invalid_timestsamp() {
Expand Down Expand Up @@ -354,7 +353,12 @@ fn newline_right_after_signature_multiline_header() -> crate::Result {
let pgp_sig = crate::commit::OTHER_SIGNATURE.as_bstr();
assert_eq!(commit.extra_headers[0].1.as_ref(), pgp_sig);
assert_eq!(commit.extra_headers().pgp_signature(), Some(pgp_sig));
assert_eq!(commit.extra_headers().find("gpgsig"), Some(pgp_sig));
assert_eq!(
commit.extra_headers().find(gix_object::commit::SIGNATURE_FIELD_NAME),
Some(pgp_sig)
);
assert_eq!(commit.extra_headers().find_pos("gpgsig"), Some(0));
assert_eq!(commit.extra_headers().find_pos("something else"), None);
assert!(commit.message.starts_with(b"Rollup"));
Ok(())
}
16 changes: 16 additions & 0 deletions gix-object/tests/object/tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use gix_object::{bstr::ByteSlice, Kind, TagRef, TagRefIter};
use crate::fixture_name;

mod method {
use bstr::ByteSlice;
use gix_object::TagRef;
use pretty_assertions::assert_eq;

Expand All @@ -15,6 +16,21 @@ mod method {
let tag = TagRef::from_bytes(&fixture)?;
assert_eq!(tag.target(), hex_to_id("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec"));
assert_eq!(tag.target, "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bytes());

let gix_object::Tag {
target,
target_kind,
name,
tagger,
message,
pgp_signature,
} = tag.into_owned();
assert_eq!(target.to_string(), tag.target);
assert_eq!(target_kind, tag.target_kind);
assert_eq!(name, tag.name);
assert_eq!(tagger.as_ref().map(|s| s.to_ref()), tag.tagger);
assert_eq!(message, tag.message);
assert_eq!(pgp_signature.as_ref().map(|s| s.as_bstr()), tag.pgp_signature);
Ok(())
}
}
Expand Down
7 changes: 6 additions & 1 deletion gix/src/repository/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl crate::Repository {
/// Create a new commit object with `message` referring to `tree` with `parents`, and point `reference`
/// to it. The commit is written 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.
/// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
///
/// `reference` will be created if it doesn't exist, and can be `"HEAD"` to automatically write-through to the symbolic reference
/// that `HEAD` points to if it is not detached. For this reason, detached head states cannot be created unless the `HEAD` is detached
Expand All @@ -352,6 +352,11 @@ impl crate::Repository {
/// If there is no parent, the `reference` is expected to not exist yet.
///
/// The method fails immediately if a `reference` lock can't be acquired.
///
/// ### Writing a commit without `reference` update
///
/// If the reference shouldn't be updated, use [`Self::write_object()`] along with a newly created [`crate::objs::Object`] whose fields
/// can be fully defined.
pub fn commit<Name, E>(
&self,
reference: Name,
Expand Down

0 comments on commit b4fe425

Please sign in to comment.