Skip to content

Commit b4fe425

Browse files
authored
Merge pull request #1837 from GitoxideLabs/improvements
improvements
2 parents b310c16 + de4375a commit b4fe425

File tree

10 files changed

+132
-28
lines changed

10 files changed

+132
-28
lines changed

gix-index/src/access/mod.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,8 @@ impl State {
518518

519519
/// Physically remove all entries for which `should_remove(idx, path, entry)` returns `true`, traversing them from first to last.
520520
///
521-
/// Note that the memory used for the removed entries paths is not freed, as it's append-only.
521+
/// Note that the memory used for the removed entries paths is not freed, as it's append-only, and
522+
/// that some extensions might refer to paths which are now deleted.
522523
///
523524
/// ### Performance
524525
///
@@ -534,6 +535,16 @@ impl State {
534535
res
535536
});
536537
}
538+
539+
/// Physically remove the entry at `index`, or panic if the entry didn't exist.
540+
///
541+
/// This call is typically made after looking up `index`, so it's clear that it will not panic.
542+
///
543+
/// Note that the memory used for the removed entries paths is not freed, as it's append-only, and
544+
/// that some extensions might refer to paths which are now deleted.
545+
pub fn remove_entry_at_index(&mut self, index: usize) -> Entry {
546+
self.entries.remove(index)
547+
}
537548
}
538549

539550
/// Extensions
@@ -542,6 +553,10 @@ impl State {
542553
pub fn tree(&self) -> Option<&extension::Tree> {
543554
self.tree.as_ref()
544555
}
556+
/// Remove the `tree` extension.
557+
pub fn remove_tree(&mut self) -> Option<extension::Tree> {
558+
self.tree.take()
559+
}
545560
/// Access the `link` extension.
546561
pub fn link(&self) -> Option<&extension::Link> {
547562
self.link.as_ref()
@@ -550,6 +565,10 @@ impl State {
550565
pub fn resolve_undo(&self) -> Option<&extension::resolve_undo::Paths> {
551566
self.resolve_undo.as_ref()
552567
}
568+
/// Remove the resolve-undo extension.
569+
pub fn remove_resolve_undo(&mut self) -> Option<extension::resolve_undo::Paths> {
570+
self.resolve_undo.take()
571+
}
553572
/// Obtain the untracked extension.
554573
pub fn untracked(&self) -> Option<&extension::UntrackedCache> {
555574
self.untracked.as_ref()

gix-index/src/entry/mod.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,21 @@ mod access {
107107
mod _impls {
108108
use std::cmp::Ordering;
109109

110+
use crate::{entry, Entry, State};
110111
use bstr::BStr;
111-
112-
use crate::{Entry, State};
112+
use gix_object::tree::EntryKind;
113+
114+
impl From<EntryKind> for entry::Mode {
115+
fn from(value: EntryKind) -> Self {
116+
match value {
117+
EntryKind::Tree => entry::Mode::DIR,
118+
EntryKind::Blob => entry::Mode::FILE,
119+
EntryKind::BlobExecutable => entry::Mode::FILE_EXECUTABLE,
120+
EntryKind::Link => entry::Mode::SYMLINK,
121+
EntryKind::Commit => entry::Mode::COMMIT,
122+
}
123+
}
124+
}
113125

114126
impl Entry {
115127
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to

gix-index/tests/index/access.rs

+12
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,18 @@ fn remove_entries() {
214214
file.remove_entries(|_, _, _| unreachable!("should not be called"));
215215
}
216216

217+
#[test]
218+
fn remove_entry_at_index() {
219+
let mut file = Fixture::Loose("conflicting-file").open();
220+
221+
file.remove_entry_at_index(0);
222+
assert_eq!(file.entries().len(), 2);
223+
file.remove_entry_at_index(0);
224+
assert_eq!(file.entries().len(), 1);
225+
file.remove_entry_at_index(0);
226+
assert_eq!(file.entries().len(), 0);
227+
}
228+
217229
#[test]
218230
fn sort_entries() {
219231
let mut file = Fixture::Generated("v4_more_files_IEOT").open();

gix-object/src/blob.rs

+5
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,9 @@ impl BlobRef<'_> {
4444
pub fn from_bytes(data: &[u8]) -> Result<BlobRef<'_>, Infallible> {
4545
Ok(BlobRef { data })
4646
}
47+
48+
/// Clone the data in this instance by allocating a new vector for a fully owned blob.
49+
pub fn into_owned(self) -> Blob {
50+
self.into()
51+
}
4752
}

gix-object/src/commit/mod.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use winnow::prelude::*;
55

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

8+
/// The well-known field name for gpg signatures.
9+
pub const SIGNATURE_FIELD_NAME: &str = "gpgsig";
10+
811
mod decode;
912
///
1013
pub mod message;
@@ -84,7 +87,7 @@ impl<'a> CommitRef<'a> {
8487
}
8588

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

@@ -113,6 +116,19 @@ impl<'a> CommitRef<'a> {
113116
}
114117
}
115118

119+
/// Conversion
120+
impl CommitRef<'_> {
121+
/// Copy all fields of this instance into a fully owned commit, consuming this instance.
122+
pub fn into_owned(self) -> Commit {
123+
self.into()
124+
}
125+
126+
/// Copy all fields of this instance into a fully owned commit, internally cloning this instance.
127+
pub fn to_owned(self) -> Commit {
128+
self.clone().into()
129+
}
130+
}
131+
116132
impl Commit {
117133
/// Returns a convenient iterator over all extra headers.
118134
pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
@@ -134,16 +150,26 @@ where
134150
pub fn new(iter: I) -> Self {
135151
ExtraHeaders { inner: iter }
136152
}
153+
137154
/// Find the _value_ of the _first_ header with the given `name`.
138155
pub fn find(mut self, name: &str) -> Option<&'a BStr> {
139156
self.inner
140157
.find_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
141158
}
159+
160+
/// Find the entry index with the given name, or return `None` if unavailable.
161+
pub fn find_pos(self, name: &str) -> Option<usize> {
162+
self.inner
163+
.enumerate()
164+
.find_map(|(pos, (field, _value))| (field == name).then_some(pos))
165+
}
166+
142167
/// Return an iterator over all _values_ of headers with the given `name`.
143168
pub fn find_all(self, name: &'a str) -> impl Iterator<Item = &'a BStr> {
144169
self.inner
145170
.filter_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
146171
}
172+
147173
/// Return an iterator over all git mergetags.
148174
///
149175
/// A merge tag is a tag object embedded within the respective header field of a commit, making
@@ -154,6 +180,6 @@ where
154180

155181
/// Return the cryptographic signature provided by gpg/pgp verbatim.
156182
pub fn pgp_signature(self) -> Option<&'a BStr> {
157-
self.find("gpgsig")
183+
self.find(SIGNATURE_FIELD_NAME)
158184
}
159185
}

gix-object/src/lib.rs

+16-16
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,23 @@ pub enum Kind {
5858
Commit,
5959
Tag,
6060
}
61-
/// A chunk of any [`data`][BlobRef::data].
61+
/// A chunk of any [`data`](BlobRef::data).
6262
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
6363
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6464
pub struct BlobRef<'a> {
6565
/// The bytes themselves.
6666
pub data: &'a [u8],
6767
}
6868

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

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

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

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

187-
/// Immutable objects are read-only structures referencing most data from [a byte slice][crate::ObjectRef::from_bytes()].
187+
/// Immutable objects are read-only structures referencing most data from [a byte slice](ObjectRef::from_bytes()).
188188
///
189189
/// Immutable objects are expected to be deserialized from bytes that acts as backing store, and they
190-
/// cannot be mutated or serialized. Instead, one will [convert][crate::ObjectRef::into_owned()] them into their [`mutable`][Object] counterparts
190+
/// cannot be mutated or serialized. Instead, one will [convert](ObjectRef::into_owned()) them into their [`mutable`](Object) counterparts
191191
/// which support mutation and serialization.
192192
///
193-
/// An `ObjectRef` is representing [`Trees`][TreeRef], [`Blobs`][BlobRef], [`Commits`][CommitRef], or [`Tags`][TagRef].
193+
/// An `ObjectRef` is representing [`Trees`](TreeRef), [`Blobs`](BlobRef), [`Commits`](CommitRef), or [`Tags`](TagRef).
194194
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
195195
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196196
#[allow(missing_docs)]
@@ -206,10 +206,10 @@ pub enum ObjectRef<'a> {
206206
///
207207
/// Mutable objects are Commits, Trees, Blobs and Tags that can be changed and serialized.
208208
///
209-
/// They either created using object [construction][Object] or by [deserializing existing objects][ObjectRef::from_bytes()]
210-
/// and converting these [into mutable copies][ObjectRef::into_owned()] for adjustments.
209+
/// They either created using object [construction](Object) or by [deserializing existing objects](ObjectRef::from_bytes())
210+
/// and converting these [into mutable copies](ObjectRef::into_owned()) for adjustments.
211211
///
212-
/// An `Object` is representing [`Trees`][Tree], [`Blobs`][Blob], [`Commits`][Commit] or [`Tags`][Tag].
212+
/// An `Object` is representing [`Trees`](Tree), [`Blobs`](Blob), [`Commits`](Commit), or [`Tags`](Tag).
213213
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
214214
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215215
#[allow(clippy::large_enum_variant, missing_docs)]

gix-object/src/tag/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ impl<'a> TagRef<'a> {
2323
pub fn target(&self) -> gix_hash::ObjectId {
2424
gix_hash::ObjectId::from_hex(self.target).expect("prior validation")
2525
}
26+
27+
/// Copy all data into a fully-owned instance.
28+
pub fn into_owned(self) -> crate::Tag {
29+
self.into()
30+
}
2631
}

gix-object/tests/object/commit/from_bytes.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use gix_actor::SignatureRef;
2-
use gix_date::{time::Sign, Time};
3-
use gix_object::{bstr::ByteSlice, commit::message::body::TrailerRef, CommitRef};
4-
use smallvec::SmallVec;
5-
61
use crate::{
72
commit::{LONG_MESSAGE, MERGE_TAG, SIGNATURE},
83
fixture_name, linus_signature, signature,
94
};
5+
use gix_actor::SignatureRef;
6+
use gix_date::{time::Sign, Time};
7+
use gix_object::{bstr::ByteSlice, commit::message::body::TrailerRef, CommitRef};
8+
use smallvec::SmallVec;
109

1110
#[test]
1211
fn invalid_timestsamp() {
@@ -354,7 +353,12 @@ fn newline_right_after_signature_multiline_header() -> crate::Result {
354353
let pgp_sig = crate::commit::OTHER_SIGNATURE.as_bstr();
355354
assert_eq!(commit.extra_headers[0].1.as_ref(), pgp_sig);
356355
assert_eq!(commit.extra_headers().pgp_signature(), Some(pgp_sig));
357-
assert_eq!(commit.extra_headers().find("gpgsig"), Some(pgp_sig));
356+
assert_eq!(
357+
commit.extra_headers().find(gix_object::commit::SIGNATURE_FIELD_NAME),
358+
Some(pgp_sig)
359+
);
360+
assert_eq!(commit.extra_headers().find_pos("gpgsig"), Some(0));
361+
assert_eq!(commit.extra_headers().find_pos("something else"), None);
358362
assert!(commit.message.starts_with(b"Rollup"));
359363
Ok(())
360364
}

gix-object/tests/object/tag/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use gix_object::{bstr::ByteSlice, Kind, TagRef, TagRefIter};
44
use crate::fixture_name;
55

66
mod method {
7+
use bstr::ByteSlice;
78
use gix_object::TagRef;
89
use pretty_assertions::assert_eq;
910

@@ -15,6 +16,21 @@ mod method {
1516
let tag = TagRef::from_bytes(&fixture)?;
1617
assert_eq!(tag.target(), hex_to_id("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec"));
1718
assert_eq!(tag.target, "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bytes());
19+
20+
let gix_object::Tag {
21+
target,
22+
target_kind,
23+
name,
24+
tagger,
25+
message,
26+
pgp_signature,
27+
} = tag.into_owned();
28+
assert_eq!(target.to_string(), tag.target);
29+
assert_eq!(target_kind, tag.target_kind);
30+
assert_eq!(name, tag.name);
31+
assert_eq!(tagger.as_ref().map(|s| s.to_ref()), tag.tagger);
32+
assert_eq!(message, tag.message);
33+
assert_eq!(pgp_signature.as_ref().map(|s| s.as_bstr()), tag.pgp_signature);
1834
Ok(())
1935
}
2036
}

gix/src/repository/object.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ impl crate::Repository {
342342
/// Create a new commit object with `message` referring to `tree` with `parents`, and point `reference`
343343
/// to it. The commit is written without message encoding field, which can be assumed to be UTF-8.
344344
/// `author` and `committer` fields are pre-set from the configuration, which can be altered
345-
/// [temporarily][crate::Repository::config_snapshot_mut()] before the call if required.
345+
/// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
346346
///
347347
/// `reference` will be created if it doesn't exist, and can be `"HEAD"` to automatically write-through to the symbolic reference
348348
/// that `HEAD` points to if it is not detached. For this reason, detached head states cannot be created unless the `HEAD` is detached
@@ -352,6 +352,11 @@ impl crate::Repository {
352352
/// If there is no parent, the `reference` is expected to not exist yet.
353353
///
354354
/// The method fails immediately if a `reference` lock can't be acquired.
355+
///
356+
/// ### Writing a commit without `reference` update
357+
///
358+
/// If the reference shouldn't be updated, use [`Self::write_object()`] along with a newly created [`crate::objs::Object`] whose fields
359+
/// can be fully defined.
355360
pub fn commit<Name, E>(
356361
&self,
357362
reference: Name,

0 commit comments

Comments
 (0)