diff --git a/Cargo.lock b/Cargo.lock index 06af5eee446..a6db1c6a981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1079,7 +1079,7 @@ dependencies = [ [[package]] name = "git-object" -version = "0.12.2" +version = "0.13.0" dependencies = [ "bstr", "git-actor", diff --git a/cargo-smart-release/src/command/release/git.rs b/cargo-smart-release/src/command/release/git.rs index 0048edd93a4..28e34819c68 100644 --- a/cargo-smart-release/src/command/release/git.rs +++ b/cargo-smart-release/src/command/release/git.rs @@ -6,10 +6,9 @@ use cargo_metadata::{ camino::{Utf8Component, Utf8Path}, Package, }; -use git_repository::easy::object; +use git_repository::{easy::object, prelude::ReferenceAccessExt, refs}; use super::{tag_name_for, utils::will, Context, Oid, Options}; -use git_repository::{prelude::ReferenceAccessExt, refs}; fn is_top_level_package(manifest_path: &Utf8Path, shared: &git_repository::Easy) -> bool { manifest_path diff --git a/experiments/diffing/src/main.rs b/experiments/diffing/src/main.rs index 1d329ca113c..d2e99d0951a 100644 --- a/experiments/diffing/src/main.rs +++ b/experiments/diffing/src/main.rs @@ -6,7 +6,7 @@ use git_repository::{ diff, easy::object, hash::{oid, ObjectId}, - objs::{bstr::BStr, immutable}, + objs::{bstr::BStr, TreeRefIter}, odb, prelude::*, refs::file::loose::reference::peel, @@ -254,14 +254,14 @@ where } }; - fn find_tree_iter<'b, L>(id: &oid, buf: &'b mut Vec, mut find: L) -> Option> + fn find_tree_iter<'b, L>(id: &oid, buf: &'b mut Vec, mut find: L) -> Option> where L: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, { find(id, buf).and_then(|o| o.into_tree_iter()) } - fn tree_iter_by_commit<'b, L>(id: &oid, buf: &'b mut Vec, mut find: L) -> immutable::TreeIter<'b> + fn tree_iter_by_commit<'b, L>(id: &oid, buf: &'b mut Vec, mut find: L) -> TreeRefIter<'b> where L: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, { diff --git a/experiments/traversal/src/main.rs b/experiments/traversal/src/main.rs index 8e62722722a..f919b599149 100644 --- a/experiments/traversal/src/main.rs +++ b/experiments/traversal/src/main.rs @@ -7,7 +7,7 @@ use anyhow::anyhow; use dashmap::DashSet; use git_repository::{ hash::ObjectId, - objs::{bstr::BStr, immutable::tree::Entry}, + objs::{bstr::BStr, tree::EntryRef}, odb, prelude::*, refs::file::loose::reference::peel, @@ -186,7 +186,7 @@ where fn push_back_tracked_path_component(&mut self, _component: &BStr) {} fn push_path_component(&mut self, _component: &BStr) {} fn pop_path_component(&mut self) {} - fn visit_tree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_tree(&mut self, entry: &EntryRef<'_>) -> Action { self.entries += 1; let inserted = self.seen.insert(entry.oid.to_owned()); if !inserted { @@ -195,7 +195,7 @@ where tree::visit::Action::Continue } } - fn visit_nontree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action { self.entries += 1; self.seen.insert(entry.oid.to_owned()); tree::visit::Action::Continue @@ -241,7 +241,7 @@ where fn push_back_tracked_path_component(&mut self, _component: &BStr) {} fn push_path_component(&mut self, _component: &BStr) {} fn pop_path_component(&mut self) {} - fn visit_tree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_tree(&mut self, entry: &EntryRef<'_>) -> Action { self.entries += 1; let inserted = self.seen.insert(entry.oid.to_owned()); if !inserted { @@ -250,7 +250,7 @@ where tree::visit::Action::Continue } } - fn visit_nontree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action { self.entries += 1; self.seen.insert(entry.oid.to_owned()); tree::visit::Action::Continue diff --git a/git-actor/src/lib.rs b/git-actor/src/lib.rs index d0a5eed386c..25a26df59a8 100644 --- a/git-actor/src/lib.rs +++ b/git-actor/src/lib.rs @@ -1,4 +1,4 @@ -//! This crate provides ways of identifying an actor within the git repository both in shared/signature_ref and mutable variants. +//! This crate provides ways of identifying an actor within the git repository both in shared/mutable and mutable variants. #![forbid(unsafe_code)] #![deny(rust_2018_idioms, missing_docs)] use bstr::{BStr, BString}; diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index 283069b1154..55a4db1426c 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -41,7 +41,7 @@ mod convert { } impl Signature { - /// Borrow this instance as signature_ref + /// Borrow this instance as immutable pub fn to_ref(&self) -> SignatureRef<'_> { SignatureRef { name: self.name.as_ref(), diff --git a/git-config/src/file/git_config.rs b/git-config/src/file/git_config.rs index 2a1d6644553..46345f0b51b 100644 --- a/git-config/src/file/git_config.rs +++ b/git-config/src/file/git_config.rs @@ -306,7 +306,7 @@ impl<'event> GitConfig<'event> { .map_err(|_| GitConfigError::FailedConversion) } - /// Returns an signature_ref section reference. + /// Returns an immutable section reference. /// /// # Errors /// diff --git a/git-diff/Cargo.toml b/git-diff/Cargo.toml index 41082f880f3..4bfd81c0ebd 100644 --- a/git-diff/Cargo.toml +++ b/git-diff/Cargo.toml @@ -15,7 +15,7 @@ doctest = false [dependencies] git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } quick-error = "2.0.0" [dev-dependencies] diff --git a/git-diff/src/tree/changes.rs b/git-diff/src/tree/changes.rs index e52dce8f79c..5fb66c70093 100644 --- a/git-diff/src/tree/changes.rs +++ b/git-diff/src/tree/changes.rs @@ -1,7 +1,6 @@ use std::{borrow::BorrowMut, collections::VecDeque}; use git_hash::{oid, ObjectId}; -use git_object::immutable; use quick_error::quick_error; use crate::{ @@ -20,7 +19,7 @@ quick_error! { Cancelled { display("The delegate cancelled the operation") } - EntriesDecode(err: immutable::object::decode::Error) { + EntriesDecode(err: git_object::decode::Error) { display("tree entries could not be decoded.") from() source(err) @@ -54,13 +53,13 @@ impl<'a> tree::Changes<'a> { /// [git_cmp_rs]: https://github.com/Byron/gitoxide/blob/a4d5f99c8dc99bf814790928a3bf9649cd99486b/git-object/src/mutable/tree.rs#L52-L55 pub fn needed_to_obtain( mut self, - other: immutable::TreeIter<'_>, + other: git_object::TreeRefIter<'_>, mut state: StateMut, mut find: FindFn, delegate: &mut R, ) -> Result<(), Error> where - FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, + FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, R: tree::Visit, StateMut: BorrowMut, { @@ -120,7 +119,7 @@ impl<'a> tree::Changes<'a> { } fn delete_entry_schedule_recursion( - entry: immutable::tree::Entry<'_>, + entry: git_object::tree::EntryRef<'_>, queue: &mut VecDeque, delegate: &mut R, ) -> Result<(), Error> { @@ -143,7 +142,7 @@ fn delete_entry_schedule_recursion( } fn add_entry_schedule_recursion( - entry: immutable::tree::Entry<'_>, + entry: git_object::tree::EntryRef<'_>, queue: &mut VecDeque, delegate: &mut R, ) -> Result<(), Error> { @@ -165,9 +164,9 @@ fn add_entry_schedule_recursion( Ok(()) } fn catchup_rhs_with_lhs( - rhs_entries: &mut IteratorType>, - lhs: immutable::tree::Entry<'_>, - rhs: immutable::tree::Entry<'_>, + rhs_entries: &mut IteratorType>, + lhs: git_object::tree::EntryRef<'_>, + rhs: git_object::tree::EntryRef<'_>, queue: &mut VecDeque, delegate: &mut R, ) -> Result<(), Error> { @@ -205,9 +204,9 @@ fn catchup_rhs_with_lhs( } fn catchup_lhs_with_rhs( - lhs_entries: &mut IteratorType>, - lhs: immutable::tree::Entry<'_>, - rhs: immutable::tree::Entry<'_>, + lhs_entries: &mut IteratorType>, + lhs: git_object::tree::EntryRef<'_>, + rhs: git_object::tree::EntryRef<'_>, queue: &mut VecDeque, delegate: &mut R, ) -> Result<(), Error> { @@ -245,8 +244,8 @@ fn catchup_lhs_with_rhs( } fn handle_lhs_and_rhs_with_equal_filenames( - lhs: immutable::tree::Entry<'_>, - rhs: immutable::tree::Entry<'_>, + lhs: git_object::tree::EntryRef<'_>, + rhs: git_object::tree::EntryRef<'_>, queue: &mut VecDeque, delegate: &mut R, ) -> Result<(), Error> { diff --git a/git-diff/src/tree/mod.rs b/git-diff/src/tree/mod.rs index 254cfdb938f..7d6ca8cef42 100644 --- a/git-diff/src/tree/mod.rs +++ b/git-diff/src/tree/mod.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use git_hash::ObjectId; -use git_object::immutable; +use git_object::TreeRefIter; /// The state required to visit [Changes] to be instantiated with `State::default()`. #[derive(Default, Clone)] @@ -22,11 +22,11 @@ impl State { } /// An iterator over changes of a tree, instantiated using `Changes::from(…)`. -pub struct Changes<'a>(Option>); +pub struct Changes<'a>(Option>); impl<'a, T> From for Changes<'a> where - T: Into>>, + T: Into>>, { fn from(v: T) -> Self { Changes(v.into()) diff --git a/git-diff/tests/visit/mod.rs b/git-diff/tests/visit/mod.rs index 4890cfdaed7..bd41eb5448a 100644 --- a/git-diff/tests/visit/mod.rs +++ b/git-diff/tests/visit/mod.rs @@ -2,7 +2,7 @@ mod changes { mod to_obtain_tree { use git_diff::tree::{recorder, recorder::Change::*}; use git_hash::{oid, ObjectId}; - use git_object::{bstr::ByteSlice, immutable, tree::EntryMode}; + use git_object::{bstr::ByteSlice, tree::EntryMode, TreeRefIter}; use git_odb::{linked, pack, Find}; use crate::hex_to_id; @@ -22,7 +22,7 @@ mod changes { db: &linked::Store, commit: &oid, buf: &'a mut Vec, - ) -> crate::Result> { + ) -> crate::Result> { let tree_id = db .find(commit, buf, &mut pack::cache::Never)? .ok_or_else(|| format!("start commit {:?} to be present", commit))? diff --git a/git-object/Cargo.toml b/git-object/Cargo.toml index 11fb434e006..e5066684842 100644 --- a/git-object/Cargo.toml +++ b/git-object/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-object" -version = "0.12.2" +version = "0.13.0" description = "Immutable and mutable git objects with decoding and encoding support" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" diff --git a/git-object/src/blob.rs b/git-object/src/blob.rs new file mode 100644 index 00000000000..f1e11fff50d --- /dev/null +++ b/git-object/src/blob.rs @@ -0,0 +1,17 @@ +use std::{convert::Infallible, io}; + +use crate::{Blob, BlobRef}; + +impl Blob { + /// Write the blobs data to `out` verbatim. + pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { + out.write_all(&self.data) + } +} + +impl<'a> BlobRef<'a> { + /// Instantiate a `Blob` from the given `data`, which is used as-is. + pub fn from_bytes(data: &[u8]) -> Result, Infallible> { + Ok(BlobRef { data }) + } +} diff --git a/git-object/src/commit.rs b/git-object/src/commit.rs deleted file mode 100644 index d932c07eda5..00000000000 --- a/git-object/src/commit.rs +++ /dev/null @@ -1,41 +0,0 @@ -use bstr::{BStr, ByteSlice}; - -use crate::immutable; - -/// An iterator over extra headers in [owned][crate::mutable::Commit] and [borrowed][immutable::Commit] commits. -pub struct ExtraHeaders { - inner: I, -} - -/// Instantiation and convenience. -impl<'a, I> ExtraHeaders -where - I: Iterator, -{ - /// Create a new instance from an iterator over tuples of (name, value) pairs. - 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 }) - } - /// Return an iterator over all _values_ of headers with the given `name`. - pub fn find_all(self, name: &'a str) -> impl Iterator { - 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 - /// it a child object of sorts. - pub fn mergetags(self) -> impl Iterator, immutable::object::decode::Error>> { - self.find_all("mergetag").map(|b| immutable::Tag::from_bytes(b)) - } - - /// Return the cryptographic signature provided by gpg/pgp verbatim. - pub fn pgp_signature(self) -> Option<&'a BStr> { - self.find("gpgsig") - } -} diff --git a/git-object/src/immutable/commit/decode.rs b/git-object/src/commit/decode.rs similarity index 93% rename from git-object/src/immutable/commit/decode.rs rename to git-object/src/commit/decode.rs index 812ba761d5e..92933773f99 100644 --- a/git-object/src/immutable/commit/decode.rs +++ b/git-object/src/commit/decode.rs @@ -10,10 +10,7 @@ use nom::{ }; use smallvec::SmallVec; -use crate::{ - immutable::{parse, parse::NL, Commit}, - BStr, ByteSlice, -}; +use crate::{parse, parse::NL, BStr, ByteSlice, CommitRef}; pub fn message<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a BStr, E> { if i.is_empty() { @@ -28,7 +25,9 @@ pub fn message<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8] Ok((&[], i.as_bstr())) } -pub fn commit<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], Commit<'_>, E> { +pub fn commit<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + i: &'a [u8], +) -> IResult<&'a [u8], CommitRef<'_>, E> { let (i, tree) = context("tree <40 lowercase hex char>", |i| { parse::header_field(i, b"tree", parse::hex_hash) })(i)?; @@ -59,7 +58,7 @@ pub fn commit<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) Ok(( i, - Commit { + CommitRef { tree, parents: SmallVec::from(parents), author, diff --git a/git-object/src/commit/mod.rs b/git-object/src/commit/mod.rs new file mode 100644 index 00000000000..84bcb734a17 --- /dev/null +++ b/git-object/src/commit/mod.rs @@ -0,0 +1,79 @@ +use bstr::{BStr, ByteSlice}; + +pub use crate::CommitRefIter; +use crate::{Commit, CommitRef, TagRef}; + +mod decode; + +/// +pub mod ref_iter; + +mod write; + +impl<'a> CommitRef<'a> { + /// Deserialize a commit from the given `data` bytes while avoiding most allocations. + pub fn from_bytes(data: &'a [u8]) -> Result, crate::decode::Error> { + decode::commit(data).map(|(_, t)| t).map_err(crate::decode::Error::from) + } + /// Return the `tree` fields hash digest. + pub fn tree(&self) -> git_hash::ObjectId { + git_hash::ObjectId::from_hex(self.tree).expect("prior validation of tree hash during parsing") + } + + /// Returns an iterator of parent object ids + pub fn parents(&self) -> impl Iterator + '_ { + self.parents + .iter() + .map(|hex_hash| git_hash::ObjectId::from_hex(hex_hash).expect("prior validation of hashes during parsing")) + } + + /// Returns a convenient iterator over all extra headers. + pub fn extra_headers(&self) -> crate::commit::ExtraHeaders> { + crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref()))) + } +} + +impl Commit { + /// Returns a convenient iterator over all extra headers. + pub fn extra_headers(&self) -> ExtraHeaders> { + ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr()))) + } +} + +/// An iterator over extra headers in [owned][crate::Commit] and [borrowed][crate::CommitRef] commits. +pub struct ExtraHeaders { + inner: I, +} + +/// Instantiation and convenience. +impl<'a, I> ExtraHeaders +where + I: Iterator, +{ + /// Create a new instance from an iterator over tuples of (name, value) pairs. + 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 }) + } + /// Return an iterator over all _values_ of headers with the given `name`. + pub fn find_all(self, name: &'a str) -> impl Iterator { + 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 + /// it a child object of sorts. + pub fn mergetags(self) -> impl Iterator, crate::decode::Error>> { + self.find_all("mergetag").map(|b| TagRef::from_bytes(b)) + } + + /// Return the cryptographic signature provided by gpg/pgp verbatim. + pub fn pgp_signature(self) -> Option<&'a BStr> { + self.find("gpgsig") + } +} diff --git a/git-object/src/immutable/commit/iter.rs b/git-object/src/commit/ref_iter.rs similarity index 91% rename from git-object/src/immutable/commit/iter.rs rename to git-object/src/commit/ref_iter.rs index 37134bb3754..29ee69475ba 100644 --- a/git-object/src/immutable/commit/iter.rs +++ b/git-object/src/commit/ref_iter.rs @@ -9,18 +9,15 @@ use nom::{ error::context, }; -use crate::{ - bstr::ByteSlice, - immutable::{commit::decode, object, parse, parse::NL}, -}; +use crate::{bstr::ByteSlice, commit::decode, parse, parse::NL, CommitRefIter}; #[derive(Copy, Clone)] -enum SignatureKind { +pub(crate) enum SignatureKind { Author, Committer, } -enum State { +pub(crate) enum State { Tree, Parents, Signature { of: SignatureKind }, @@ -35,17 +32,10 @@ impl Default for State { } } -/// Like [`signature_ref::Commit`][super::Commit], but as `Iterator` to support (up to) entirely allocation free parsing. -/// It's particularly useful to traverse the commit graph without ever allocating arrays for parents. -pub struct Iter<'a> { - data: &'a [u8], - state: State, -} - -impl<'a> Iter<'a> { +impl<'a> CommitRefIter<'a> { /// Create a commit iterator from data. - pub fn from_bytes(data: &'a [u8]) -> Iter<'a> { - Iter { + pub fn from_bytes(data: &'a [u8]) -> CommitRefIter<'a> { + CommitRefIter { data, state: State::default(), } @@ -77,8 +67,8 @@ impl<'a> Iter<'a> { } } -impl<'a> Iter<'a> { - fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), object::decode::Error> { +impl<'a> CommitRefIter<'a> { + fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> { use State::*; Ok(match state { Tree => { @@ -176,8 +166,8 @@ impl<'a> Iter<'a> { } } -impl<'a> Iterator for Iter<'a> { - type Item = Result, object::decode::Error>; +impl<'a> Iterator for CommitRefIter<'a> { + type Item = Result, crate::decode::Error>; fn next(&mut self) -> Option { if self.data.is_empty() { @@ -196,7 +186,7 @@ impl<'a> Iterator for Iter<'a> { } } -/// A token returned by the [commit iterator][Iter]. +/// A token returned by the [commit iterator][CommitRefIter]. #[allow(missing_docs)] #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Token<'a> { diff --git a/git-object/src/commit/write.rs b/git-object/src/commit/write.rs new file mode 100644 index 00000000000..1ce29176712 --- /dev/null +++ b/git-object/src/commit/write.rs @@ -0,0 +1,30 @@ +use std::io; + +use bstr::ByteSlice; + +use crate::{encode, encode::NL, Commit}; + +impl Commit { + /// Serializes this instance to `out` in the git serialization format. + pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { + encode::trusted_header_id(b"tree", &self.tree, &mut out)?; + for parent in &self.parents { + encode::trusted_header_id(b"parent", parent, &mut out)?; + } + encode::trusted_header_signature(b"author", &self.author, &mut out)?; + encode::trusted_header_signature(b"committer", &self.committer, &mut out)?; + if let Some(encoding) = self.encoding.as_ref() { + encode::header_field(b"encoding", encoding, &mut out)?; + } + for (name, value) in &self.extra_headers { + let has_newline = value.find_byte(b'\n').is_some(); + if has_newline { + encode::header_field_multi_line(name, value, &mut out)?; + } else { + encode::trusted_header_field(name, value, &mut out)?; + } + } + out.write_all(NL)?; + out.write_all(&self.message) + } +} diff --git a/git-object/src/mutable/encode.rs b/git-object/src/encode.rs similarity index 96% rename from git-object/src/mutable/encode.rs rename to git-object/src/encode.rs index df564825557..8cc2f7e3f10 100644 --- a/git-object/src/mutable/encode.rs +++ b/git-object/src/encode.rs @@ -3,8 +3,6 @@ use std::io; use bstr::{BString, ByteSlice}; use quick_error::quick_error; -use crate::mutable::{NL, SPACE}; - quick_error! { #[derive(Debug)] enum Error { @@ -71,3 +69,6 @@ pub fn header_field(name: &[u8], value: &[u8], out: impl io::Write) -> io::Resul } trusted_header_field(name, value, out) } + +pub(crate) const NL: &[u8; 1] = b"\n"; +pub(crate) const SPACE: &[u8; 1] = b" "; diff --git a/git-object/src/immutable/blob.rs b/git-object/src/immutable/blob.rs deleted file mode 100644 index e5ea698116d..00000000000 --- a/git-object/src/immutable/blob.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::convert::Infallible; - -/// A chunk of any [`data`][Blob::data]. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Blob<'a> { - /// The bytes themselves. - pub data: &'a [u8], -} - -impl<'a> Blob<'a> { - /// Instantiate a `Blob` from the given `data`, which is used as-is. - pub fn from_bytes(data: &[u8]) -> Result, Infallible> { - Ok(Blob { data }) - } -} diff --git a/git-object/src/immutable/commit/mod.rs b/git-object/src/immutable/commit/mod.rs deleted file mode 100644 index f5498e69a99..00000000000 --- a/git-object/src/immutable/commit/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::borrow::Cow; - -use smallvec::SmallVec; - -use crate::{immutable::object, BStr}; - -mod decode; - -/// -pub mod iter; - -/// A git commit parsed using [`from_bytes()`][Commit::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`. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Commit<'a> { - /// HEX hash of tree object we point to. Usually 40 bytes long. - /// - /// Use [`tree()`][Commit::tree()] to obtain a decoded version of it. - #[cfg_attr(feature = "serde1", serde(borrow))] - pub tree: &'a BStr, - /// HEX hash of each parent commit. Empty for first commit in repository. - pub parents: SmallVec<[&'a BStr; 2]>, - /// Who wrote this commit. - pub author: git_actor::SignatureRef<'a>, - /// Who committed this commit. - /// - /// 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. - pub committer: git_actor::SignatureRef<'a>, - /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). - 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()`][Commit::extra_headers()]. - pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>, -} - -impl<'a> Commit<'a> { - /// Deserialize a commit from the given `data` bytes while avoiding most allocations. - pub fn from_bytes(data: &'a [u8]) -> Result, object::decode::Error> { - decode::commit(data) - .map(|(_, t)| t) - .map_err(object::decode::Error::from) - } - /// Return the `tree` fields hash digest. - pub fn tree(&self) -> git_hash::ObjectId { - git_hash::ObjectId::from_hex(self.tree).expect("prior validation of tree hash during parsing") - } - - /// Returns an iterator of parent object ids - pub fn parents(&self) -> impl Iterator + '_ { - self.parents - .iter() - .map(|hex_hash| git_hash::ObjectId::from_hex(hex_hash).expect("prior validation of hashes during parsing")) - } - - /// Returns a convenient iterator over all extra headers. - pub fn extra_headers(&self) -> crate::commit::ExtraHeaders> { - crate::commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref()))) - } -} diff --git a/git-object/src/immutable/mod.rs b/git-object/src/immutable/mod.rs deleted file mode 100644 index 64b9ccc648f..00000000000 --- a/git-object/src/immutable/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Immutable objects are read-only structures referencing most data from [a byte slice][Object::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][Object::into_mutable()] them into their [`mutable`][crate::mutable] counterparts -//! which support mutation and serialization. - -mod blob; -pub use blob::Blob; - -/// -pub mod commit; -pub use commit::{iter::Iter as CommitIter, Commit}; - -/// -pub mod object; -pub use object::Object; - -/// -pub mod tag; -pub use tag::{iter::Iter as TagIter, Tag}; - -/// -pub mod tree; -pub use tree::{Tree, TreeIter}; - -mod parse; diff --git a/git-object/src/immutable/object.rs b/git-object/src/immutable/object.rs deleted file mode 100644 index ffb25d7f626..00000000000 --- a/git-object/src/immutable/object.rs +++ /dev/null @@ -1,264 +0,0 @@ -use crate::{ - immutable, - immutable::{Blob, Commit, Tag, Tree}, - Kind, -}; - -/// An signature_ref object representing [`Trees`][Tree], [`Blobs`][Blob], [`Commits`][Commit], or [`Tags`][Tag]. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -#[allow(missing_docs)] -pub enum Object<'a> { - #[cfg_attr(feature = "serde1", serde(borrow))] - Tree(Tree<'a>), - Blob(Blob<'a>), - Commit(Commit<'a>), - Tag(Tag<'a>), -} - -impl<'a> Object<'a> { - /// Deserialize an object of `kind` from the given `data`. - pub fn from_bytes(kind: Kind, data: &'a [u8]) -> Result, decode::Error> { - Ok(match kind { - Kind::Tree => Object::Tree(Tree::from_bytes(data)?), - Kind::Blob => Object::Blob(Blob { data }), - Kind::Commit => Object::Commit(Commit::from_bytes(data)?), - Kind::Tag => Object::Tag(Tag::from_bytes(data)?), - }) - } - - /// Convert the signature_ref object into a mutable version, consuming the source in the process. - /// - /// Note that this is an expensive operation. - pub fn into_mutable(self) -> crate::mutable::Object { - self.into() - } - - /// Convert this signature_ref object into its mutable counterpart. - /// - /// Note that this is an expensive operation. - pub fn to_mutable(&self) -> crate::mutable::Object { - self.clone().into() - } -} - -/// Convenient access to contained objects. -impl<'a> Object<'a> { - /// Interpret this object as blob. - pub fn as_blob(&self) -> Option<&immutable::Blob<'a>> { - match self { - Object::Blob(v) => Some(v), - _ => None, - } - } - /// Interpret this object as blob, chainable. - pub fn into_blob(self) -> Option> { - match self { - Object::Blob(v) => Some(v), - _ => None, - } - } - /// Interpret this object as commit. - pub fn as_commit(&self) -> Option<&immutable::Commit<'a>> { - match self { - Object::Commit(v) => Some(v), - _ => None, - } - } - /// Interpret this object as commit, chainable. - pub fn into_commit(self) -> Option> { - match self { - Object::Commit(v) => Some(v), - _ => None, - } - } - /// Interpret this object as tree. - pub fn as_tree(&self) -> Option<&immutable::Tree<'a>> { - match self { - Object::Tree(v) => Some(v), - _ => None, - } - } - /// Interpret this object as tree, chainable - pub fn into_tree(self) -> Option> { - match self { - Object::Tree(v) => Some(v), - _ => None, - } - } - /// Interpret this object as tag. - pub fn as_tag(&self) -> Option<&immutable::Tag<'a>> { - match self { - Object::Tag(v) => Some(v), - _ => None, - } - } - /// Interpret this object as tag, chainable. - pub fn into_tag(self) -> Option> { - match self { - Object::Tag(v) => Some(v), - _ => None, - } - } - /// Return the kind of object. - pub fn kind(&self) -> Kind { - match self { - Object::Tree(_) => Kind::Tree, - Object::Blob(_) => Kind::Blob, - Object::Commit(_) => Kind::Commit, - Object::Tag(_) => Kind::Tag, - } - } -} - -mod convert { - use std::convert::TryFrom; - - use crate::immutable::{Blob, Commit, Object, Tag, Tree}; - - impl<'a> From> for Object<'a> { - fn from(v: Tag<'a>) -> Self { - Object::Tag(v) - } - } - - impl<'a> From> for Object<'a> { - fn from(v: Commit<'a>) -> Self { - Object::Commit(v) - } - } - - impl<'a> From> for Object<'a> { - fn from(v: Tree<'a>) -> Self { - Object::Tree(v) - } - } - - impl<'a> From> for Object<'a> { - fn from(v: Blob<'a>) -> Self { - Object::Blob(v) - } - } - - impl<'a> TryFrom> for Tag<'a> { - type Error = Object<'a>; - - fn try_from(value: Object<'a>) -> Result { - Ok(match value { - Object::Tag(v) => v, - _ => return Err(value), - }) - } - } - - impl<'a> TryFrom> for Commit<'a> { - type Error = Object<'a>; - - fn try_from(value: Object<'a>) -> Result { - Ok(match value { - Object::Commit(v) => v, - _ => return Err(value), - }) - } - } - - impl<'a> TryFrom> for Tree<'a> { - type Error = Object<'a>; - - fn try_from(value: Object<'a>) -> Result { - Ok(match value { - Object::Tree(v) => v, - _ => return Err(value), - }) - } - } - - impl<'a> TryFrom> for Blob<'a> { - type Error = Object<'a>; - - fn try_from(value: Object<'a>) -> Result { - Ok(match value { - Object::Blob(v) => v, - _ => return Err(value), - }) - } - } -} - -/// -#[cfg(feature = "verbose-object-parsing-errors")] -pub mod decode { - use crate::bstr::{BString, ByteSlice}; - - /// The type to be used for parse errors. - pub type ParseError<'a> = nom::error::VerboseError<&'a [u8]>; - /// The owned type to be used for parse errors. - pub type ParseErrorOwned = nom::error::VerboseError; - - /// A type to indicate errors during parsing and to abstract away details related to `nom`. - #[derive(Debug, Clone)] - pub struct Error { - /// The actual error - pub inner: ParseErrorOwned, - } - - impl<'a> From>> for Error { - fn from(v: nom::Err>) -> Self { - Error { - inner: match v { - nom::Err::Error(err) | nom::Err::Failure(err) => nom::error::VerboseError { - errors: err - .errors - .into_iter() - .map(|(i, v)| (i.as_bstr().to_owned(), v)) - .collect(), - }, - nom::Err::Incomplete(_) => unreachable!("we don't have streaming parsers"), - }, - } - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.inner.fmt(f) - } - } - - impl std::error::Error for Error {} -} - -/// -#[cfg(not(feature = "verbose-object-parsing-errors"))] -pub mod decode { - /// The type to be used for parse errors, discards everything and is zero size - pub type ParseError<'a> = (); - /// The owned type to be used for parse errors, discards everything and is zero size - pub type ParseErrorOwned = (); - - /// A type to indicate errors during parsing and to abstract away details related to `nom`. - #[derive(Debug, Clone)] - pub struct Error { - /// The actual error - pub inner: ParseErrorOwned, - } - - impl<'a> From>> for Error { - fn from(v: nom::Err>) -> Self { - Error { - inner: match v { - nom::Err::Error(err) | nom::Err::Failure(err) => err, - nom::Err::Incomplete(_) => unreachable!("we don't have streaming parsers"), - }, - } - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } - } - - impl std::error::Error for Error {} -} diff --git a/git-object/src/immutable/tag.rs b/git-object/src/immutable/tag.rs deleted file mode 100644 index 70d7c42fa49..00000000000 --- a/git-object/src/immutable/tag.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::{immutable::object, BStr}; - -/// Represents a git tag, commonly indicating a software release. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Tag<'a> { - /// The hash in hexadecimal being the object this tag points to. Use [`target()`][Tag::target()] to obtain a byte representation. - #[cfg_attr(feature = "serde1", serde(borrow))] - pub target: &'a BStr, - /// The kind of object that `target` points to. - pub target_kind: crate::Kind, - /// The name of the tag, e.g. "v1.0". - pub name: &'a BStr, - /// The author of the tag. - pub tagger: Option>, - /// The message describing this release. - pub message: &'a BStr, - /// A cryptographic signature over the entire content of the serialized tag object thus far. - pub pgp_signature: Option<&'a BStr>, -} - -impl<'a> Tag<'a> { - /// Deserialize a tag from `data`. - pub fn from_bytes(data: &'a [u8]) -> Result, object::decode::Error> { - decode::git_tag(data) - .map(|(_, t)| t) - .map_err(object::decode::Error::from) - } - /// The object this tag points to as `Id`. - pub fn target(&self) -> git_hash::ObjectId { - git_hash::ObjectId::from_hex(self.target).expect("prior validation") - } -} - -mod decode { - use nom::{ - branch::alt, - bytes::complete::{tag, take_until, take_while, take_while1}, - character::is_alphabetic, - combinator::{all_consuming, opt, recognize}, - error::{context, ContextError, ParseError}, - sequence::{preceded, tuple}, - IResult, - }; - - use crate::{ - immutable::{parse, parse::NL, Tag}, - BStr, ByteSlice, - }; - - pub fn git_tag<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], Tag<'a>, E> { - let (i, target) = context("object <40 lowercase hex char>", |i| { - parse::header_field(i, b"object", parse::hex_hash) - })(i)?; - - let (i, kind) = context("type ", |i| { - parse::header_field(i, b"type", take_while1(is_alphabetic)) - })(i)?; - let kind = crate::Kind::from_bytes(kind) - .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))?; - - let (i, tag_version) = context("tag ", |i| { - parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) - })(i)?; - - let (i, signature) = context( - "tagger ", - opt(|i| parse::header_field(i, b"tagger", parse::signature)), - )(i)?; - let (i, (message, pgp_signature)) = all_consuming(message)(i)?; - Ok(( - i, - Tag { - target, - name: tag_version.as_bstr(), - target_kind: kind, - message, - tagger: signature, - pgp_signature, - }, - )) - } - - pub fn message<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a BStr, Option<&'a BStr>), E> { - const PGP_SIGNATURE_BEGIN: &[u8] = b"\n-----BEGIN PGP SIGNATURE-----"; - const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----"; - - if i.is_empty() { - return Ok((i, (i.as_bstr(), None))); - } - let (i, _) = tag(NL)(i)?; - fn all_to_end<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], &'a [u8]), E> { - if i.is_empty() { - return Err(nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::Eof))); - } - // an empty signature message signals that there is none - the function signature is needed - // to work with 'alt(…)'. PGP signatures are never empty - Ok((&[], (i, &[]))) - } - let (i, (message, signature)) = alt(( - tuple(( - take_until(PGP_SIGNATURE_BEGIN), - preceded( - tag(NL), - recognize(tuple(( - tag(&PGP_SIGNATURE_BEGIN[1..]), - take_until(PGP_SIGNATURE_END), - tag(PGP_SIGNATURE_END), - take_while(|_| true), - ))), - ), - )), - all_to_end, - ))(i)?; - let (i, _) = opt(tag(NL))(i)?; - Ok(( - i, - ( - message.as_bstr(), - if signature.is_empty() { - None - } else { - Some(signature.as_bstr()) - }, - ), - )) - } -} - -/// -pub mod iter { - use bstr::BStr; - use git_hash::{oid, ObjectId}; - use nom::{ - bytes::complete::take_while1, - character::is_alphabetic, - combinator::{all_consuming, opt}, - error::{context, ParseError}, - }; - - use crate::{ - bstr::ByteSlice, - immutable::{object, parse, parse::NL, tag::decode}, - Kind, - }; - - enum State { - Target, - TargetKind, - Name, - Tagger, - Message, - } - - impl Default for State { - fn default() -> Self { - State::Target - } - } - - /// Like [`signature_ref::Tag`][super::Tag], but as `Iterator` to support entirely allocation free parsing. - /// It's particularly useful to dereference only the target chain. - pub struct Iter<'a> { - data: &'a [u8], - state: State, - } - - impl<'a> Iter<'a> { - /// Create a tag iterator from data. - pub fn from_bytes(data: &'a [u8]) -> Iter<'a> { - Iter { - data, - state: State::default(), - } - } - - /// Returns the target id of this tag if it is the first function called and if there is no error in decoding - /// the data. - /// - /// Note that this method must only be called once or else will always return None while consuming a single token. - /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they - /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again. - /// `next()`. - pub fn target_id(&mut self) -> Option { - self.next().and_then(Result::ok).and_then(Token::into_id) - } - } - - impl<'a> Iter<'a> { - fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), object::decode::Error> { - use State::*; - Ok(match state { - Target => { - let (i, target) = context("object <40 lowercase hex char>", |i| { - parse::header_field(i, b"object", parse::hex_hash) - })(i)?; - *state = State::TargetKind; - ( - i, - Token::Target { - id: ObjectId::from_hex(target).expect("parsing validation"), - }, - ) - } - TargetKind => { - let (i, kind) = context("type ", |i| { - parse::header_field(i, b"type", take_while1(is_alphabetic)) - })(i)?; - let kind = crate::Kind::from_bytes(kind).map_err(|_| { - let err = object::decode::ParseError::from_error_kind(i, nom::error::ErrorKind::MapRes); - nom::Err::Error(err) - })?; - *state = State::Name; - (i, Token::TargetKind(kind)) - } - Name => { - let (i, tag_version) = context("tag ", |i| { - parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) - })(i)?; - *state = State::Tagger; - (i, Token::Name(tag_version.as_bstr())) - } - Tagger => { - let (i, signature) = context( - "tagger ", - opt(|i| parse::header_field(i, b"tagger", parse::signature)), - )(i)?; - *state = State::Message; - (i, Token::Tagger(signature)) - } - Message => { - let (i, (message, pgp_signature)) = all_consuming(decode::message)(i)?; - debug_assert!( - i.is_empty(), - "we should have consumed all data - otherwise iter may go forever" - ); - return Ok((i, Token::Body { message, pgp_signature })); - } - }) - } - } - - impl<'a> Iterator for Iter<'a> { - type Item = Result, object::decode::Error>; - - fn next(&mut self) -> Option { - if self.data.is_empty() { - return None; - } - match Self::next_inner(self.data, &mut self.state) { - Ok((data, token)) => { - self.data = data; - Some(Ok(token)) - } - Err(err) => { - self.data = &[]; - Some(Err(err)) - } - } - } - } - - /// A token returned by the [commit iterator][Iter]. - #[allow(missing_docs)] - #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] - pub enum Token<'a> { - Target { - id: ObjectId, - }, - TargetKind(Kind), - Name(&'a BStr), - Tagger(Option>), - Body { - message: &'a BStr, - pgp_signature: Option<&'a BStr>, - }, - } - - impl<'a> Token<'a> { - /// Return the object id of this token if its a [Target][Token::Target]. - pub fn id(&self) -> Option<&oid> { - match self { - Token::Target { id } => Some(id.as_ref()), - _ => None, - } - } - - /// Return the owned object id of this token if its a [Target][Token::Target]. - pub fn into_id(self) -> Option { - match self { - Token::Target { id } => Some(id), - _ => None, - } - } - } -} diff --git a/git-object/src/lib.rs b/git-object/src/lib.rs index ea719bc4a17..e58b34ef01b 100644 --- a/git-object/src/lib.rs +++ b/git-object/src/lib.rs @@ -1,17 +1,288 @@ -//! This crate provides types for [read-only git objects][immutable::Object] backed by bytes provided in git's serialization format -//! as well as [mutable versions][mutable::Object] of these. The latter can be serialized into git's serialization format for objects. +//! This crate provides types for [read-only git objects][crate::ObjectRef] backed by bytes provided in git's serialization format +//! as well as [mutable versions][Object] of these. The latter can be serialized into git's serialization format for objects. #![forbid(unsafe_code)] #![deny(rust_2018_idioms, missing_docs)] +use std::borrow::Cow; + /// For convenience to allow using `bstr` without adding it to own cargo manifest. pub use bstr; use bstr::{BStr, BString, ByteSlice}; +use smallvec::SmallVec; +use tree::Entry; +pub use types::{Error, Kind}; + +use crate::tree::EntryRef; + +/// +pub mod commit; +mod object; +/// +pub mod tag; +/// +pub mod tree; -pub mod immutable; -pub mod mutable; +mod blob; +mod encode; +pub(crate) mod parse; mod types; -pub use types::{tree, Error, Kind}; +/// A chunk of any [`data`][BlobRef::data]. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct BlobRef<'a> { + /// The bytes themselves. + pub data: &'a [u8], +} + +/// A mutable chunk of any [`data`][Blob::data]. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Blob { + /// The data itself. + pub data: Vec, +} + +/// A git commit parsed using [`from_bytes()`][CommitRef::from_bytes()]. /// -pub mod commit; +/// 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`. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +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. + #[cfg_attr(feature = "serde1", serde(borrow))] + pub tree: &'a BStr, + /// HEX hash of each parent commit. Empty for first commit in repository. + pub parents: SmallVec<[&'a BStr; 2]>, + /// Who wrote this commit. + pub author: git_actor::SignatureRef<'a>, + /// Who committed this commit. + /// + /// 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. + pub committer: git_actor::SignatureRef<'a>, + /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). + 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()]. + pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>, +} + +/// Like [`CommitRef`][crate::CommitRef], but as `Iterator` to support (up to) entirely allocation free parsing. +/// It's particularly useful to traverse the commit graph without ever allocating arrays for parents. +pub struct CommitRefIter<'a> { + data: &'a [u8], + state: commit::ref_iter::State, +} + +/// A mutable git commit, representing an annotated state of a working tree along with a reference to its historical commits. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Commit { + /// The hash of recorded working tree state. + pub tree: git_hash::ObjectId, + /// Hash of each parent commit. Empty for the first commit in repository. + pub parents: SmallVec<[git_hash::ObjectId; 1]>, + /// Who wrote this commit. + pub author: git_actor::Signature, + /// Who committed this commit. + /// + /// 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. + pub committer: git_actor::Signature, + /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). + pub encoding: Option, + /// 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()]. + pub extra_headers: Vec<(BString, BString)>, +} + +/// Represents a git tag, commonly indicating a software release. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", 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. + #[cfg_attr(feature = "serde1", serde(borrow))] + pub target: &'a BStr, + /// The kind of object that `target` points to. + pub target_kind: crate::Kind, + /// The name of the tag, e.g. "v1.0". + pub name: &'a BStr, + /// The author of the tag. + pub tagger: Option>, + /// The message describing this release. + pub message: &'a BStr, + /// A cryptographic signature over the entire content of the serialized tag object thus far. + pub pgp_signature: Option<&'a BStr>, +} + +/// Like [`TagRef`], but as `Iterator` to support entirely allocation free parsing. +/// It's particularly useful to dereference only the target chain. +pub struct TagRefIter<'a> { + data: &'a [u8], + state: tag::ref_iter::State, +} + +/// A mutable git tag. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Tag { + /// The hash this tag is pointing to. + pub target: git_hash::ObjectId, + /// The kind of object this tag is pointing to. + pub target_kind: crate::Kind, + /// The name of the tag, e.g. "v1.0". + pub name: BString, + /// The message describing the tag. + pub message: BString, + /// The tags author. + pub signature: Option, + /// A pgp signature over all bytes of the encoded tag, excluding the pgp signature itself. + pub pgp_signature: Option, +} + +/// Immutable objects are read-only structures referencing most data from [a byte slice][crate::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 +/// which support mutation and serialization. +/// +/// An `ObjectRef` is representing [`Trees`][TreeRef], [`Blobs`][BlobRef], [`Commits`][CommitRef], or [`Tags`][TagRef]. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[allow(missing_docs)] +pub enum ObjectRef<'a> { + #[cfg_attr(feature = "serde1", serde(borrow))] + Tree(TreeRef<'a>), + Blob(BlobRef<'a>), + Commit(CommitRef<'a>), + Tag(TagRef<'a>), +} + +/// Mutable objects with each field being separately allocated and changeable. +/// +/// 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. +/// +/// An `Object` is representing [`Trees`][Tree], [`Blobs`][Blob], [`Commits`][Commit] or [`Tags`][Tag]. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[allow(clippy::large_enum_variant, missing_docs)] +pub enum Object { + Tree(Tree), + Blob(Blob), + Commit(Commit), + Tag(Tag), +} +/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits). +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct TreeRef<'a> { + /// The directories and files contained in this tree. + #[cfg_attr(feature = "serde1", serde(borrow))] + pub entries: Vec>, +} + +/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits), lazily evaluated. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct TreeRefIter<'a> { + /// The directories and files contained in this tree. + #[cfg_attr(feature = "serde1", serde(borrow))] + data: &'a [u8], +} + +/// A mutable Tree, containing other trees, blobs or commits. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Tree { + /// The directories and files contained in this tree. They must be and remain sorted by [`filename`][Entry::filename]. + pub entries: Vec, +} + +/// +#[cfg(feature = "verbose-object-parsing-errors")] +pub mod decode { + use crate::bstr::{BString, ByteSlice}; + + /// The type to be used for parse errors. + pub type ParseError<'a> = nom::error::VerboseError<&'a [u8]>; + /// The owned type to be used for parse errors. + pub type ParseErrorOwned = nom::error::VerboseError; + + /// A type to indicate errors during parsing and to abstract away details related to `nom`. + #[derive(Debug, Clone)] + pub struct Error { + /// The actual error + pub inner: ParseErrorOwned, + } + + impl<'a> From>> for Error { + fn from(v: nom::Err>) -> Self { + Error { + inner: match v { + nom::Err::Error(err) | nom::Err::Failure(err) => nom::error::VerboseError { + errors: err + .errors + .into_iter() + .map(|(i, v)| (i.as_bstr().to_owned(), v)) + .collect(), + }, + nom::Err::Incomplete(_) => unreachable!("we don't have streaming parsers"), + }, + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } + } + + impl std::error::Error for Error {} +} + +/// +#[cfg(not(feature = "verbose-object-parsing-errors"))] +pub mod decode { + /// The type to be used for parse errors, discards everything and is zero size + pub type ParseError<'a> = (); + /// The owned type to be used for parse errors, discards everything and is zero size + pub type ParseErrorOwned = (); + + /// A type to indicate errors during parsing and to abstract away details related to `nom`. + #[derive(Debug, Clone)] + pub struct Error { + /// The actual error + pub inner: ParseErrorOwned, + } + + impl<'a> From>> for Error { + fn from(v: nom::Err>) -> Self { + Error { + inner: match v { + nom::Err::Error(err) | nom::Err::Failure(err) => err, + nom::Err::Incomplete(_) => unreachable!("we don't have streaming parsers"), + }, + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } + } + + impl std::error::Error for Error {} +} diff --git a/git-object/src/mutable/commit.rs b/git-object/src/mutable/commit.rs deleted file mode 100644 index a52e7687034..00000000000 --- a/git-object/src/mutable/commit.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::io; - -use bstr::{BStr, BString, ByteSlice}; -use smallvec::SmallVec; - -use crate::{ - commit, - mutable::{encode, NL}, -}; - -/// A mutable git commit, representing an annotated state of a working tree along with a reference to its historical commits. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Commit { - /// The hash of recorded working tree state. - pub tree: git_hash::ObjectId, - /// Hash of each parent commit. Empty for the first commit in repository. - pub parents: SmallVec<[git_hash::ObjectId; 1]>, - /// Who wrote this commit. - pub author: git_actor::Signature, - /// Who committed this commit. - /// - /// 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. - pub committer: git_actor::Signature, - /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). - pub encoding: Option, - /// 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()]. - pub extra_headers: Vec<(BString, BString)>, -} - -impl Commit { - /// Returns a convenient iterator over all extra headers. - pub fn extra_headers(&self) -> commit::ExtraHeaders> { - commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr()))) - } - /// Serializes this instance to `out` in the git serialization format. - pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { - encode::trusted_header_id(b"tree", &self.tree, &mut out)?; - for parent in &self.parents { - encode::trusted_header_id(b"parent", parent, &mut out)?; - } - encode::trusted_header_signature(b"author", &self.author, &mut out)?; - encode::trusted_header_signature(b"committer", &self.committer, &mut out)?; - if let Some(encoding) = self.encoding.as_ref() { - encode::header_field(b"encoding", encoding, &mut out)?; - } - for (name, value) in &self.extra_headers { - let has_newline = value.find_byte(b'\n').is_some(); - if has_newline { - encode::header_field_multi_line(name, value, &mut out)?; - } else { - encode::trusted_header_field(name, value, &mut out)?; - } - } - out.write_all(NL)?; - out.write_all(&self.message) - } -} diff --git a/git-object/src/mutable/convert.rs b/git-object/src/mutable/convert.rs deleted file mode 100644 index e5325324772..00000000000 --- a/git-object/src/mutable/convert.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{immutable, mutable}; - -impl From> for mutable::Tag { - fn from(other: immutable::Tag<'_>) -> mutable::Tag { - let immutable::Tag { - target, - name, - target_kind, - message, - tagger: signature, - pgp_signature, - } = other; - mutable::Tag { - target: git_hash::ObjectId::from_hex(target).expect("40 bytes hex sha1"), - name: name.to_owned(), - target_kind, - message: message.to_owned(), - signature: signature.map(Into::into), - pgp_signature: pgp_signature.map(ToOwned::to_owned), - } - } -} - -impl From> for mutable::Commit { - fn from(other: immutable::Commit<'_>) -> mutable::Commit { - let immutable::Commit { - tree, - parents, - author, - committer, - encoding, - message, - extra_headers, - } = other; - mutable::Commit { - tree: git_hash::ObjectId::from_hex(tree).expect("40 bytes hex sha1"), - parents: parents - .iter() - .map(|parent| git_hash::ObjectId::from_hex(parent).expect("40 bytes hex sha1")) - .collect(), - author: author.into(), - committer: committer.into(), - encoding: encoding.map(ToOwned::to_owned), - message: message.to_owned(), - extra_headers: extra_headers - .into_iter() - .map(|(k, v)| (k.into(), v.into_owned())) - .collect(), - } - } -} - -impl<'a> From> for mutable::Blob { - fn from(v: immutable::Blob<'a>) -> Self { - mutable::Blob { - data: v.data.to_owned(), - } - } -} - -impl From> for mutable::Tree { - fn from(other: immutable::Tree<'_>) -> mutable::Tree { - let immutable::Tree { entries } = other; - mutable::Tree { - entries: entries.into_iter().map(Into::into).collect(), - } - } -} - -impl From> for mutable::tree::Entry { - fn from(other: immutable::tree::Entry<'_>) -> mutable::tree::Entry { - let immutable::tree::Entry { mode, filename, oid } = other; - mutable::tree::Entry { - mode, - filename: filename.to_owned(), - oid: oid.into(), - } - } -} - -impl<'a> From> for mutable::Object { - fn from(v: immutable::Object<'_>) -> Self { - match v { - immutable::Object::Tree(v) => mutable::Object::Tree(v.into()), - immutable::Object::Blob(v) => mutable::Object::Blob(v.into()), - immutable::Object::Commit(v) => mutable::Object::Commit(v.into()), - immutable::Object::Tag(v) => mutable::Object::Tag(v.into()), - } - } -} diff --git a/git-object/src/mutable/mod.rs b/git-object/src/mutable/mod.rs deleted file mode 100644 index a685298ac95..00000000000 --- a/git-object/src/mutable/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Mutable objects with each field being separately allocated and changeable. -//! -//! 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][crate::immutable::Object::from_bytes()] -//! and converting these [into mutable copies][crate::immutable::Object::into_mutable()] for adjustments. - -const NL: &[u8; 1] = b"\n"; -const SPACE: &[u8; 1] = b" "; - -mod convert; -mod encode; - -mod tag; -pub use tag::Tag; - -/// -pub mod tree; -pub use tree::Tree; - -mod commit; -pub use commit::Commit; - -mod blob { - use std::io; - - /// A mutable chunk of any [`data`][Blob::data]. - #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] - #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] - pub struct Blob { - /// The data itself. - pub data: Vec, - } - - impl Blob { - /// Write the blobs data to `out` verbatim. - pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { - out.write_all(&self.data) - } - } -} -pub use blob::Blob; - -mod object; -pub use object::Object; diff --git a/git-object/src/mutable/object.rs b/git-object/src/mutable/object.rs deleted file mode 100644 index 3314df648d0..00000000000 --- a/git-object/src/mutable/object.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::io; - -use crate::mutable; - -/// A mutable object representing [`Trees`][mutable::Tree], [`Blobs`][mutable::Blob], [`Commits`][mutable::Commit] or [`Tags`][mutable::Tag]. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -#[allow(clippy::large_enum_variant, missing_docs)] -pub enum Object { - Tree(mutable::Tree), - Blob(mutable::Blob), - Commit(mutable::Commit), - Tag(mutable::Tag), -} - -/// Convenient extraction of typed object. -impl Object { - /// Returns a [`Blob`][mutable::Blob] if it is one. - pub fn as_blob(&self) -> Option<&mutable::Blob> { - match self { - Object::Blob(v) => Some(v), - _ => None, - } - } - /// Returns a [`Commit`][mutable::Commit] if it is one. - pub fn as_commit(&self) -> Option<&mutable::Commit> { - match self { - Object::Commit(v) => Some(v), - _ => None, - } - } - /// Returns a [`Tree`][mutable::Tree] if it is one. - pub fn as_tree(&self) -> Option<&mutable::Tree> { - match self { - Object::Tree(v) => Some(v), - _ => None, - } - } - /// Returns a [`Tag`][mutable::Tag] if it is one. - pub fn as_tag(&self) -> Option<&mutable::Tag> { - match self { - Object::Tag(v) => Some(v), - _ => None, - } - } - /// Returns the kind of object stored in this instance. - pub fn kind(&self) -> crate::Kind { - match self { - Object::Tree(_) => crate::Kind::Tree, - Object::Blob(_) => crate::Kind::Blob, - Object::Commit(_) => crate::Kind::Commit, - Object::Tag(_) => crate::Kind::Tag, - } - } -} - -/// Serialization -impl Object { - /// Write the contained object to `out` in the git serialization format. - pub fn write_to(&self, out: impl io::Write) -> io::Result<()> { - use Object::*; - match self { - Tree(v) => v.write_to(out), - Blob(v) => v.write_to(out), - Commit(v) => v.write_to(out), - Tag(v) => v.write_to(out), - } - } -} - -mod convert { - use std::convert::TryFrom; - - use crate::mutable::{Blob, Commit, Object, Tag, Tree}; - - impl From for Object { - fn from(v: Tag) -> Self { - Object::Tag(v) - } - } - - impl From for Object { - fn from(v: Commit) -> Self { - Object::Commit(v) - } - } - - impl From for Object { - fn from(v: Tree) -> Self { - Object::Tree(v) - } - } - - impl From for Object { - fn from(v: Blob) -> Self { - Object::Blob(v) - } - } - - impl TryFrom for Tag { - type Error = Object; - - fn try_from(value: Object) -> Result { - Ok(match value { - Object::Tag(v) => v, - _ => return Err(value), - }) - } - } - - impl TryFrom for Commit { - type Error = Object; - - fn try_from(value: Object) -> Result { - Ok(match value { - Object::Commit(v) => v, - _ => return Err(value), - }) - } - } - - impl TryFrom for Tree { - type Error = Object; - - fn try_from(value: Object) -> Result { - Ok(match value { - Object::Tree(v) => v, - _ => return Err(value), - }) - } - } - - impl TryFrom for Blob { - type Error = Object; - - fn try_from(value: Object) -> Result { - Ok(match value { - Object::Blob(v) => v, - _ => return Err(value), - }) - } - } -} diff --git a/git-object/src/mutable/tree.rs b/git-object/src/mutable/tree.rs deleted file mode 100644 index 477b0e00ca0..00000000000 --- a/git-object/src/mutable/tree.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::{cmp::Ordering, io}; - -use bstr::{BString, ByteSlice}; -use quick_error::quick_error; - -use crate::{mutable::SPACE, tree::EntryMode}; - -quick_error! { - /// The Error used in [`Tree::write_to()`]. - #[derive(Debug)] - #[allow(missing_docs)] - pub enum Error { - NewlineInFilename(name: BString) { - display("Newlines are invalid in file paths: {:?}", name) - } - } -} - -impl From for io::Error { - fn from(err: Error) -> Self { - io::Error::new(io::ErrorKind::Other, err) - } -} - -/// A mutable Tree, containing other trees, blobs or commits. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Tree { - /// The directories and files contained in this tree. They must be and remain sorted by [`filename`][Entry::filename]. - pub entries: Vec, -} - -/// An entry in a [`Tree`], similar to an entry in a directory. -#[derive(PartialEq, Eq, Debug, Hash, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Entry { - /// The kind of object to which `oid` is pointing to. - pub mode: EntryMode, - /// The name of the file in the parent tree. - pub filename: BString, - /// The id of the object representing the entry. - pub oid: git_hash::ObjectId, -} - -impl PartialOrd for Entry { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Entry { - /// Entries compare by the common portion of the filename. This is critical for proper functioning of algorithms working on trees. - fn cmp(&self, other: &Self) -> Ordering { - let len = self.filename.len().min(other.filename.len()); - self.filename[..len].cmp(&other.filename[..len]) - } -} - -/// Serialization -impl EntryMode { - /// Return the representation as used in the git internal format. - pub fn as_bytes(&self) -> &'static [u8] { - use EntryMode::*; - match self { - Tree => b"40000", - Blob => b"100644", - BlobExecutable => b"100755", - Link => b"120000", - Commit => b"160000", - } - } -} - -/// Serialization -impl Tree { - /// Serialize this tree to `out` in the git internal format. - pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { - debug_assert_eq!( - &{ - let mut entries_sorted = self.entries.clone(); - entries_sorted.sort(); - entries_sorted - }, - &self.entries, - "entries for serialization must be sorted by filename" - ); - for Entry { mode, filename, oid } in &self.entries { - out.write_all(mode.as_bytes())?; - out.write_all(SPACE)?; - - if filename.find_byte(b'\n').is_some() { - return Err(Error::NewlineInFilename(filename.to_owned()).into()); - } - out.write_all(filename)?; - out.write_all(&[b'\0'])?; - - out.write_all(oid.as_bytes())?; - } - Ok(()) - } -} diff --git a/git-object/src/object/convert.rs b/git-object/src/object/convert.rs new file mode 100644 index 00000000000..60e2e58d6ee --- /dev/null +++ b/git-object/src/object/convert.rs @@ -0,0 +1,227 @@ +use crate::{tree, Blob, BlobRef, Commit, CommitRef, Object, ObjectRef, Tag, TagRef, Tree, TreeRef}; +use std::convert::TryFrom; + +impl From> for Tag { + fn from(other: TagRef<'_>) -> Tag { + let TagRef { + target, + name, + target_kind, + message, + tagger: signature, + pgp_signature, + } = other; + Tag { + target: git_hash::ObjectId::from_hex(target).expect("40 bytes hex sha1"), + name: name.to_owned(), + target_kind, + message: message.to_owned(), + signature: signature.map(Into::into), + pgp_signature: pgp_signature.map(ToOwned::to_owned), + } + } +} + +impl From> for Commit { + fn from(other: CommitRef<'_>) -> Commit { + let CommitRef { + tree, + parents, + author, + committer, + encoding, + message, + extra_headers, + } = other; + Commit { + tree: git_hash::ObjectId::from_hex(tree).expect("40 bytes hex sha1"), + parents: parents + .iter() + .map(|parent| git_hash::ObjectId::from_hex(parent).expect("40 bytes hex sha1")) + .collect(), + author: author.into(), + committer: committer.into(), + encoding: encoding.map(ToOwned::to_owned), + message: message.to_owned(), + extra_headers: extra_headers + .into_iter() + .map(|(k, v)| (k.into(), v.into_owned())) + .collect(), + } + } +} + +impl<'a> From> for Blob { + fn from(v: BlobRef<'a>) -> Self { + Blob { + data: v.data.to_owned(), + } + } +} + +impl From> for Tree { + fn from(other: TreeRef<'_>) -> Tree { + let TreeRef { entries } = other; + Tree { + entries: entries.into_iter().map(Into::into).collect(), + } + } +} + +impl From> for tree::Entry { + fn from(other: tree::EntryRef<'_>) -> tree::Entry { + let tree::EntryRef { mode, filename, oid } = other; + tree::Entry { + mode, + filename: filename.to_owned(), + oid: oid.into(), + } + } +} + +impl<'a> From> for Object { + fn from(v: ObjectRef<'_>) -> Self { + match v { + ObjectRef::Tree(v) => Object::Tree(v.into()), + ObjectRef::Blob(v) => Object::Blob(v.into()), + ObjectRef::Commit(v) => Object::Commit(v.into()), + ObjectRef::Tag(v) => Object::Tag(v.into()), + } + } +} + +impl From for Object { + fn from(v: Tag) -> Self { + Object::Tag(v) + } +} + +impl From for Object { + fn from(v: Commit) -> Self { + Object::Commit(v) + } +} + +impl From for Object { + fn from(v: Tree) -> Self { + Object::Tree(v) + } +} + +impl From for Object { + fn from(v: Blob) -> Self { + Object::Blob(v) + } +} + +impl TryFrom for Tag { + type Error = Object; + + fn try_from(value: Object) -> Result { + Ok(match value { + Object::Tag(v) => v, + _ => return Err(value), + }) + } +} + +impl TryFrom for Commit { + type Error = Object; + + fn try_from(value: Object) -> Result { + Ok(match value { + Object::Commit(v) => v, + _ => return Err(value), + }) + } +} + +impl TryFrom for Tree { + type Error = Object; + + fn try_from(value: Object) -> Result { + Ok(match value { + Object::Tree(v) => v, + _ => return Err(value), + }) + } +} + +impl TryFrom for Blob { + type Error = Object; + + fn try_from(value: Object) -> Result { + Ok(match value { + Object::Blob(v) => v, + _ => return Err(value), + }) + } +} + +impl<'a> From> for ObjectRef<'a> { + fn from(v: TagRef<'a>) -> Self { + ObjectRef::Tag(v) + } +} + +impl<'a> From> for ObjectRef<'a> { + fn from(v: CommitRef<'a>) -> Self { + ObjectRef::Commit(v) + } +} + +impl<'a> From> for ObjectRef<'a> { + fn from(v: TreeRef<'a>) -> Self { + ObjectRef::Tree(v) + } +} + +impl<'a> From> for ObjectRef<'a> { + fn from(v: BlobRef<'a>) -> Self { + ObjectRef::Blob(v) + } +} + +impl<'a> TryFrom> for TagRef<'a> { + type Error = ObjectRef<'a>; + + fn try_from(value: ObjectRef<'a>) -> Result { + Ok(match value { + ObjectRef::Tag(v) => v, + _ => return Err(value), + }) + } +} + +impl<'a> TryFrom> for CommitRef<'a> { + type Error = ObjectRef<'a>; + + fn try_from(value: ObjectRef<'a>) -> Result { + Ok(match value { + ObjectRef::Commit(v) => v, + _ => return Err(value), + }) + } +} + +impl<'a> TryFrom> for TreeRef<'a> { + type Error = ObjectRef<'a>; + + fn try_from(value: ObjectRef<'a>) -> Result { + Ok(match value { + ObjectRef::Tree(v) => v, + _ => return Err(value), + }) + } +} + +impl<'a> TryFrom> for BlobRef<'a> { + type Error = ObjectRef<'a>; + + fn try_from(value: ObjectRef<'a>) -> Result { + Ok(match value { + ObjectRef::Blob(v) => v, + _ => return Err(value), + }) + } +} diff --git a/git-object/src/object/mod.rs b/git-object/src/object/mod.rs new file mode 100644 index 00000000000..6ec2b263ec8 --- /dev/null +++ b/git-object/src/object/mod.rs @@ -0,0 +1,161 @@ +use crate::{Blob, Commit, Object, Tag, Tree}; + +mod convert; + +mod write { + use std::io; + + use crate::Object; + + /// Serialization + impl Object { + /// Write the contained object to `out` in the git serialization format. + pub fn write_to(&self, out: impl io::Write) -> io::Result<()> { + use crate::Object::*; + match self { + Tree(v) => v.write_to(out), + Blob(v) => v.write_to(out), + Commit(v) => v.write_to(out), + Tag(v) => v.write_to(out), + } + } + } +} + +/// Convenient extraction of typed object. +impl Object { + /// Returns a [`Blob`][Blob] if it is one. + pub fn as_blob(&self) -> Option<&Blob> { + match self { + Object::Blob(v) => Some(v), + _ => None, + } + } + /// Returns a [`Commit`][Commit] if it is one. + pub fn as_commit(&self) -> Option<&Commit> { + match self { + Object::Commit(v) => Some(v), + _ => None, + } + } + /// Returns a [`Tree`][Tree] if it is one. + pub fn as_tree(&self) -> Option<&Tree> { + match self { + Object::Tree(v) => Some(v), + _ => None, + } + } + /// Returns a [`Tag`][Tag] if it is one. + pub fn as_tag(&self) -> Option<&Tag> { + match self { + Object::Tag(v) => Some(v), + _ => None, + } + } + /// Returns the kind of object stored in this instance. + pub fn kind(&self) -> crate::Kind { + match self { + Object::Tree(_) => crate::Kind::Tree, + Object::Blob(_) => crate::Kind::Blob, + Object::Commit(_) => crate::Kind::Commit, + Object::Tag(_) => crate::Kind::Tag, + } + } +} + +use crate::{BlobRef, CommitRef, Kind, ObjectRef, TagRef, TreeRef}; + +impl<'a> ObjectRef<'a> { + /// Deserialize an object of `kind` from the given `data`. + pub fn from_bytes(kind: Kind, data: &'a [u8]) -> Result, crate::decode::Error> { + Ok(match kind { + Kind::Tree => ObjectRef::Tree(TreeRef::from_bytes(data)?), + Kind::Blob => ObjectRef::Blob(BlobRef { data }), + Kind::Commit => ObjectRef::Commit(CommitRef::from_bytes(data)?), + Kind::Tag => ObjectRef::Tag(TagRef::from_bytes(data)?), + }) + } + + /// Convert the immutable object into a mutable version, consuming the source in the process. + /// + /// Note that this is an expensive operation. + pub fn into_owned(self) -> Object { + self.into() + } + + /// Convert this immutable object into its mutable counterpart. + /// + /// Note that this is an expensive operation. + pub fn to_owned(&self) -> Object { + self.clone().into() + } +} + +/// Convenient access to contained objects. +impl<'a> ObjectRef<'a> { + /// Interpret this object as blob. + pub fn as_blob(&self) -> Option<&BlobRef<'a>> { + match self { + ObjectRef::Blob(v) => Some(v), + _ => None, + } + } + /// Interpret this object as blob, chainable. + pub fn into_blob(self) -> Option> { + match self { + ObjectRef::Blob(v) => Some(v), + _ => None, + } + } + /// Interpret this object as commit. + pub fn as_commit(&self) -> Option<&CommitRef<'a>> { + match self { + ObjectRef::Commit(v) => Some(v), + _ => None, + } + } + /// Interpret this object as commit, chainable. + pub fn into_commit(self) -> Option> { + match self { + ObjectRef::Commit(v) => Some(v), + _ => None, + } + } + /// Interpret this object as tree. + pub fn as_tree(&self) -> Option<&TreeRef<'a>> { + match self { + ObjectRef::Tree(v) => Some(v), + _ => None, + } + } + /// Interpret this object as tree, chainable + pub fn into_tree(self) -> Option> { + match self { + ObjectRef::Tree(v) => Some(v), + _ => None, + } + } + /// Interpret this object as tag. + pub fn as_tag(&self) -> Option<&TagRef<'a>> { + match self { + ObjectRef::Tag(v) => Some(v), + _ => None, + } + } + /// Interpret this object as tag, chainable. + pub fn into_tag(self) -> Option> { + match self { + ObjectRef::Tag(v) => Some(v), + _ => None, + } + } + /// Return the kind of object. + pub fn kind(&self) -> Kind { + match self { + ObjectRef::Tree(_) => Kind::Tree, + ObjectRef::Blob(_) => Kind::Blob, + ObjectRef::Commit(_) => Kind::Commit, + ObjectRef::Tag(_) => Kind::Tag, + } + } +} diff --git a/git-object/src/immutable/parse.rs b/git-object/src/parse.rs similarity index 100% rename from git-object/src/immutable/parse.rs rename to git-object/src/parse.rs diff --git a/git-object/src/tag/decode.rs b/git-object/src/tag/decode.rs new file mode 100644 index 00000000000..655e5009af8 --- /dev/null +++ b/git-object/src/tag/decode.rs @@ -0,0 +1,89 @@ +use nom::{ + branch::alt, + bytes::complete::{tag, take_until, take_while, take_while1}, + character::is_alphabetic, + combinator::{all_consuming, opt, recognize}, + error::{context, ContextError, ParseError}, + sequence::{preceded, tuple}, + IResult, +}; + +use crate::{parse, parse::NL, BStr, ByteSlice, TagRef}; + +pub fn git_tag<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], TagRef<'a>, E> { + let (i, target) = context("object <40 lowercase hex char>", |i| { + parse::header_field(i, b"object", parse::hex_hash) + })(i)?; + + let (i, kind) = context("type ", |i| { + parse::header_field(i, b"type", take_while1(is_alphabetic)) + })(i)?; + let kind = crate::Kind::from_bytes(kind) + .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))?; + + let (i, tag_version) = context("tag ", |i| { + parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) + })(i)?; + + let (i, signature) = context( + "tagger ", + opt(|i| parse::header_field(i, b"tagger", parse::signature)), + )(i)?; + let (i, (message, pgp_signature)) = all_consuming(message)(i)?; + Ok(( + i, + TagRef { + target, + name: tag_version.as_bstr(), + target_kind: kind, + message, + tagger: signature, + pgp_signature, + }, + )) +} + +pub fn message<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a BStr, Option<&'a BStr>), E> { + const PGP_SIGNATURE_BEGIN: &[u8] = b"\n-----BEGIN PGP SIGNATURE-----"; + const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----"; + + if i.is_empty() { + return Ok((i, (i.as_bstr(), None))); + } + let (i, _) = tag(NL)(i)?; + fn all_to_end<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], &'a [u8]), E> { + if i.is_empty() { + return Err(nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::Eof))); + } + // an empty signature message signals that there is none - the function signature is needed + // to work with 'alt(…)'. PGP signatures are never empty + Ok((&[], (i, &[]))) + } + let (i, (message, signature)) = alt(( + tuple(( + take_until(PGP_SIGNATURE_BEGIN), + preceded( + tag(NL), + recognize(tuple(( + tag(&PGP_SIGNATURE_BEGIN[1..]), + take_until(PGP_SIGNATURE_END), + tag(PGP_SIGNATURE_END), + take_while(|_| true), + ))), + ), + )), + all_to_end, + ))(i)?; + let (i, _) = opt(tag(NL))(i)?; + Ok(( + i, + ( + message.as_bstr(), + if signature.is_empty() { + None + } else { + Some(signature.as_bstr()) + }, + ), + )) +} diff --git a/git-object/src/tag/mod.rs b/git-object/src/tag/mod.rs new file mode 100644 index 00000000000..0d82598f587 --- /dev/null +++ b/git-object/src/tag/mod.rs @@ -0,0 +1,22 @@ +use crate::TagRef; + +mod decode; + +/// +pub mod write; + +/// +pub mod ref_iter; + +impl<'a> TagRef<'a> { + /// Deserialize a tag from `data`. + pub fn from_bytes(data: &'a [u8]) -> Result, crate::decode::Error> { + decode::git_tag(data) + .map(|(_, t)| t) + .map_err(crate::decode::Error::from) + } + /// The object this tag points to as `Id`. + pub fn target(&self) -> git_hash::ObjectId { + git_hash::ObjectId::from_hex(self.target).expect("prior validation") + } +} diff --git a/git-object/src/tag/ref_iter.rs b/git-object/src/tag/ref_iter.rs new file mode 100644 index 00000000000..7cf5250417e --- /dev/null +++ b/git-object/src/tag/ref_iter.rs @@ -0,0 +1,153 @@ +use bstr::BStr; +use git_hash::{oid, ObjectId}; +use nom::{ + bytes::complete::take_while1, + character::is_alphabetic, + combinator::{all_consuming, opt}, + error::{context, ParseError}, +}; + +use crate::{bstr::ByteSlice, parse, parse::NL, tag::decode, Kind, TagRefIter}; + +pub(crate) enum State { + Target, + TargetKind, + Name, + Tagger, + Message, +} + +impl Default for State { + fn default() -> Self { + State::Target + } +} + +impl<'a> TagRefIter<'a> { + /// Create a tag iterator from data. + pub fn from_bytes(data: &'a [u8]) -> TagRefIter<'a> { + TagRefIter { + data, + state: State::default(), + } + } + + /// Returns the target id of this tag if it is the first function called and if there is no error in decoding + /// the data. + /// + /// Note that this method must only be called once or else will always return None while consuming a single token. + /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they + /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again. + /// `next()`. + pub fn target_id(&mut self) -> Option { + self.next().and_then(Result::ok).and_then(Token::into_id) + } +} + +impl<'a> TagRefIter<'a> { + fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> { + use State::*; + Ok(match state { + Target => { + let (i, target) = context("object <40 lowercase hex char>", |i| { + parse::header_field(i, b"object", parse::hex_hash) + })(i)?; + *state = State::TargetKind; + ( + i, + Token::Target { + id: ObjectId::from_hex(target).expect("parsing validation"), + }, + ) + } + TargetKind => { + let (i, kind) = context("type ", |i| { + parse::header_field(i, b"type", take_while1(is_alphabetic)) + })(i)?; + let kind = crate::Kind::from_bytes(kind).map_err(|_| { + let err = crate::decode::ParseError::from_error_kind(i, nom::error::ErrorKind::MapRes); + nom::Err::Error(err) + })?; + *state = State::Name; + (i, Token::TargetKind(kind)) + } + Name => { + let (i, tag_version) = context("tag ", |i| { + parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) + })(i)?; + *state = State::Tagger; + (i, Token::Name(tag_version.as_bstr())) + } + Tagger => { + let (i, signature) = context( + "tagger ", + opt(|i| parse::header_field(i, b"tagger", parse::signature)), + )(i)?; + *state = State::Message; + (i, Token::Tagger(signature)) + } + Message => { + let (i, (message, pgp_signature)) = all_consuming(decode::message)(i)?; + debug_assert!( + i.is_empty(), + "we should have consumed all data - otherwise iter may go forever" + ); + return Ok((i, Token::Body { message, pgp_signature })); + } + }) + } +} + +impl<'a> Iterator for TagRefIter<'a> { + type Item = Result, crate::decode::Error>; + + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + match Self::next_inner(self.data, &mut self.state) { + Ok((data, token)) => { + self.data = data; + Some(Ok(token)) + } + Err(err) => { + self.data = &[]; + Some(Err(err)) + } + } + } +} + +/// A token returned by the [tag iterator][TagRefIter]. +#[allow(missing_docs)] +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum Token<'a> { + Target { + id: ObjectId, + }, + TargetKind(Kind), + Name(&'a BStr), + Tagger(Option>), + Body { + message: &'a BStr, + pgp_signature: Option<&'a BStr>, + }, +} + +impl<'a> Token<'a> { + /// Return the object id of this token if its a [Target][Token::Target]. + pub fn id(&self) -> Option<&oid> { + match self { + Token::Target { id } => Some(id.as_ref()), + _ => None, + } + } + + /// Return the owned object id of this token if its a [Target][Token::Target]. + pub fn into_id(self) -> Option { + match self { + Token::Target { id } => Some(id), + _ => None, + } + } +} diff --git a/git-object/src/mutable/tag.rs b/git-object/src/tag/write.rs similarity index 67% rename from git-object/src/mutable/tag.rs rename to git-object/src/tag/write.rs index 1c7e769384f..e39c80e57d6 100644 --- a/git-object/src/mutable/tag.rs +++ b/git-object/src/tag/write.rs @@ -1,13 +1,14 @@ use std::io; -use bstr::{BStr, BString}; +use bstr::BStr; use quick_error::quick_error; -use crate::mutable::{encode, NL}; +use crate::{encode, encode::NL, Tag}; quick_error! { /// An Error used in [`Tag::write_to()`]. #[derive(Debug)] + #[allow(missing_docs)] pub enum Error { StartsWithDash { display("Tags must not start with a dash: '-'") @@ -26,24 +27,6 @@ impl From for io::Error { } } -/// A mutable git tag. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Tag { - /// The hash this tag is pointing to. - pub target: git_hash::ObjectId, - /// The kind of object this tag is pointing to. - pub target_kind: crate::Kind, - /// The name of the tag, e.g. "v1.0". - pub name: BString, - /// The message describing the tag. - pub message: BString, - /// The tags author. - pub signature: Option, - /// A pgp signature over all bytes of the encoded tag, excluding the pgp signature itself. - pub pgp_signature: Option, -} - impl Tag { /// Writes the encoded tag to `out`. pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { diff --git a/git-object/src/mutable/tag/tests.rs b/git-object/src/tag/write/tests.rs similarity index 100% rename from git-object/src/mutable/tag/tests.rs rename to git-object/src/tag/write/tests.rs diff --git a/git-object/src/tree/mod.rs b/git-object/src/tree/mod.rs new file mode 100644 index 00000000000..4479f1bb7c3 --- /dev/null +++ b/git-object/src/tree/mod.rs @@ -0,0 +1,93 @@ +use std::cmp::Ordering; + +use crate::{ + bstr::{BStr, BString}, + tree, +}; + +mod ref_iter; +/// +pub mod write; + +/// The mode of items storable in a tree, similar to the file mode on a unix file system. +/// +/// Used in [mutable::Entry][crate::tree::Entry] and [EntryRef]. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)] +#[repr(u16)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[allow(missing_docs)] +pub enum EntryMode { + Tree = 0o040000u16, + Blob = 0o100644, + BlobExecutable = 0o100755, + Link = 0o120000, + Commit = 0o160000, +} + +impl EntryMode { + /// Return true if this entry mode represents a Tree/directory + pub fn is_tree(&self) -> bool { + *self == EntryMode::Tree + } + + /// Return true if this entry mode represents anything BUT Tree/directory + pub fn is_no_tree(&self) -> bool { + *self != EntryMode::Tree + } +} + +/// An element of a [`TreeRef`][crate::TreeRef::entries]. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct EntryRef<'a> { + /// The kind of object to which `oid` is pointing. + pub mode: tree::EntryMode, + /// The name of the file in the parent tree. + pub filename: &'a BStr, + /// The id of the object representing the entry. + // TODO: figure out how these should be called. id or oid? It's inconsistent around the codebase. + // Answer: make it 'id', as in `git2` + #[cfg_attr(feature = "serde1", serde(borrow))] + pub oid: &'a git_hash::oid, +} + +/// An entry in a [`Tree`][crate::Tree], similar to an entry in a directory. +#[derive(PartialEq, Eq, Debug, Hash, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Entry { + /// The kind of object to which `oid` is pointing to. + pub mode: EntryMode, + /// The name of the file in the parent tree. + pub filename: BString, + /// The id of the object representing the entry. + pub oid: git_hash::ObjectId, +} + +impl PartialOrd for Entry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Entry { + /// Entries compare by the common portion of the filename. This is critical for proper functioning of algorithms working on trees. + fn cmp(&self, other: &Self) -> Ordering { + let len = self.filename.len().min(other.filename.len()); + self.filename[..len].cmp(&other.filename[..len]) + } +} + +/// Serialization +impl EntryMode { + /// Return the representation as used in the git internal format. + pub fn as_bytes(&self) -> &'static [u8] { + use EntryMode::*; + match self { + Tree => b"40000", + Blob => b"100644", + BlobExecutable => b"100755", + Link => b"120000", + Commit => b"160000", + } + } +} diff --git a/git-object/src/immutable/tree.rs b/git-object/src/tree/ref_iter.rs similarity index 52% rename from git-object/src/immutable/tree.rs rename to git-object/src/tree/ref_iter.rs index 70fa4277872..8e859ce522b 100644 --- a/git-object/src/immutable/tree.rs +++ b/git-object/src/tree/ref_iter.rs @@ -1,78 +1,43 @@ use std::convert::TryFrom; -use bstr::BStr; +use crate::{tree, tree::EntryRef, TreeRef, TreeRefIter}; -use crate::{immutable::object, tree}; - -/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits). -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Tree<'a> { - /// The directories and files contained in this tree. - #[cfg_attr(feature = "serde1", serde(borrow))] - pub entries: Vec>, -} - -/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits), lazily evaluated. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct TreeIter<'a> { - /// The directories and files contained in this tree. - #[cfg_attr(feature = "serde1", serde(borrow))] - data: &'a [u8], -} - -impl<'a> TreeIter<'a> { +impl<'a> TreeRefIter<'a> { /// Instantiate an iterator from the given tree data. - pub fn from_bytes(data: &'a [u8]) -> TreeIter<'a> { - TreeIter { data } + pub fn from_bytes(data: &'a [u8]) -> TreeRefIter<'a> { + TreeRefIter { data } } } -/// An element of a [`Tree`][Tree::entries]. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Entry<'a> { - /// The kind of object to which `oid` is pointing. - pub mode: tree::EntryMode, - /// The name of the file in the parent tree. - pub filename: &'a BStr, - /// The id of the object representing the entry. - // TODO: figure out how these should be called. id or oid? It's inconsistent around the codebase. - // Answer: make it 'id', as in `git2` - #[cfg_attr(feature = "serde1", serde(borrow))] - pub oid: &'a git_hash::oid, -} - -impl<'a> Tree<'a> { +impl<'a> TreeRef<'a> { /// Deserialize a Tree from `data`. - pub fn from_bytes(data: &'a [u8]) -> Result, object::decode::Error> { - decode::tree(data).map(|(_, t)| t).map_err(object::decode::Error::from) + pub fn from_bytes(data: &'a [u8]) -> Result, crate::decode::Error> { + decode::tree(data).map(|(_, t)| t).map_err(crate::decode::Error::from) } /// Create an instance of the empty tree. /// /// It's particularly useful as static part of a program. - pub const fn empty() -> Tree<'static> { - Tree { entries: Vec::new() } + pub const fn empty() -> TreeRef<'static> { + TreeRef { entries: Vec::new() } } } -impl<'a> TreeIter<'a> { +impl<'a> TreeRefIter<'a> { /// Consume self and return all parsed entries. - pub fn entries(self) -> Result>, object::decode::Error> { + pub fn entries(self) -> Result>, crate::decode::Error> { self.collect() } } -impl<'a> Default for TreeIter<'a> { +impl<'a> Default for TreeRefIter<'a> { fn default() -> Self { - TreeIter { data: &[] } + TreeRefIter { data: &[] } } } -impl<'a> Iterator for TreeIter<'a> { - type Item = Result, object::decode::Error>; +impl<'a> Iterator for TreeRefIter<'a> { + type Item = Result, crate::decode::Error>; fn next(&mut self) -> Option { if self.data.is_empty() { @@ -122,14 +87,11 @@ mod decode { IResult, }; - use crate::{ - immutable::{parse::SPACE, tree::Entry, Tree}, - tree, - }; + use crate::{parse::SPACE, tree, tree::EntryRef, TreeRef}; const NULL: &[u8] = b"\0"; - pub fn entry<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], Entry<'_>, E> { + pub fn entry<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], EntryRef<'_>, E> { let (i, mode) = terminated(take_while_m_n(5, 6, is_digit), tag(SPACE))(i)?; let mode = tree::EntryMode::try_from(mode) .map_err(|invalid| nom::Err::Error(E::from_error_kind(invalid, nom::error::ErrorKind::MapRes)))?; @@ -138,7 +100,7 @@ mod decode { Ok(( i, - Entry { + EntryRef { mode, filename: filename.as_bstr(), oid: git_hash::oid::try_from(oid).expect("we counted exactly 20 bytes"), @@ -146,8 +108,8 @@ mod decode { )) } - pub fn tree<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], Tree<'a>, E> { + pub fn tree<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], TreeRef<'a>, E> { let (i, entries) = all_consuming(many0(entry))(i)?; - Ok((i, Tree { entries })) + Ok((i, TreeRef { entries })) } } diff --git a/git-object/src/tree/write.rs b/git-object/src/tree/write.rs new file mode 100644 index 00000000000..5f7e6fa9787 --- /dev/null +++ b/git-object/src/tree/write.rs @@ -0,0 +1,52 @@ +use std::io; + +use bstr::{BString, ByteSlice}; +use quick_error::quick_error; + +use crate::{encode::SPACE, tree::Entry, Tree}; + +quick_error! { + /// The Error used in [`Tree::write_to()`]. + #[derive(Debug)] + #[allow(missing_docs)] + pub enum Error { + NewlineInFilename(name: BString) { + display("Newlines are invalid in file paths: {:?}", name) + } + } +} + +impl From for io::Error { + fn from(err: Error) -> Self { + io::Error::new(io::ErrorKind::Other, err) + } +} + +/// Serialization +impl Tree { + /// Serialize this tree to `out` in the git internal format. + pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { + debug_assert_eq!( + &{ + let mut entries_sorted = self.entries.clone(); + entries_sorted.sort(); + entries_sorted + }, + &self.entries, + "entries for serialization must be sorted by filename" + ); + for Entry { mode, filename, oid } in &self.entries { + out.write_all(mode.as_bytes())?; + out.write_all(SPACE)?; + + if filename.find_byte(b'\n').is_some() { + return Err(Error::NewlineInFilename(filename.to_owned()).into()); + } + out.write_all(filename)?; + out.write_all(&[b'\0'])?; + + out.write_all(oid.as_bytes())?; + } + Ok(()) + } +} diff --git a/git-object/src/types.rs b/git-object/src/types.rs index 73212d7d27d..77d0b32f721 100644 --- a/git-object/src/types.rs +++ b/git-object/src/types.rs @@ -51,33 +51,3 @@ impl fmt::Display for Kind { f.write_str(std::str::from_utf8(self.as_bytes()).expect("Converting Kind name to utf8")) } } - -/// -pub mod tree { - /// The mode of items storable in a tree, similar to the file mode on a unix file system. - /// - /// Used in [mutable::Entry][crate::mutable::tree::Entry] and [tree::Entry][crate::immutable::tree::Entry]. - #[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)] - #[repr(u16)] - #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] - #[allow(missing_docs)] - pub enum EntryMode { - Tree = 0o040000u16, - Blob = 0o100644, - BlobExecutable = 0o100755, - Link = 0o120000, - Commit = 0o160000, - } - - impl EntryMode { - /// Return true if this entry mode represents a Tree/directory - pub fn is_tree(&self) -> bool { - *self == EntryMode::Tree - } - - /// Return true if this entry mode represents anything BUT Tree/directory - pub fn is_no_tree(&self) -> bool { - *self != EntryMode::Tree - } - } -} diff --git a/git-object/tests/immutable/commit.rs b/git-object/tests/immutable/commit.rs index 377d84b4187..f8a0598f830 100644 --- a/git-object/tests/immutable/commit.rs +++ b/git-object/tests/immutable/commit.rs @@ -131,7 +131,7 @@ dS3aXZhRfaPqpdsWrMB9fY7ll+oyfw== =T+RI -----END PGP SIGNATURE-----"; mod method { - use git_object::immutable::Commit; + use git_object::CommitRef; use pretty_assertions::assert_eq; use crate::{hex_to_id, immutable::fixture_bytes}; @@ -139,7 +139,7 @@ mod method { #[test] fn tree() -> crate::Result { let fixture = fixture_bytes("commit", "unsigned.txt"); - let commit = Commit::from_bytes(&fixture)?; + let commit = CommitRef::from_bytes(&fixture)?; assert_eq!(commit.tree(), hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d")); assert_eq!(commit.tree, "1b2dfb4ac5e42080b682fc676e9738c94ce6d54d"); Ok(()) @@ -147,10 +147,7 @@ mod method { } mod iter { - use git_object::{ - bstr::ByteSlice, - immutable::{commit::iter::Token, CommitIter}, - }; + use git_object::{bstr::ByteSlice, commit, commit::ref_iter::Token}; use crate::{ hex_to_id, @@ -163,7 +160,7 @@ mod iter { #[test] fn newline_right_after_signature_multiline_header() -> crate::Result { let data = fixture_bytes("commit", "signed-whitespace.txt"); - let tokens = CommitIter::from_bytes(&data).collect::, _>>()?; + let tokens = commit::CommitRefIter::from_bytes(&data).collect::, _>>()?; assert_eq!(tokens.len(), 7, "mainly a parsing exercise"); match tokens.last().expect("there are tokens") { Token::Message(msg) => { @@ -177,7 +174,7 @@ mod iter { #[test] fn signed_with_encoding() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt")) + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt")) .collect::, _>>()?, vec![ Token::Tree { @@ -203,7 +200,8 @@ mod iter { #[test] fn whitespace() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "whitespace.txt")).collect::, _>>()?, + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "whitespace.txt")) + .collect::, _>>()?, vec![ Token::Tree { id: hex_to_id("9bed6275068a0575243ba8409253e61af81ab2ff") @@ -226,7 +224,8 @@ mod iter { #[test] fn unsigned() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")).collect::, _>>()?, + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")) + .collect::, _>>()?, vec![ Token::Tree { id: hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d") @@ -246,7 +245,8 @@ mod iter { #[test] fn signed_singleline() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "signed-singleline.txt")).collect::, _>>()?, + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "signed-singleline.txt")) + .collect::, _>>()?, vec![ Token::Tree { id: hex_to_id("00fc39317701176e326974ce44f5bd545a32ec0b") @@ -270,7 +270,7 @@ mod iter { #[test] fn error_handling() -> crate::Result { let data = fixture_bytes("commit", "unsigned.txt"); - let iter = CommitIter::from_bytes(&data[..data.len() / 2]); + let iter = commit::CommitRefIter::from_bytes(&data[..data.len() / 2]); let tokens = iter.collect::>(); assert!( tokens.last().expect("at least the errored token").is_err(), @@ -282,7 +282,8 @@ mod iter { #[test] fn mergetag() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "mergetag.txt")).collect::, _>>()?, + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "mergetag.txt")) + .collect::, _>>()?, vec![ Token::Tree { id: hex_to_id("1c61918031bf2c7fab9e17dde3c52a6a9884fcb5") @@ -307,7 +308,7 @@ mod iter { } mod method { - use git_object::immutable::CommitIter; + use git_object::commit; use crate::{ hex_to_id, @@ -317,11 +318,11 @@ mod iter { #[test] fn tree_id() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")).tree_id(), + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")).tree_id(), Some(hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d")) ); assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")) + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")) .signatures() .collect::>(), vec![signature(1592437401), signature(1592437401)] @@ -332,7 +333,7 @@ mod iter { #[test] fn signatures() -> crate::Result { assert_eq!( - CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")) + commit::CommitRefIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")) .signatures() .collect::>(), vec![signature(1592437401), signature(1592437401)] @@ -343,7 +344,7 @@ mod iter { } mod from_bytes { - use git_object::{bstr::ByteSlice, immutable::Commit}; + use git_object::{bstr::ByteSlice, CommitRef}; use smallvec::SmallVec; use crate::immutable::{ @@ -354,8 +355,8 @@ mod from_bytes { #[test] fn unsigned() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "unsigned.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "unsigned.txt"))?, + CommitRef { tree: b"1b2dfb4ac5e42080b682fc676e9738c94ce6d54d".as_bstr(), parents: SmallVec::default(), author: signature(1592437401), @@ -371,8 +372,8 @@ mod from_bytes { #[test] fn whitespace() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "whitespace.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "whitespace.txt"))?, + CommitRef { tree: b"9bed6275068a0575243ba8409253e61af81ab2ff".as_bstr(), parents: SmallVec::from(vec![b"26b4df046d1776c123ac69d918f5aec247b58cc6".as_bstr()]), author: signature(1592448450), @@ -388,8 +389,8 @@ mod from_bytes { #[test] fn signed_singleline() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "signed-singleline.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "signed-singleline.txt"))?, + CommitRef { tree: b"00fc39317701176e326974ce44f5bd545a32ec0b".as_bstr(), parents: SmallVec::from(vec![b"09d8d3a12e161a7f6afb522dbe8900a9c09bce06".as_bstr()]), author: signature(1592391367), @@ -405,7 +406,7 @@ mod from_bytes { #[test] fn mergetag() -> crate::Result { let fixture = fixture_bytes("commit", "mergetag.txt"); - let commit = Commit { + let commit = CommitRef { tree: b"1c61918031bf2c7fab9e17dde3c52a6a9884fcb5".as_bstr(), parents: SmallVec::from(vec![ b"44ebe016df3aad96e3be8f95ec52397728dd7701".as_bstr(), @@ -420,7 +421,7 @@ mod from_bytes { std::borrow::Cow::Owned(MERGE_TAG.as_bytes().into()), )], }; - assert_eq!(Commit::from_bytes(&fixture)?, commit); + assert_eq!(CommitRef::from_bytes(&fixture)?, commit); assert_eq!(commit.extra_headers().find_all("mergetag").count(), 1); assert_eq!(commit.extra_headers().mergetags().count(), 1); Ok(()) @@ -429,8 +430,8 @@ mod from_bytes { #[test] fn signed() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "signed.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "signed.txt"))?, + CommitRef { tree: b"00fc39317701176e326974ce44f5bd545a32ec0b".as_bstr(), parents: SmallVec::from(vec![b"09d8d3a12e161a7f6afb522dbe8900a9c09bce06".as_bstr()]), author: signature(1592391367), @@ -446,8 +447,8 @@ mod from_bytes { #[test] fn signed_with_encoding() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt"))?, + CommitRef { tree: b"1973afa74d87b2bb73fa884aaaa8752aec43ea88".as_bstr(), parents: SmallVec::from(vec![b"79c51cc86923e2b8ca0ee5c4eb75e48027133f9a".as_bstr()]), author: signature(1592448995), @@ -463,8 +464,8 @@ mod from_bytes { #[test] fn with_encoding() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "with-encoding.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "with-encoding.txt"))?, + CommitRef { tree: b"4a1c03029e7407c0afe9fc0320b3258e188b115e".as_bstr(), parents: SmallVec::from(vec![b"7ca98aad461a5c302cb4c9e3acaaa6053cc67a62".as_bstr()]), author: signature(1592438199), @@ -480,8 +481,8 @@ mod from_bytes { #[test] fn merge() -> crate::Result { assert_eq!( - Commit::from_bytes(&fixture_bytes("commit", "merge.txt"))?, - Commit { + CommitRef::from_bytes(&fixture_bytes("commit", "merge.txt"))?, + CommitRef { tree: b"0cf16ce8e229b59a761198975f0c0263229faf82".as_bstr(), parents: SmallVec::from(vec![ b"6a6054db4ce3c1e4e6a37f8c4d7acb63a4d6ad71".as_bstr(), @@ -512,7 +513,7 @@ iyBBl69jASy41Ug/BlFJbw4+ItkShpXwkJKuBBV/JExChmvbxYWaS7QnyYC9UO0= #[test] fn newline_right_after_signature_multiline_header() -> crate::Result { let fixture = fixture_bytes("commit", "signed-whitespace.txt"); - let commit = Commit::from_bytes(&fixture)?; + let commit = CommitRef::from_bytes(&fixture)?; let pgp_sig = 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)); diff --git a/git-object/tests/immutable/tag.rs b/git-object/tests/immutable/tag.rs index 76aa22633c9..07c5d7f017c 100644 --- a/git-object/tests/immutable/tag.rs +++ b/git-object/tests/immutable/tag.rs @@ -1,7 +1,7 @@ -use git_object::{bstr::ByteSlice, immutable::Tag, Kind}; +use git_object::{bstr::ByteSlice, Kind, TagRef}; mod method { - use git_object::immutable::Tag; + use git_object::TagRef; use pretty_assertions::assert_eq; use crate::{hex_to_id, immutable::fixture_bytes}; @@ -9,7 +9,7 @@ mod method { #[test] fn target() -> crate::Result { let fixture = fixture_bytes("tag", "signed.txt"); - let tag = Tag::from_bytes(&fixture)?; + let tag = TagRef::from_bytes(&fixture)?; assert_eq!(tag.target(), hex_to_id("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec")); assert_eq!(tag.target, "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bytes()); Ok(()) @@ -17,11 +17,7 @@ mod method { } mod iter { - use git_object::{ - bstr::ByteSlice, - immutable::{tag::iter::Token, TagIter}, - Kind, - }; + use git_object::{bstr::ByteSlice, tag::ref_iter::Token, Kind, TagRefIter}; use crate::{ hex_to_id, @@ -31,7 +27,7 @@ mod iter { #[test] fn empty() -> crate::Result { assert_eq!( - TagIter::from_bytes(&fixture_bytes("tag", "empty.txt")).collect::, _>>()?, + TagRefIter::from_bytes(&fixture_bytes("tag", "empty.txt")).collect::, _>>()?, vec![ Token::Target { id: hex_to_id("01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc") @@ -47,7 +43,7 @@ mod iter { #[test] fn no_tagger() -> crate::Result { assert_eq!( - TagIter::from_bytes(&fixture_bytes("tag", "no-tagger.txt")).collect::, _>>()?, + TagRefIter::from_bytes(&fixture_bytes("tag", "no-tagger.txt")).collect::, _>>()?, vec![ Token::Target { id: hex_to_id("c39ae07f393806ccf406ef966e9a15afc43cc36a") @@ -83,7 +79,7 @@ KLMHist5yj0sw1E4hDTyQa0= #[test] fn whitespace() -> crate::Result { assert_eq!( - TagIter::from_bytes(&fixture_bytes("tag", "whitespace.txt")).collect::, _>>()?, + TagRefIter::from_bytes(&fixture_bytes("tag", "whitespace.txt")).collect::, _>>()?, vec![ Token::Target { id: hex_to_id("01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc") @@ -103,7 +99,7 @@ KLMHist5yj0sw1E4hDTyQa0= #[test] fn error_handling() -> crate::Result { let data = fixture_bytes("tag", "empty.txt"); - let iter = TagIter::from_bytes(&data[..data.len() / 2]); + let iter = TagRefIter::from_bytes(&data[..data.len() / 2]); let tokens = iter.collect::>(); assert!( tokens.last().expect("at least the errored token").is_err(), @@ -114,21 +110,24 @@ KLMHist5yj0sw1E4hDTyQa0= } mod from_bytes { - use git_object::{bstr::ByteSlice, immutable::Tag, Kind}; + use git_object::{bstr::ByteSlice, Kind, TagRef}; use crate::immutable::{fixture_bytes, signature, tag::tag_fixture}; #[test] fn signed() -> crate::Result { - assert_eq!(Tag::from_bytes(&fixture_bytes("tag", "signed.txt"))?, tag_fixture(9000)); + assert_eq!( + TagRef::from_bytes(&fixture_bytes("tag", "signed.txt"))?, + tag_fixture(9000) + ); Ok(()) } #[test] fn empty() -> crate::Result { assert_eq!( - Tag::from_bytes(&fixture_bytes("tag", "empty.txt"))?, - Tag { + TagRef::from_bytes(&fixture_bytes("tag", "empty.txt"))?, + TagRef { target: b"01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc".as_bstr(), name: b"empty".as_bstr(), target_kind: Kind::Commit, @@ -143,8 +142,8 @@ mod from_bytes { #[test] fn with_newlines() -> crate::Result { assert_eq!( - Tag::from_bytes(&fixture_bytes("tag", "with-newlines.txt"))?, - Tag { + TagRef::from_bytes(&fixture_bytes("tag", "with-newlines.txt"))?, + TagRef { target: b"ebdf205038b66108c0331aa590388431427493b7".as_bstr(), name: b"baz".as_bstr(), target_kind: Kind::Commit, @@ -159,8 +158,8 @@ mod from_bytes { #[test] fn no_tagger() -> crate::Result { assert_eq!( - Tag::from_bytes(&fixture_bytes("tag", "no-tagger.txt"))?, - Tag { + TagRef::from_bytes(&fixture_bytes("tag", "no-tagger.txt"))?, + TagRef { target: b"c39ae07f393806ccf406ef966e9a15afc43cc36a".as_bstr(), name: b"v2.6.11-tree".as_bstr(), target_kind: Kind::Tree, @@ -191,8 +190,8 @@ KLMHist5yj0sw1E4hDTyQa0= #[test] fn whitespace() -> crate::Result { assert_eq!( - Tag::from_bytes(&fixture_bytes("tag", "whitespace.txt"))?, - Tag { + TagRef::from_bytes(&fixture_bytes("tag", "whitespace.txt"))?, + TagRef { target: b"01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc".as_bstr(), name: b"whitespace".as_bstr(), target_kind: Kind::Commit, @@ -205,8 +204,8 @@ KLMHist5yj0sw1E4hDTyQa0= } } -fn tag_fixture(offset: i32) -> Tag<'static> { - Tag { +fn tag_fixture(offset: i32) -> TagRef<'static> { + TagRef { target: b"ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bstr(), name: b"1.0.0".as_bstr(), target_kind: Kind::Commit, diff --git a/git-object/tests/immutable/tree.rs b/git-object/tests/immutable/tree.rs index 37bf5f60789..24d210dd062 100644 --- a/git-object/tests/immutable/tree.rs +++ b/git-object/tests/immutable/tree.rs @@ -1,21 +1,17 @@ mod iter { - use git_object::{ - bstr::ByteSlice, - immutable::{tree::Entry, TreeIter}, - tree, - }; + use git_object::{bstr::ByteSlice, tree, tree::EntryRef, TreeRefIter}; use crate::{hex_to_id, immutable::fixture_bytes}; #[test] fn empty() { - assert_eq!(TreeIter::from_bytes(&[]).count(), 0, "empty trees are definitely ok"); + assert_eq!(TreeRefIter::from_bytes(&[]).count(), 0, "empty trees are definitely ok"); } #[test] fn error_handling() { let data = fixture_bytes("tree", "everything.tree"); - let iter = TreeIter::from_bytes(&data[..data.len() / 2]); + let iter = TreeRefIter::from_bytes(&data[..data.len() / 2]); let entries = iter.collect::>(); assert!( entries.last().expect("at least one token").is_err(), @@ -26,29 +22,29 @@ mod iter { #[test] fn everything() -> crate::Result { assert_eq!( - TreeIter::from_bytes(&fixture_bytes("tree", "everything.tree")).collect::, _>>()?, + TreeRefIter::from_bytes(&fixture_bytes("tree", "everything.tree")).collect::, _>>()?, vec![ - Entry { + EntryRef { mode: tree::EntryMode::BlobExecutable, filename: b"exe".as_bstr(), oid: &hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") }, - Entry { + EntryRef { mode: tree::EntryMode::Blob, filename: b"file".as_bstr(), oid: &hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") }, - Entry { + EntryRef { mode: tree::EntryMode::Commit, filename: b"grit-submodule".as_bstr(), oid: &hex_to_id("b2d1b5d684bdfda5f922b466cc13d4ce2d635cf8") }, - Entry { + EntryRef { mode: tree::EntryMode::Tree, filename: b"subdir".as_bstr(), oid: &hex_to_id("4d5fcadc293a348e88f777dc0920f11e7d71441c") }, - Entry { + EntryRef { mode: tree::EntryMode::Link, filename: b"symlink".as_bstr(), oid: &hex_to_id("1a010b1c0f081b2e8901d55307a15c29ff30af0e") @@ -60,19 +56,15 @@ mod iter { } mod from_bytes { - use git_object::{ - bstr::ByteSlice, - immutable::{tree::Entry, Tree}, - tree, - }; + use git_object::{bstr::ByteSlice, tree, tree::EntryRef, TreeRef}; use crate::{hex_to_id, immutable::fixture_bytes}; #[test] fn empty() -> crate::Result { assert_eq!( - Tree::from_bytes(&[])?, - Tree { entries: vec![] }, + TreeRef::from_bytes(&[])?, + TreeRef { entries: vec![] }, "empty trees are valid despite usually rare in the wild" ); Ok(()) @@ -81,30 +73,30 @@ mod from_bytes { #[test] fn everything() -> crate::Result { assert_eq!( - Tree::from_bytes(&fixture_bytes("tree", "everything.tree"))?, - Tree { + TreeRef::from_bytes(&fixture_bytes("tree", "everything.tree"))?, + TreeRef { entries: vec![ - Entry { + EntryRef { mode: tree::EntryMode::BlobExecutable, filename: b"exe".as_bstr(), oid: &hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") }, - Entry { + EntryRef { mode: tree::EntryMode::Blob, filename: b"file".as_bstr(), oid: &hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") }, - Entry { + EntryRef { mode: tree::EntryMode::Commit, filename: b"grit-submodule".as_bstr(), oid: &hex_to_id("b2d1b5d684bdfda5f922b466cc13d4ce2d635cf8") }, - Entry { + EntryRef { mode: tree::EntryMode::Tree, filename: b"subdir".as_bstr(), oid: &hex_to_id("4d5fcadc293a348e88f777dc0920f11e7d71441c") }, - Entry { + EntryRef { mode: tree::EntryMode::Link, filename: b"symlink".as_bstr(), oid: &hex_to_id("1a010b1c0f081b2e8901d55307a15c29ff30af0e") @@ -118,7 +110,7 @@ mod from_bytes { #[test] fn maybe_special() -> crate::Result { assert_eq!( - Tree::from_bytes(&fixture_bytes("tree", "maybe-special.tree"))? + TreeRef::from_bytes(&fixture_bytes("tree", "maybe-special.tree"))? .entries .len(), 160 @@ -129,7 +121,7 @@ mod from_bytes { #[test] fn definitely_special() -> crate::Result { assert_eq!( - Tree::from_bytes(&fixture_bytes("tree", "definitely-special.tree"))? + TreeRef::from_bytes(&fixture_bytes("tree", "definitely-special.tree"))? .entries .len(), 19 diff --git a/git-object/tests/mutable/mod.rs b/git-object/tests/mutable/mod.rs index f7644c8ca2f..0d3e2bb8436 100644 --- a/git-object/tests/mutable/mod.rs +++ b/git-object/tests/mutable/mod.rs @@ -3,7 +3,7 @@ macro_rules! round_trip { #[test] fn round_trip() -> Result<(), Box> { use crate::fixture_bytes; - use git_object::{mutable, immutable}; + use git_object::{ObjectRef, Object}; use bstr::ByteSlice; for input in &[ $( $files ),* @@ -16,7 +16,7 @@ macro_rules! round_trip { assert_eq!(output.as_bstr(), input.as_bstr()); // Test the parse->borrowed->owned->write chain for the top-level objects - let item: mutable::Object = immutable::Object::from(<$borrowed>::from_bytes(&input)?).into(); + let item: Object = ObjectRef::from(<$borrowed>::from_bytes(&input)?).into(); output.clear(); item.write_to(&mut output)?; assert_eq!(output.as_bstr(), input.as_bstr()); @@ -29,8 +29,8 @@ macro_rules! round_trip { mod object; mod tag { round_trip!( - mutable::Tag, - immutable::Tag, + git_object::Tag, + git_object::TagRef, "tag/empty.txt", "tag/no-tagger.txt", "tag/whitespace.txt", @@ -41,8 +41,8 @@ mod tag { mod commit { round_trip!( - mutable::Commit, - immutable::Commit, + git_object::Commit, + git_object::CommitRef, "commit/signed-whitespace.txt", "commit/two-multiline-headers.txt", "commit/mergetag.txt", @@ -57,10 +57,10 @@ mod commit { } mod tree { - round_trip!(mutable::Tree, immutable::Tree, "tree/everything.tree"); + round_trip!(git_object::Tree, git_object::TreeRef, "tree/everything.tree"); } mod blob { // It doesn't matter which data we use - it's not interpreted. - round_trip!(mutable::Blob, immutable::Blob, "tree/everything.tree"); + round_trip!(git_object::Blob, git_object::BlobRef, "tree/everything.tree"); } diff --git a/git-object/tests/mutable/object.rs b/git-object/tests/mutable/object.rs index 49758394315..6a934aa0207 100644 --- a/git-object/tests/mutable/object.rs +++ b/git-object/tests/mutable/object.rs @@ -1,4 +1,4 @@ -use git_object::mutable::Object; +use git_object::Object; #[test] fn size_in_memory() { diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index 3c71ce5b21c..c1d6b112d51 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -30,7 +30,7 @@ all-features = true [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["rustsha1", "walkdir", "zlib"] } git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } git-pack = { version ="^0.9.0", path = "../git-pack" } btoi = "0.4.2" diff --git a/git-odb/src/store/compound/write.rs b/git-odb/src/store/compound/write.rs index 3716387a90b..bbaa7e845b3 100644 --- a/git-odb/src/store/compound/write.rs +++ b/git-odb/src/store/compound/write.rs @@ -1,13 +1,13 @@ use std::io::Read; -use git_object::{mutable, Kind}; +use git_object::{Kind, Object}; use crate::store::{compound, loose}; impl crate::write::Write for compound::Store { type Error = loose::write::Error; - fn write(&self, object: &mutable::Object, hash: git_hash::Kind) -> Result { + fn write(&self, object: &Object, hash: git_hash::Kind) -> Result { self.loose.write(object, hash) } diff --git a/git-odb/src/store/linked/write.rs b/git-odb/src/store/linked/write.rs index 6f17b5a13a1..8a34f787a02 100644 --- a/git-odb/src/store/linked/write.rs +++ b/git-odb/src/store/linked/write.rs @@ -1,13 +1,13 @@ use std::io::Read; -use git_object::{mutable, Kind}; +use git_object::{Kind, Object}; use crate::store::{linked, loose}; impl crate::write::Write for linked::Store { type Error = loose::write::Error; - fn write(&self, object: &mutable::Object, hash: git_hash::Kind) -> Result { + fn write(&self, object: &Object, hash: git_hash::Kind) -> Result { self.dbs[0].loose.write(object, hash) } diff --git a/git-odb/src/write.rs b/git-odb/src/write.rs index 67c96874aa7..bb35723908a 100644 --- a/git-odb/src/write.rs +++ b/git-odb/src/write.rs @@ -1,6 +1,6 @@ use std::io; -use git_object::mutable; +use git_object::Object; /// Describe the capability to write git objects into an object store. pub trait Write { @@ -9,9 +9,9 @@ pub trait Write { /// _Note_ the default implementations require the `From` bound. type Error: std::error::Error + From; - /// Write [`object`][mutable::Object] using the given kind of [`hash`][git_hash::Kind] into the database, + /// Write [`object`][Object] using the given kind of [`hash`][git_hash::Kind] into the database, /// returning id to reference it in subsequent reads. - fn write(&self, object: &mutable::Object, hash: git_hash::Kind) -> Result { + fn write(&self, object: &Object, hash: git_hash::Kind) -> Result { let mut buf = Vec::with_capacity(2048); object.write_to(&mut buf)?; self.write_stream(object.kind(), buf.len() as u64, buf.as_slice(), hash) diff --git a/git-odb/tests/odb/store/loose/backend.rs b/git-odb/tests/odb/store/loose/backend.rs index f94c8749f26..649392c7cb6 100644 --- a/git-odb/tests/odb/store/loose/backend.rs +++ b/git-odb/tests/odb/store/loose/backend.rs @@ -55,7 +55,7 @@ mod write { } mod locate { - use git_object::{bstr::ByteSlice, immutable, immutable::tree, tree::EntryMode, Kind}; + use git_object::{bstr::ByteSlice, tree::EntryMode, BlobRef, CommitRef, Kind, TagRef, TreeRef}; use crate::{ hex_to_id, @@ -75,7 +75,7 @@ mod locate { let o = locate("722fe60ad4f0276d5a8121970b5bb9dccdad4ef9", &mut buf); assert_eq!(o.kind, Kind::Tag); assert_eq!(o.data.len(), 1024); - let expected = immutable::Tag { + let expected = TagRef { target: b"ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bstr(), name: b"1.0.0".as_bstr(), target_kind: Kind::Commit, @@ -113,7 +113,7 @@ cjHJZXWmV4CcRfmLsXzU8s2cR9A0DBvOxhPD1TlKC2JhBFXigjuL9U4Rbq9tdegB let o = locate("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec", &mut buf); assert_eq!(o.kind, Kind::Commit); assert_eq!(o.data.len(), 1084); - let expected = immutable::Commit { + let expected = CommitRef { tree: b"6ba2a0ded519f737fd5b8d5ccfb141125ef3176f".as_bstr(), parents: vec![].into(), author: signature(1528473303), @@ -141,7 +141,7 @@ cjHJZXWmV4CcRfmLsXzU8s2cR9A0DBvOxhPD1TlKC2JhBFXigjuL9U4Rbq9tdegB let o = locate("37d4e6c5c48ba0d245164c4e10d5f41140cab980", &mut buf); assert_eq!( o.decode()?.as_blob().expect("blob"), - &immutable::Blob { + &BlobRef { data: &[104, 105, 32, 116, 104, 101, 114, 101, 10] }, "small blobs are treated similarly to other object types and are read into memory at once when the header is read" @@ -182,16 +182,16 @@ cjHJZXWmV4CcRfmLsXzU8s2cR9A0DBvOxhPD1TlKC2JhBFXigjuL9U4Rbq9tdegB assert_eq!(o.kind, Kind::Tree); assert_eq!(o.data.len(), 66); - let expected = immutable::Tree { + let expected = TreeRef { entries: vec![ - tree::Entry { + git_object::tree::EntryRef { mode: EntryMode::Tree, filename: b"dir".as_bstr(), oid: as_id(&[ 150, 174, 134, 139, 53, 57, 245, 81, 200, 143, 213, 240, 35, 148, 208, 34, 88, 27, 17, 176, ]), }, - tree::Entry { + git_object::tree::EntryRef { mode: EntryMode::Blob, filename: b"file.txt".as_bstr(), oid: as_id(&[ diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index d6c236f702e..b3821c031a8 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -34,7 +34,7 @@ all-features = true [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } git-traverse = { version ="0.7.0", path = "../git-traverse" } git-diff = { version ="0.8.0", path = "../git-diff" } git-tempfile = { version ="^1.0.0", path = "../git-tempfile" } diff --git a/git-pack/src/data/object.rs b/git-pack/src/data/object.rs index fe1433e6c18..cd6392232a1 100644 --- a/git-pack/src/data/object.rs +++ b/git-pack/src/data/object.rs @@ -1,6 +1,6 @@ //! Contains a borrowed Object bound to a buffer holding its decompressed data. -use git_object::immutable; +use git_object::{commit, BlobRef, CommitRef, ObjectRef, TagRef, TagRefIter, TreeRef, TreeRefIter}; /// A borrowed object using a borrowed slice as backing buffer. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -22,43 +22,43 @@ impl<'a> Object<'a> { pack_location: None, } } - /// Decodes the data in the backing slice into a [`git_object::immutable::Object`], allowing to access all of its data + /// Decodes the data in the backing slice into a [`git_object::ObjectRef`], allowing to access all of its data /// conveniently. The cost of parsing an object is negligible. /// - /// **Note** that [mutable, decoded objects][git_object::mutable::Object] can be created from a [`crate::data::Object`] - /// using [`git_object::immutable::Object::into_mutable()`]. - pub fn decode(&self) -> Result, immutable::object::decode::Error> { + /// **Note** that [mutable, decoded objects][git_object::Object] can be created from a [`crate::data::Object`] + /// using [`git_object::ObjectRef::into_owned()`]. + pub fn decode(&self) -> Result, git_object::decode::Error> { Ok(match self.kind { - git_object::Kind::Tree => immutable::Object::Tree(immutable::Tree::from_bytes(self.data)?), - git_object::Kind::Blob => immutable::Object::Blob(immutable::Blob { data: self.data }), - git_object::Kind::Commit => immutable::Object::Commit(immutable::Commit::from_bytes(self.data)?), - git_object::Kind::Tag => immutable::Object::Tag(immutable::Tag::from_bytes(self.data)?), + git_object::Kind::Tree => ObjectRef::Tree(TreeRef::from_bytes(self.data)?), + git_object::Kind::Blob => ObjectRef::Blob(BlobRef { data: self.data }), + git_object::Kind::Commit => ObjectRef::Commit(CommitRef::from_bytes(self.data)?), + git_object::Kind::Tag => ObjectRef::Tag(TagRef::from_bytes(self.data)?), }) } /// Returns this object as tree iterator to parse entries one at a time to avoid allocations, or /// `None` if this is not a tree object. - pub fn into_tree_iter(self) -> Option> { + pub fn into_tree_iter(self) -> Option> { match self.kind { - git_object::Kind::Tree => Some(immutable::TreeIter::from_bytes(self.data)), + git_object::Kind::Tree => Some(TreeRefIter::from_bytes(self.data)), _ => None, } } /// Returns this object as commit iterator to parse tokens one at a time to avoid allocations, or /// `None` if this is not a commit object. - pub fn into_commit_iter(self) -> Option> { + pub fn into_commit_iter(self) -> Option> { match self.kind { - git_object::Kind::Commit => Some(immutable::CommitIter::from_bytes(self.data)), + git_object::Kind::Commit => Some(commit::CommitRefIter::from_bytes(self.data)), _ => None, } } /// Returns this object as tag iterator to parse tokens one at a time to avoid allocations, or /// `None` if this is not a tag object. - pub fn into_tag_iter(self) -> Option> { + pub fn into_tag_iter(self) -> Option> { match self.kind { - git_object::Kind::Tag => Some(immutable::TagIter::from_bytes(self.data)), + git_object::Kind::Tag => Some(TagRefIter::from_bytes(self.data)), _ => None, } } diff --git a/git-pack/src/data/output/count/objects.rs b/git-pack/src/data/output/count/objects.rs index 87bd0aa661e..f766ca8f533 100644 --- a/git-pack/src/data/output/count/objects.rs +++ b/git-pack/src/data/output/count/objects.rs @@ -5,7 +5,7 @@ use std::sync::{ use git_features::{parallel, progress::Progress}; use git_hash::{oid, ObjectId}; -use git_object::immutable; +use git_object::{commit, TagRefIter}; use crate::{data::output, find, FindExt}; @@ -179,7 +179,7 @@ where match obj.kind { Tree | Blob => break, Tag => { - id = immutable::TagIter::from_bytes(obj.data) + id = TagRefIter::from_bytes(obj.data) .target_id() .expect("every tag has a target"); obj = db.find_existing(id, buf1, cache)?; @@ -188,19 +188,21 @@ where } Commit => { let current_tree_iter = { - let mut commit_iter = immutable::CommitIter::from_bytes(obj.data); + let mut commit_iter = commit::CommitRefIter::from_bytes(obj.data); let tree_id = commit_iter.tree_id().expect("every commit has a tree"); parent_commit_ids.clear(); for token in commit_iter { match token { - Ok(immutable::commit::iter::Token::Parent { id }) => parent_commit_ids.push(id), + Ok(git_object::commit::ref_iter::Token::Parent { id }) => { + parent_commit_ids.push(id) + } Ok(_) => break, Err(err) => return Err(Error::CommitDecode(err)), } } let obj = db.find_existing(tree_id, buf1, cache)?; push_obj_count_unique(&mut out, seen_objs, &tree_id, &obj, progress, stats, true); - immutable::TreeIter::from_bytes(obj.data) + git_object::TreeRefIter::from_bytes(obj.data) }; let objects = if parent_commit_ids.is_empty() { @@ -238,7 +240,7 @@ where stats, true, ); - immutable::CommitIter::from_bytes(parent_commit_obj.data) + commit::CommitRefIter::from_bytes(parent_commit_obj.data) .tree_id() .expect("every commit has a tree") }; @@ -253,7 +255,7 @@ where stats, true, ); - immutable::TreeIter::from_bytes(parent_tree_obj.data) + git_object::TreeRefIter::from_bytes(parent_tree_obj.data) }; changes_delegate.clear(); @@ -289,7 +291,7 @@ where Tree => { traverse_delegate.clear(); git_traverse::tree::breadthfirst( - git_object::immutable::TreeIter::from_bytes(obj.data), + git_object::TreeRefIter::from_bytes(obj.data), &mut tree_traversal_state, |oid, buf| { stats.decoded_objects += 1; @@ -312,7 +314,7 @@ where break; } Commit => { - id = immutable::CommitIter::from_bytes(obj.data) + id = commit::CommitRefIter::from_bytes(obj.data) .tree_id() .expect("every commit has a tree"); stats.expanded_objects += 1; @@ -321,7 +323,7 @@ where } Blob => break, Tag => { - id = immutable::TagIter::from_bytes(obj.data) + id = TagRefIter::from_bytes(obj.data) .target_id() .expect("every tag has a target"); stats.expanded_objects += 1; @@ -397,7 +399,7 @@ mod tree { pub mod traverse { use git_hash::ObjectId; - use git_object::{bstr::BStr, immutable::tree::Entry}; + use git_object::{bstr::BStr, tree::EntryRef}; use git_traverse::tree::visit::{Action, Visit}; use crate::data::output::count::objects::util::InsertImmutable; @@ -434,7 +436,7 @@ mod tree { fn pop_path_component(&mut self) {} - fn visit_tree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_tree(&mut self, entry: &EntryRef<'_>) -> Action { let inserted = self.all_seen.insert(entry.oid.to_owned()); if inserted { Action::Continue @@ -443,7 +445,7 @@ mod tree { } } - fn visit_nontree(&mut self, entry: &Entry<'_>) -> Action { + fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action { let inserted = self.all_seen.insert(entry.oid.to_owned()); if inserted { self.non_trees.push(entry.oid.to_owned()); @@ -641,7 +643,7 @@ mod types { IterErr: std::error::Error + 'static, { #[error(transparent)] - CommitDecode(git_object::immutable::object::decode::Error), + CommitDecode(git_object::decode::Error), #[error(transparent)] FindExisting(#[from] FindErr), #[error(transparent)] diff --git a/git-pack/src/find.rs b/git-pack/src/find.rs index 68e91547a1c..a95c27b8e6d 100644 --- a/git-pack/src/find.rs +++ b/git-pack/src/find.rs @@ -49,7 +49,7 @@ pub trait Find { } mod ext { - use git_object::{immutable, Kind}; + use git_object::{commit, BlobRef, CommitRef, Kind, ObjectRef, TagRef, TreeRef, TreeRefIter}; use crate::{data, find}; @@ -123,37 +123,17 @@ mod ext { }) } - make_obj_lookup!( - find_existing_commit, - immutable::Object::Commit, - Kind::Commit, - immutable::Commit<'a> - ); - make_obj_lookup!( - find_existing_tree, - immutable::Object::Tree, - Kind::Tree, - immutable::Tree<'a> - ); - make_obj_lookup!(find_existing_tag, immutable::Object::Tag, Kind::Tag, immutable::Tag<'a>); - make_obj_lookup!( - find_existing_blob, - immutable::Object::Blob, - Kind::Blob, - immutable::Blob<'a> - ); + make_obj_lookup!(find_existing_commit, ObjectRef::Commit, Kind::Commit, CommitRef<'a>); + make_obj_lookup!(find_existing_tree, ObjectRef::Tree, Kind::Tree, TreeRef<'a>); + make_obj_lookup!(find_existing_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>); + make_obj_lookup!(find_existing_blob, ObjectRef::Blob, Kind::Blob, BlobRef<'a>); make_iter_lookup!( find_existing_commit_iter, Kind::Blob, - immutable::CommitIter<'a>, + commit::CommitRefIter<'a>, into_commit_iter ); - make_iter_lookup!( - find_existing_tree_iter, - Kind::Tree, - immutable::TreeIter<'a>, - into_tree_iter - ); + make_iter_lookup!(find_existing_tree_iter, Kind::Tree, TreeRefIter<'a>, into_tree_iter); } impl FindExt for T {} @@ -178,7 +158,6 @@ pub mod existing { /// pub mod existing_object { use git_hash::ObjectId; - use git_object::immutable; /// The error returned by the various [`find_existing_*`][crate::FindExt::find_existing_commit()] trait methods. #[derive(Debug, thiserror::Error)] @@ -187,7 +166,7 @@ pub mod existing_object { #[error(transparent)] Find(T), #[error(transparent)] - Decode(immutable::object::decode::Error), + Decode(git_object::decode::Error), #[error("An object with id {} could not be found", .oid)] NotFound { oid: ObjectId }, #[error("Expected object of kind {} something else", .expected)] diff --git a/git-pack/src/index/verify.rs b/git-pack/src/index/verify.rs index ad554eb8eaf..e9f0802882f 100644 --- a/git-pack/src/index/verify.rs +++ b/git-pack/src/index/verify.rs @@ -2,11 +2,7 @@ use std::sync::{atomic::AtomicBool, Arc}; use git_features::progress::{self, Progress}; use git_hash::SIZE_OF_SHA1_DIGEST as SHA1_SIZE; -use git_object::{ - bstr::{BString, ByteSlice}, - immutable::object, - mutable, -}; +use git_object::bstr::{BString, ByteSlice}; use crate::index; @@ -21,7 +17,7 @@ pub enum Error { }, #[error("{kind} object {id} could not be decoded")] ObjectDecode { - source: object::decode::Error, + source: git_object::decode::Error, kind: git_object::Kind, id: git_hash::ObjectId, }, @@ -181,15 +177,13 @@ impl index::File { match object_kind { Tree | Commit | Tag => { let borrowed_object = - git_object::immutable::Object::from_bytes(object_kind, buf).map_err(|err| { - Error::ObjectDecode { - source: err, - kind: object_kind, - id: index_entry.oid, - } + git_object::ObjectRef::from_bytes(object_kind, buf).map_err(|err| Error::ObjectDecode { + source: err, + kind: object_kind, + id: index_entry.oid, })?; if let Mode::Sha1Crc32DecodeEncode = mode { - let object = mutable::Object::from(borrowed_object); + let object = git_object::Object::from(borrowed_object); encode_buf.clear(); object.write_to(&mut *encode_buf)?; if encode_buf.as_slice() != buf { diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 0f1f7e081c3..43139a4607d 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -26,7 +26,7 @@ required-features = ["internal-testing-git-features-parallel"] [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["walkdir"]} git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } git-validate = { version = "^0.5.0", path = "../git-validate" } git-actor = { version ="^0.5.0", path = "../git-actor" } git-lock = { version ="^1.0.0", path = "../git-lock" } diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 63b4288f593..8b9f7163a38 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -71,7 +71,7 @@ pub enum Target { Symbolic(FullName), } -/// Denotes a ref target, equivalent to [`Kind`], but with signature_ref data. +/// Denotes a ref target, equivalent to [`Kind`], but with immutable data. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] pub enum TargetRef<'a> { /// A ref that points to an object id diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 9b40387a949..f50aebcb246 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -1,7 +1,9 @@ use bstr::ByteSlice; -use crate::store::file; -use crate::store::file::{log, log::iter::decode::LineNumber}; +use crate::store::{ + file, + file::{log, log::iter::decode::LineNumber}, +}; /// pub mod decode { diff --git a/git-ref/src/store/file/log/line.rs b/git-ref/src/store/file/log/line.rs index 4b693b6d968..3c7cd67b06e 100644 --- a/git-ref/src/store/file/log/line.rs +++ b/git-ref/src/store/file/log/line.rs @@ -1,7 +1,6 @@ use git_hash::ObjectId; -use crate::store::file::log::Line; -use crate::store::file::log::LineRef; +use crate::store::file::log::{Line, LineRef}; impl<'a> LineRef<'a> { /// Convert this instance into its mutable counterpart diff --git a/git-ref/src/store/file/log/mod.rs b/git-ref/src/store/file/log/mod.rs index 9d53a15fd23..c4630a72484 100644 --- a/git-ref/src/store/file/log/mod.rs +++ b/git-ref/src/store/file/log/mod.rs @@ -1,5 +1,4 @@ use bstr::BStr; - use git_hash::ObjectId; use git_object::bstr::BString; diff --git a/git-ref/src/store/file/loose/reference/peel.rs b/git-ref/src/store/file/loose/reference/peel.rs index 70b00f91b46..865ebd8af71 100644 --- a/git-ref/src/store/file/loose/reference/peel.rs +++ b/git-ref/src/store/file/loose/reference/peel.rs @@ -157,7 +157,7 @@ pub mod to_id { })?; match kind { git_object::Kind::Tag => { - oid = git_object::immutable::TagIter::from_bytes(data) + oid = git_object::TagRefIter::from_bytes(data) .target_id() .ok_or_else(|| Error::NotFound { oid, diff --git a/git-ref/src/store/packed/transaction.rs b/git-ref/src/store/packed/transaction.rs index ad214e44d08..0d2454bb7af 100644 --- a/git-ref/src/store/packed/transaction.rs +++ b/git-ref/src/store/packed/transaction.rs @@ -77,13 +77,11 @@ impl packed::Transaction { let kind = find(next_id, &mut buf)?; match kind { Some(kind) if kind == git_object::Kind::Tag => { - next_id = git_object::immutable::TagIter::from_bytes(&buf) - .target_id() - .ok_or_else(|| { - prepare::Error::Resolve( - format!("Couldn't get target object id from tag {}", next_id).into(), - ) - })?; + next_id = git_object::TagRefIter::from_bytes(&buf).target_id().ok_or_else(|| { + prepare::Error::Resolve( + format!("Couldn't get target object id from tag {}", next_id).into(), + ) + })?; } Some(_) => { break if next_id == new { None } else { Some(next_id) }; diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index c24dce50936..34b9f5bf705 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,6 +1,7 @@ +use std::fmt; + use bstr::{BStr, ByteSlice}; use git_hash::{oid, ObjectId}; -use std::fmt; use crate::{FullName, Kind, Target, TargetRef}; diff --git a/git-ref/tests/file/transaction/mod.rs b/git-ref/tests/file/transaction/mod.rs index 29b01c83249..b5c52b83d58 100644 --- a/git-ref/tests/file/transaction/mod.rs +++ b/git-ref/tests/file/transaction/mod.rs @@ -1,6 +1,5 @@ mod prepare_and_commit { use bstr::BString; - use git_actor::{Sign, Time}; use git_hash::ObjectId; use git_ref::file; diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index b80b91c7c53..473f1102498 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -41,7 +41,7 @@ git-validate = { version = "^0.5.0", path = "../git-validate" } git-odb = { version ="0.20.0", path = "../git-odb" } git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } git-actor = { version ="^0.5.0", path = "../git-actor" } git-pack = { version ="^0.9.0", path = "../git-pack" } diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 8fe7804d26a..8c090f99606 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -1,12 +1,12 @@ use std::ops::DerefMut; use git_hash::ObjectId; +use git_odb::{Find, FindExt}; use crate::{ easy, easy::{object, ObjectRef}, }; -use git_odb::{Find, FindExt}; pub fn find_object( access: &A, diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 745e3ed2986..203487b5997 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,12 +1,7 @@ use std::convert::TryInto; -use git_hash::ObjectId; - -use crate::{ - easy, - easy::{reference, Reference}, -}; use git_actor as actor; +use git_hash::ObjectId; use git_lock as lock; use git_ref::{ file::find::Error, @@ -14,6 +9,11 @@ use git_ref::{ PartialNameRef, Target, }; +use crate::{ + easy, + easy::{reference, Reference}, +}; + /// Obtain and alter references comfortably pub trait ReferenceAccessExt: easy::Access + Sized { fn tag( diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 4c1af267f10..3e6cc25af2c 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -18,12 +18,13 @@ use std::{ time::SystemTime, }; -use crate::Repository; use git_hash::ObjectId; use git_object as objs; use git_odb as odb; use git_ref as refs; +use crate::Repository; + mod impls; pub(crate) mod ext; @@ -112,7 +113,7 @@ pub struct State { /// A utility trait to represent access to a repository. /// -/// It provides signature_ref and possibly mutable access. Both types of access are validated at runtime, which may fail +/// It provides immutable and possibly mutable access. Both types of access are validated at runtime, which may fail /// or may block, depending on the implementation. /// /// Furthermore it provides access to additional state for use with the [`Repository`]. It is designed for thread-local diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index 6dd04a646a2..97f9c6496a2 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -1,15 +1,15 @@ #![allow(missing_docs)] use std::{cell::Ref, convert::TryInto}; +use git_hash::ObjectId; pub use git_object::Kind; +use git_object::{commit, TagRefIter}; +use git_odb as odb; use crate::{ easy, easy::{Object, ObjectRef, TreeRef}, }; -use git_hash::ObjectId; -use git_object::immutable; -use git_odb as odb; mod impls; mod tree; @@ -59,10 +59,10 @@ where } pub mod find { + use git_odb as odb; use quick_error::quick_error; use crate::easy; - use git_odb as odb; quick_error! { #[derive(Debug)] @@ -86,10 +86,10 @@ pub mod find { pub(crate) type OdbError = odb::compound::find::Error; pub mod existing { + use git_odb as odb; use quick_error::quick_error; use crate::easy; - use git_odb as odb; quick_error! { #[derive(Debug)] @@ -141,11 +141,11 @@ impl<'repo, A> ObjectRef<'repo, A> where A: easy::Access + Sized, { - pub fn to_commit_iter(&self) -> Option> { + pub fn to_commit_iter(&self) -> Option> { odb::data::Object::new(self.kind, &self.data).into_commit_iter() } - pub fn to_tag_iter(&self) -> Option> { + pub fn to_tag_iter(&self) -> Option> { odb::data::Object::new(self.kind, &self.data).into_tag_iter() } } diff --git a/git-repository/src/easy/object/tree.rs b/git-repository/src/easy/object/tree.rs index 84ddbb3aef0..cc51a605b56 100644 --- a/git-repository/src/easy/object/tree.rs +++ b/git-repository/src/easy/object/tree.rs @@ -1,16 +1,16 @@ +use git_object::{bstr::BStr, TreeRefIter}; + use crate::{ easy, easy::{object::find, TreeRef}, }; -use git_object as objs; -use git_object::{bstr::BStr, immutable}; impl<'repo, A> TreeRef<'repo, A> where A: easy::Access + Sized, { // TODO: move implementation to git-object, tests. - pub fn lookup_path(mut self, path: I) -> Result, find::existing::Error> + pub fn lookup_path(mut self, path: I) -> Result, find::existing::Error> where I: IntoIterator, P: PartialEq, @@ -18,7 +18,7 @@ where // let mut out = None; let mut path = path.into_iter().peekable(); while let Some(component) = path.next() { - match immutable::tree::TreeIter::from_bytes(&self.data) + match TreeRefIter::from_bytes(&self.data) .filter_map(Result::ok) .find(|entry| component.eq(entry.filename)) { diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 76b985639ea..1dbedaf5450 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -1,8 +1,9 @@ +use git_hash::{oid, ObjectId}; + use crate::{ easy, easy::{object::find, Object, ObjectRef, Oid}, }; -use git_hash::{oid, ObjectId}; impl<'repo, A, B> PartialEq> for Oid<'repo, B> { fn eq(&self, other: &Oid<'repo, A>) -> bool { diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 0dcd1b89ef8..0f24baea7dc 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -1,13 +1,14 @@ #![allow(missing_docs)] use std::ops::DerefMut; +use git_hash::ObjectId; +use git_odb::Find; +use git_ref as refs; + use crate::{ easy, easy::{Oid, Reference}, }; -use git_hash::ObjectId; -use git_odb::Find; -use git_ref as refs; pub(crate) enum Backing { OwnedPacked { @@ -23,10 +24,10 @@ pub(crate) enum Backing { } pub mod edit { + use git_ref as refs; use quick_error::quick_error; use crate::easy; - use git_ref as refs; quick_error! { #[derive(Debug)] @@ -55,10 +56,10 @@ pub mod edit { } pub mod peel_to_oid_in_place { + use git_ref as refs; use quick_error::quick_error; use crate::easy; - use git_ref as refs; quick_error! { #[derive(Debug)] @@ -163,10 +164,10 @@ where } pub mod find { + use git_ref as refs; use quick_error::quick_error; use crate::easy; - use git_ref as refs; pub mod existing { use quick_error::quick_error; diff --git a/git-repository/src/easy/state.rs b/git-repository/src/easy/state.rs index 11418385c7e..353045686d5 100644 --- a/git-repository/src/easy/state.rs +++ b/git-repository/src/easy/state.rs @@ -1,11 +1,12 @@ #![allow(missing_docs)] use std::cell::{Ref, RefMut}; +use git_ref::{file, packed}; + use crate::{ easy, easy::{borrow, PackCache}, }; -use git_ref::{file, packed}; impl Clone for easy::State { fn clone(&self) -> Self { diff --git a/git-repository/src/ext/object_id.rs b/git-repository/src/ext/object_id.rs index 6e0985de318..7f9d6cdef15 100644 --- a/git-repository/src/ext/object_id.rs +++ b/git-repository/src/ext/object_id.rs @@ -1,7 +1,6 @@ #![allow(missing_docs)] use git_hash::ObjectId; -#[cfg(feature = "git-traverse")] -use git_object::immutable; +use git_object::commit; #[cfg(feature = "git-traverse")] use git_traverse::commit::ancestors::{Ancestors, State}; @@ -13,7 +12,7 @@ pub trait ObjectIdExt: Sealed { #[cfg(feature = "git-traverse")] fn ancestors_iter(self, find: Find) -> Ancestors bool, State> where - Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>; + Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>; fn attach(self, access: &A) -> easy::Oid<'_, A>; } @@ -24,7 +23,7 @@ impl ObjectIdExt for ObjectId { #[cfg(feature = "git-traverse")] fn ancestors_iter(self, find: Find) -> Ancestors bool, State> where - Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>, { Ancestors::new(Some(self), State::default(), find) } diff --git a/git-repository/src/ext/tree.rs b/git-repository/src/ext/tree.rs index 3d989ce2232..0615201ce59 100644 --- a/git-repository/src/ext/tree.rs +++ b/git-repository/src/ext/tree.rs @@ -4,7 +4,7 @@ use std::borrow::BorrowMut; #[cfg(feature = "git-diff")] use git_hash::oid; -use git_object::immutable; +use git_object::TreeRefIter; #[cfg(feature = "git-traverse")] use git_traverse::tree::breadthfirst; @@ -14,13 +14,13 @@ pub trait TreeIterExt: Sealed { #[cfg(feature = "git-diff")] fn changes_needed( &self, - other: immutable::TreeIter<'_>, + other: TreeRefIter<'_>, state: StateMut, find: FindFn, delegate: &mut R, ) -> Result<(), git_diff::tree::changes::Error> where - FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, + FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, R: git_diff::tree::Visit, StateMut: BorrowMut; @@ -33,24 +33,24 @@ pub trait TreeIterExt: Sealed { delegate: &mut V, ) -> Result<(), breadthfirst::Error> where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, StateMut: BorrowMut, V: git_traverse::tree::Visit; } -impl<'d> Sealed for immutable::TreeIter<'d> {} +impl<'d> Sealed for TreeRefIter<'d> {} -impl<'d> TreeIterExt for immutable::TreeIter<'d> { +impl<'d> TreeIterExt for TreeRefIter<'d> { #[cfg(feature = "git-diff")] fn changes_needed( &self, - other: immutable::TreeIter<'_>, + other: TreeRefIter<'_>, state: StateMut, find: FindFn, delegate: &mut R, ) -> Result<(), git_diff::tree::changes::Error> where - FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, + FindFn: for<'b> FnMut(&oid, &'b mut Vec) -> Option>, R: git_diff::tree::Visit, StateMut: BorrowMut, { @@ -65,7 +65,7 @@ impl<'d> TreeIterExt for immutable::TreeIter<'d> { delegate: &mut V, ) -> Result<(), breadthfirst::Error> where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, StateMut: BorrowMut, V: git_traverse::tree::Visit, { diff --git a/git-traverse/Cargo.toml b/git-traverse/Cargo.toml index 03f45027e56..b14b15590da 100644 --- a/git-traverse/Cargo.toml +++ b/git-traverse/Cargo.toml @@ -15,7 +15,7 @@ doctest = false [dependencies] git-hash = { version = "^0.5.0", path = "../git-hash" } -git-object = { version ="0.12.0", path = "../git-object" } +git-object = { version ="^0.13.0", path = "../git-object" } quick-error = "2.0.0" [dev-dependencies] diff --git a/git-traverse/src/commit.rs b/git-traverse/src/commit.rs index 191625dc2c1..89c3be058ef 100644 --- a/git-traverse/src/commit.rs +++ b/git-traverse/src/commit.rs @@ -6,7 +6,7 @@ pub mod ancestors { }; use git_hash::{oid, ObjectId}; - use git_object::immutable; + use git_object::commit; use quick_error::quick_error; quick_error! { @@ -17,7 +17,7 @@ pub mod ancestors { NotFound{oid: ObjectId} { display("The commit {} could not be found", oid) } - ObjectDecode(err: immutable::object::decode::Error) { + ObjectDecode(err: git_object::decode::Error) { display("An object could not be decoded") source(err) from() @@ -50,7 +50,7 @@ pub mod ancestors { impl Ancestors bool, StateMut> where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, StateMut: BorrowMut, { /// Create a new instance. @@ -72,7 +72,7 @@ pub mod ancestors { impl Ancestors where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, Predicate: FnMut(&oid) -> bool, StateMut: BorrowMut, { @@ -114,7 +114,7 @@ pub mod ancestors { impl Iterator for Ancestors where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, Predicate: FnMut(&oid) -> bool, StateMut: BorrowMut, { @@ -131,7 +131,7 @@ pub mod ancestors { } for token in commit_iter { match token { - Ok(immutable::commit::iter::Token::Parent { id }) => { + Ok(git_object::commit::ref_iter::Token::Parent { id }) => { let was_inserted = state.seen.insert(id); if was_inserted && (self.predicate)(&id) { state.next.push_back(id); diff --git a/git-traverse/src/tree/breadthfirst.rs b/git-traverse/src/tree/breadthfirst.rs index e51fc0310c5..1a1161910fe 100644 --- a/git-traverse/src/tree/breadthfirst.rs +++ b/git-traverse/src/tree/breadthfirst.rs @@ -1,7 +1,7 @@ use std::{borrow::BorrowMut, collections::VecDeque}; use git_hash::{oid, ObjectId}; -use git_object::{immutable, tree}; +use git_object::{tree, TreeRefIter}; use quick_error::quick_error; use crate::tree::visit::Visit; @@ -17,7 +17,7 @@ quick_error! { Cancelled { display("The delegate cancelled the operation") } - ObjectDecode(err: immutable::object::decode::Error) { + ObjectDecode(err: git_object::decode::Error) { display("An object could not be decoded") source(err) from() @@ -52,13 +52,13 @@ impl State { /// be escalated into a more specific error if its encountered by the caller. /// * `delegate` - A way to observe entries and control the iteration while allowing the optimizer to let you pay only for what you use. pub fn traverse( - root: immutable::TreeIter<'_>, + root: TreeRefIter<'_>, mut state: StateMut, mut find: Find, delegate: &mut V, ) -> Result<(), Error> where - Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, + Find: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, StateMut: BorrowMut, V: Visit, { diff --git a/git-traverse/src/tree/recorder.rs b/git-traverse/src/tree/recorder.rs index 1273c4767d3..e3fed20ee03 100644 --- a/git-traverse/src/tree/recorder.rs +++ b/git-traverse/src/tree/recorder.rs @@ -3,13 +3,13 @@ use std::collections::VecDeque; use git_hash::ObjectId; use git_object::{ bstr::{BStr, BString, ByteSlice, ByteVec}, - immutable, tree, + tree, }; use crate::tree::{visit, visit::Action}; /// An owned entry as observed by a call to [`visit_(tree|nontree)(…)`][visit::Visit::visit_tree()], enhanced with the full path to it. -/// Otherwise similar to [`signature_ref::tree::Entry`][git_object::immutable::tree::Entry]. +/// Otherwise similar to [`git_object::tree::EntryRef`]. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { /// The kind of entry, similar to entries in a unix directory tree. @@ -23,7 +23,7 @@ pub struct Entry { } impl Entry { - fn new(entry: &immutable::tree::Entry<'_>, filepath: BString) -> Self { + fn new(entry: &tree::EntryRef<'_>, filepath: BString) -> Self { Entry { filepath, oid: entry.oid.to_owned(), @@ -83,12 +83,12 @@ impl visit::Visit for Recorder { self.pop_element(); } - fn visit_tree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action { + fn visit_tree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } - fn visit_nontree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action { + fn visit_nontree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } diff --git a/git-traverse/src/tree/visit.rs b/git-traverse/src/tree/visit.rs index c64bf708419..2476acb1e0a 100644 --- a/git-traverse/src/tree/visit.rs +++ b/git-traverse/src/tree/visit.rs @@ -1,4 +1,4 @@ -use git_object::{bstr::BStr, immutable}; +use git_object::{bstr::BStr, tree}; /// What to do after an entry was [recorded][Visit::visit_tree()]. #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] @@ -34,11 +34,11 @@ pub trait Visit { /// [`Action::Skip`] can be used to prevent traversing it, for example if it's known to the caller already. /// /// The implementation may use the current path to learn where in the tree the change is located. - fn visit_tree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action; + fn visit_tree(&mut self, entry: &tree::EntryRef<'_>) -> Action; /// Observe a tree entry that is NO tree and return an instruction whether to continue or not. /// [`Action::Skip`] has no effect here. /// /// The implementation may use the current path to learn where in the tree the change is located. - fn visit_nontree(&mut self, entry: &immutable::tree::Entry<'_>) -> Action; + fn visit_nontree(&mut self, entry: &tree::EntryRef<'_>) -> Action; } diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index 4149843a7db..483b280a8ee 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -68,7 +68,7 @@ where progress.inc(); repo.odb.find_existing(oid, buf, &mut pack_cache).ok().map(|o| { commits.push(o.data.to_owned()); - objs::immutable::CommitIter::from_bytes(o.data) + objs::commit::CommitRefIter::from_bytes(o.data) }) }), || anyhow!("Cancelled by user"), @@ -84,7 +84,7 @@ where let mut all_commits: Vec = all_commits .into_par_iter() .map(|commit_data: Vec| { - objs::immutable::CommitIter::from_bytes(&commit_data) + objs::commit::CommitRefIter::from_bytes(&commit_data) .signatures() .next() .map(|author| actor::Signature::from(author))