Skip to content
Open
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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions gitoxide-core/src/hours/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ fn parse_trailer_identity(trailer: gix::objs::commit::message::body::TrailerRef<
/// Return `(commit_author, [commit_author, co_authors...])`. Use the `commit_author` for easy access to the commit author itself.
fn commit_author_identities(
commit_data: &[u8],
hash_kind: gix::hash::Kind,
) -> Result<(gix::actor::SignatureRef<'_>, SmallVec<[ParsedIdentity<'_>; 2]>), gix::objs::decode::Error> {
let commit = gix::objs::CommitRef::from_bytes(commit_data)?;
let commit = gix::objs::CommitRef::from_bytes(commit_data, hash_kind)?;
let author = commit.author()?.trim();
let mut authors = smallvec![ParsedIdentity::Borrowed(gix::actor::IdentityRef::from(author))];
authors.extend(commit.co_authored_by_trailers().filter_map(parse_trailer_identity));
Expand Down Expand Up @@ -130,7 +131,7 @@ where
let extract_signatures = scope.spawn(move || -> anyhow::Result<Vec<_>> {
let mut out = Vec::new();
for (commit_idx, commit_data) in rx {
if let Ok((commit_author, authors)) = commit_author_identities(&commit_data) {
if let Ok((commit_author, authors)) = commit_author_identities(&commit_data, commit_id.kind()) {
let mut string_ref = |s: &[u8]| -> &'static BStr {
match string_heap.get(s) {
Some(n) => n.as_bstr(),
Expand Down Expand Up @@ -445,7 +446,7 @@ body\n\
\n\
Co-authored-by: Second Author <second@example.com>\n\
Co-authored-by: Third Author <third@example.com>\n";
let (author, authors) = commit_author_identities(commit).expect("valid commit");
let (author, authors) = commit_author_identities(commit, gix::hash::Kind::Sha1).expect("valid commit");
assert_eq!(author.time, "1710000000 +0000");
assert_eq!(
authors
Expand Down Expand Up @@ -478,7 +479,7 @@ committer Main Author <main@example.com> 1710000000 +0000\n\
subject\n\
\n\
Co-authored-by: not a signature\n";
let (_, authors) = commit_author_identities(commit).expect("valid commit");
let (_, authors) = commit_author_identities(commit, gix::hash::Kind::Sha1).expect("valid commit");
assert_eq!(authors.len(), 1);
assert_eq!(authors[0].name(), "Main Author".as_bytes().as_bstr());
assert_eq!(authors[0].email(), "main@example.com".as_bytes().as_bstr());
Expand Down
2 changes: 1 addition & 1 deletion gitoxide-core/src/query/engine/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ pub fn update(
self.progress.inc();
if self.known_commits.binary_search(&id.to_owned()).is_err() {
let res = {
let mut parents = gix::objs::CommitRefIter::from_bytes(obj.data).parent_ids();
let mut parents = gix::objs::CommitRefIter::from_bytes(obj.data, obj.hash_kind).parent_ids();
let res = parents.next().map(|first_parent| (Some(first_parent), id.to_owned()));
match parents.next() {
Some(_) => None,
Expand Down
5 changes: 4 additions & 1 deletion gix-config/src/parse/from_bytes/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,10 @@ mod value {
#[test]
fn trailing_backslash_is_accepted_as_continuation_to_eof() {
let (remaining, events) = parse(b"hello\\").unwrap();
assert_eq!(remaining, b"", "it consumes everything, as the continuation backslash is no value");
assert_eq!(
remaining, b"",
"it consumes everything, as the continuation backslash is no value"
);
assert_eq!(
events,
vec![value_not_done_event("hello"), value_done_event("")],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ fn single_line() {
fn global_property_uses_empty_section_name() -> crate::Result {
let mut file = file("a=b\n[core]\na=c");
let err = file.set_existing_raw_value_by("", None, "a", "d").unwrap_err();
assert_eq!(err.to_string(), "The requested section does not exist", "cannot set global values");
assert_eq!(
err.to_string(),
"The requested section does not exist",
"cannot set global values"
);
Ok(())
}

Expand Down
1 change: 0 additions & 1 deletion gix-object/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ gix-hashtable = { version = "^0.14.0", path = "../gix-hashtable" }
gix-validate = { version = "^0.11.1", path = "../gix-validate" }
gix-actor = { version = "^0.40.1", path = "../gix-actor" }
gix-date = { version = "^0.15.2", path = "../gix-date" }
gix-error = { version = "^0.2.2", path = "../gix-error" }
gix-utils = { version = "^0.3.2", path = "../gix-utils" }

itoa = "1.0.17"
Expand Down
18 changes: 14 additions & 4 deletions gix-object/benches/decode_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@ use std::hint::black_box;

fn parse_commit(c: &mut Criterion) {
c.bench_function("CommitRef(sig)", |b| {
b.iter(|| black_box(gix_object::CommitRef::from_bytes(COMMIT_WITH_MULTI_LINE_HEADERS)).unwrap());
b.iter(|| {
black_box(gix_object::CommitRef::from_bytes(
COMMIT_WITH_MULTI_LINE_HEADERS,
gix_hash::Kind::Sha1,
))
.unwrap()
});
});
c.bench_function("CommitRefIter(sig)", |b| {
b.iter(|| black_box(gix_object::CommitRefIter::from_bytes(COMMIT_WITH_MULTI_LINE_HEADERS).count()));
b.iter(|| {
black_box(
gix_object::CommitRefIter::from_bytes(COMMIT_WITH_MULTI_LINE_HEADERS, gix_hash::Kind::Sha1).count(),
)
});
});
}

fn parse_tag(c: &mut Criterion) {
c.bench_function("TagRef(sig)", |b| {
b.iter(|| black_box(gix_object::TagRef::from_bytes(TAG_WITH_SIGNATURE)).unwrap());
b.iter(|| black_box(gix_object::TagRef::from_bytes(TAG_WITH_SIGNATURE, gix_hash::Kind::Sha1)).unwrap());
});
c.bench_function("TagRefIter(sig)", |b| {
b.iter(|| black_box(gix_object::TagRefIter::from_bytes(TAG_WITH_SIGNATURE).count()));
b.iter(|| black_box(gix_object::TagRefIter::from_bytes(TAG_WITH_SIGNATURE, gix_hash::Kind::Sha1).count()));
});
}

Expand Down
6 changes: 4 additions & 2 deletions gix-object/fuzz/fuzz_targets/fuzz_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use libfuzzer_sys::fuzz_target;
use std::hint::black_box;

fuzz_target!(|commit: &[u8]| {
_ = black_box(gix_object::CommitRef::from_bytes(commit));
_ = black_box(gix_object::CommitRefIter::from_bytes(commit)).count();
_ = black_box(gix_object::CommitRef::from_bytes(commit, gix_hash::Kind::Sha1));
_ = black_box(gix_object::CommitRefIter::from_bytes(commit, gix_hash::Kind::Sha1)).count();
_ = black_box(gix_object::CommitRef::from_bytes(commit, gix_hash::Kind::Sha256));
_ = black_box(gix_object::CommitRefIter::from_bytes(commit, gix_hash::Kind::Sha256)).count();
});
6 changes: 4 additions & 2 deletions gix-object/fuzz/fuzz_targets/fuzz_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use libfuzzer_sys::fuzz_target;
use std::hint::black_box;

fuzz_target!(|tag: &[u8]| {
_ = black_box(gix_object::TagRef::from_bytes(tag));
_ = black_box(gix_object::TagRefIter::from_bytes(tag).count());
_ = black_box(gix_object::TagRef::from_bytes(tag, gix_hash::Kind::Sha1));
_ = black_box(gix_object::TagRefIter::from_bytes(tag, gix_hash::Kind::Sha1).count());
_ = black_box(gix_object::TagRef::from_bytes(tag, gix_hash::Kind::Sha256));
_ = black_box(gix_object::TagRefIter::from_bytes(tag, gix_hash::Kind::Sha256).count());
});
6 changes: 3 additions & 3 deletions gix-object/src/commit/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ pub fn message<'a>(i: &mut &'a [u8]) -> ParseResult<&'a BStr> {
/// This parser is not transactional as a whole: if a later required field or
/// the final message parse fails, `i` may already have been advanced past
/// earlier successfully parsed fields.
pub fn commit<'a>(i: &mut &'a [u8]) -> ParseResult<CommitRef<'a>> {
let tree = parse::header_field(i, b"tree", parse::hex_hash)?;
pub fn commit<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<CommitRef<'a>> {
let tree = parse::header_field(i, b"tree", |value| parse::hex_hash(value, hash_kind))?;

let mut parents = SmallVec::new();
loop {
let before = *i;
match parse::header_field(i, b"parent", parse::hex_hash) {
match parse::header_field(i, b"parent", |value| parse::hex_hash(value, hash_kind)) {
Ok(parent) => parents.push(parent),
Err(_) => {
*i = before;
Expand Down
25 changes: 17 additions & 8 deletions gix-object/src/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ mod write;

/// Lifecycle
impl<'a> CommitRef<'a> {
/// Deserialize a commit from the given `data` bytes while avoiding most allocations.
pub fn from_bytes(mut data: &'a [u8]) -> Result<CommitRef<'a>, crate::decode::Error> {
/// Deserialize a commit from the given `data` bytes while avoiding most allocations, using `hash_kind` to know
/// what kind of hash to expect for validation.
pub fn from_bytes(mut data: &'a [u8], hash_kind: gix_hash::Kind) -> Result<CommitRef<'a>, crate::decode::Error> {
let input = &mut data;
match decode::commit(input) {
match decode::commit(input, hash_kind) {
Ok(tag) => Ok(tag),
Err(err) => Err(err),
}
Expand All @@ -88,7 +89,10 @@ impl<'a> CommitRef<'a> {

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

/// Return the author, with whitespace trimmed.
Expand Down Expand Up @@ -132,13 +136,17 @@ impl CommitRef<'_> {
impl Commit {
/// Returns a convenient iterator over all extra headers.
pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr())))
ExtraHeaders::new(
self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr())),
self.tree.kind(),
)
}
}

/// An iterator over extra headers in [owned][crate::Commit] and [borrowed][crate::CommitRef] commits.
pub struct ExtraHeaders<I> {
inner: I,
hash_kind: gix_hash::Kind,
}

/// Instantiation and convenience.
Expand All @@ -147,8 +155,8 @@ where
I: Iterator<Item = (&'a BStr, &'a BStr)>,
{
/// Create a new instance from an iterator over tuples of (name, value) pairs.
pub fn new(iter: I) -> Self {
ExtraHeaders { inner: iter }
pub fn new(iter: I, hash_kind: gix_hash::Kind) -> Self {
ExtraHeaders { inner: iter, hash_kind }
}

/// Find the _value_ of the _first_ header with the given `name`.
Expand All @@ -175,7 +183,8 @@ where
/// A merge tag is a tag object embedded within the respective header field of a commit, making
/// it a child object of sorts.
pub fn mergetags(self) -> impl Iterator<Item = Result<TagRef<'a>, crate::decode::Error>> {
self.find_all("mergetag").map(|b| TagRef::from_bytes(b))
let hash_kind = self.hash_kind;
self.find_all("mergetag").map(move |b| TagRef::from_bytes(b, hash_kind))
}

/// Return the cryptographic signature provided by gpg/pgp verbatim.
Expand Down
43 changes: 29 additions & 14 deletions gix-object/src/commit/ref_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,35 @@ pub(crate) enum State {

/// Lifecycle
impl<'a> CommitRefIter<'a> {
/// Create a commit iterator from data.
pub fn from_bytes(data: &'a [u8]) -> CommitRefIter<'a> {
/// Create a commit iterator from the given `data`, using `hash_kind` to know
/// what kind of hash to expect for validation.
pub fn from_bytes(data: &'a [u8], hash_kind: gix_hash::Kind) -> CommitRefIter<'a> {
CommitRefIter {
data,
state: State::default(),
hash_kind,
}
}
}

/// Access
impl<'a> CommitRefIter<'a> {
/// Parse `data` as commit and return its PGP signature, along with *all non-signature* data as [`SignedData`], or `None`
/// if the commit isn't signed.
/// if the commit isn't signed. All hashes in `data` are parsed as `hash_kind`.
///
/// This allows the caller to validate the signature by passing the signed data along with the signature back to the program
/// that created it.
pub fn signature(data: &'a [u8]) -> Result<Option<(Cow<'a, BStr>, SignedData<'a>)>, crate::decode::Error> {
pub fn signature(
data: &'a [u8],
hash_kind: gix_hash::Kind,
) -> Result<Option<(Cow<'a, BStr>, SignedData<'a>)>, crate::decode::Error> {
let mut signature_and_range = None;

let raw_tokens = CommitRefIterRaw {
data,
state: State::default(),
offset: 0,
hash_kind,
};
for token in raw_tokens {
let token = token?;
Expand Down Expand Up @@ -146,35 +152,43 @@ fn missing_field() -> crate::decode::Error {

impl<'a> CommitRefIter<'a> {
#[inline]
fn next_inner(mut i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> {
fn next_inner(
mut i: &'a [u8],
state: &mut State,
hash_kind: gix_hash::Kind,
) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> {
let input = &mut i;
match Self::next_inner_(input, state) {
match Self::next_inner_(input, state, hash_kind) {
Ok(token) => Ok((*input, token)),
Err(err) => Err(err),
}
}

fn next_inner_(input: &mut &'a [u8], state: &mut State) -> Result<Token<'a>, crate::decode::Error> {
fn next_inner_(
input: &mut &'a [u8],
state: &mut State,
hash_kind: gix_hash::Kind,
) -> Result<Token<'a>, crate::decode::Error> {
use State::*;
Ok(match state {
Tree => {
let tree = parse::header_field(input, b"tree", parse::hex_hash)?;
let tree = parse::header_field(input, b"tree", |value| parse::hex_hash(value, hash_kind))?;
*state = State::Parents;
Token::Tree {
id: ObjectId::from_hex(tree).expect("parsing validation"),
}
}
Parents => {
if input.starts_with(b"parent ") {
let parent = parse::header_field(input, b"parent", parse::hex_hash)?;
let parent = parse::header_field(input, b"parent", |value| parse::hex_hash(value, hash_kind))?;
Token::Parent {
id: ObjectId::from_hex(parent).expect("parsing validation"),
}
} else {
*state = State::Signature {
of: SignatureKind::Author,
};
Self::next_inner_(input, state)?
Self::next_inner_(input, state, hash_kind)?
}
}
Signature { ref mut of } => {
Expand All @@ -201,13 +215,13 @@ impl<'a> CommitRefIter<'a> {
let encoding = parse::header_field(input, b"encoding", Ok)?;
Token::Encoding(encoding.as_bstr())
} else {
Self::next_inner_(input, state)?
Self::next_inner_(input, state, hash_kind)?
}
}
ExtraHeaders => {
if input.starts_with(b"\n") {
*state = State::Message;
Self::next_inner_(input, state)?
Self::next_inner_(input, state, hash_kind)?
} else {
let before = *input;
match parse::any_header_field_multi_line(input)
Expand Down Expand Up @@ -240,7 +254,7 @@ impl<'a> Iterator for CommitRefIter<'a> {
if self.data.is_empty() {
return None;
}
match Self::next_inner(self.data, &mut self.state) {
match Self::next_inner(self.data, &mut self.state, self.hash_kind) {
Ok((data, token)) => {
self.data = data;
Some(Ok(token))
Expand All @@ -258,6 +272,7 @@ struct CommitRefIterRaw<'a> {
data: &'a [u8],
state: State,
offset: usize,
hash_kind: gix_hash::Kind,
}

impl<'a> Iterator for CommitRefIterRaw<'a> {
Expand All @@ -267,7 +282,7 @@ impl<'a> Iterator for CommitRefIterRaw<'a> {
if self.data.is_empty() {
return None;
}
match CommitRefIter::next_inner(self.data, &mut self.state) {
match CommitRefIter::next_inner(self.data, &mut self.state, self.hash_kind) {
Ok((remaining, token)) => {
let consumed = self.data.len() - remaining.len();
let start = self.offset;
Expand Down
8 changes: 4 additions & 4 deletions gix-object/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ impl<'a> Data<'a> {
Ok(match self.kind {
Kind::Tree => ObjectRef::Tree(TreeRef::from_bytes(self.data, self.hash_kind)?),
Kind::Blob => ObjectRef::Blob(BlobRef { data: self.data }),
Kind::Commit => ObjectRef::Commit(CommitRef::from_bytes(self.data)?),
Kind::Tag => ObjectRef::Tag(TagRef::from_bytes(self.data)?),
Kind::Commit => ObjectRef::Commit(CommitRef::from_bytes(self.data, self.hash_kind)?),
Kind::Tag => ObjectRef::Tag(TagRef::from_bytes(self.data, self.hash_kind)?),
})
}

Expand All @@ -34,7 +34,7 @@ impl<'a> Data<'a> {
/// `None` if this is not a commit object.
pub fn try_into_commit_iter(self) -> Option<CommitRefIter<'a>> {
match self.kind {
Kind::Commit => Some(CommitRefIter::from_bytes(self.data)),
Kind::Commit => Some(CommitRefIter::from_bytes(self.data, self.hash_kind)),
_ => None,
}
}
Expand All @@ -43,7 +43,7 @@ impl<'a> Data<'a> {
/// `None` if this is not a tag object.
pub fn try_into_tag_iter(self) -> Option<TagRefIter<'a>> {
match self.kind {
Kind::Tag => Some(TagRefIter::from_bytes(self.data)),
Kind::Tag => Some(TagRefIter::from_bytes(self.data, self.hash_kind)),
_ => None,
}
}
Expand Down
Loading
Loading