Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvements #1837

Merged
merged 6 commits into from
Feb 14, 2025
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
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
Loading