Skip to content

Commit

Permalink
Merge branch 'improvements'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Oct 12, 2023
2 parents 0f3a4b0 + f478a37 commit 429e7b2
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 47 deletions.
2 changes: 2 additions & 0 deletions crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
* [x] utilities for applications to make long running operations interruptible gracefully and to support timeouts in servers.
* [x] handle `core.repositoryFormatVersion` and extensions
* [x] support for unicode-precomposition of command-line arguments (needs explicit use in parent application)
* [ ] strict object creation (validate objects referenced by newly created objects exist)
* [ ] strict hash verification (validate that objects actually have the hashes they claim to have)
* **Repository**
* [x] discovery
* [x] option to not cross file systems (default)
Expand Down
2 changes: 2 additions & 0 deletions gix-hash/src/object_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ impl ObjectId {

/// Returns an instances whose bytes are all zero.
#[inline]
#[doc(alias = "zero", alias = "git2")]
pub const fn null(kind: Kind) -> ObjectId {
match kind {
Kind::Sha1 => Self::null_sha1(),
Expand All @@ -131,6 +132,7 @@ impl ObjectId {

/// Returns `true` if this hash consists of all null bytes.
#[inline]
#[doc(alias = "is_zero", alias = "git2")]
pub fn is_null(&self) -> bool {
match self {
ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
Expand Down
2 changes: 1 addition & 1 deletion gix-object/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-act
verbose-object-parsing-errors = []

[dependencies]
gix-features = { version = "^0.35.0", path = "../gix-features", features = ["rustsha1"] }
gix-features = { version = "^0.35.0", path = "../gix-features", features = ["rustsha1", "progress"] }
gix-hash = { version = "^0.13.0", path = "../gix-hash" }
gix-validate = { version = "^0.8.0", path = "../gix-validate" }
gix-actor = { version = "^0.27.0", path = "../gix-actor" }
Expand Down
23 changes: 22 additions & 1 deletion gix-object/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,8 @@ pub mod decode {
}
}

/// A standalone function to compute a hash of kind `hash_kind` for an object of `object_kind` and its `data`.
/// A function to compute a hash of kind `hash_kind` for an object of `object_kind` and its `data`.
#[doc(alias = "hash_object", alias = "git2")]
pub fn compute_hash(hash_kind: gix_hash::Kind, object_kind: Kind, data: &[u8]) -> gix_hash::ObjectId {
let header = encode::loose_header(object_kind, data.len() as u64);

Expand All @@ -374,3 +375,23 @@ pub fn compute_hash(hash_kind: gix_hash::Kind, object_kind: Kind, data: &[u8]) -

hasher.digest().into()
}

/// A function to compute a hash of kind `hash_kind` for an object of `object_kind` and its data read from `stream`
/// which has to yield exactly `stream_len` bytes.
/// Use `progress` to learn about progress in bytes processed and `should_interrupt` to be able to abort the operation
/// if set to `true`.
#[doc(alias = "hash_file", alias = "git2")]
pub fn compute_stream_hash(
hash_kind: gix_hash::Kind,
object_kind: Kind,
stream: &mut dyn std::io::Read,
stream_len: u64,
progress: &mut dyn gix_features::progress::Progress,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> std::io::Result<gix_hash::ObjectId> {
let header = encode::loose_header(object_kind, stream_len);
let mut hasher = gix_features::hash::hasher(hash_kind);

hasher.update(&header);
gix_features::hash::bytes_with_hasher(stream, stream_len, hasher, progress, should_interrupt)
}
30 changes: 30 additions & 0 deletions gix-object/tests/object.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;

use gix_hash::ObjectId;

Expand All @@ -21,6 +22,35 @@ fn compute_hash() {
);
}

#[test]
fn compute_stream_hash() {
let hk = gix_hash::Kind::Sha1;
assert_eq!(
gix_object::compute_stream_hash(
hk,
gix_object::Kind::Blob,
&mut &[][..],
0,
&mut gix_features::progress::Discard,
&AtomicBool::default()
)
.expect("in-memory works"),
gix_hash::ObjectId::empty_blob(hk)
);
assert_eq!(
gix_object::compute_stream_hash(
hk,
gix_object::Kind::Tree,
&mut &[][..],
0,
&mut gix_features::progress::Discard,
&AtomicBool::default()
)
.expect("in-memory works"),
gix_hash::ObjectId::empty_tree(hk)
);
}

use gix_testtools::Result;

#[cfg(not(windows))]
Expand Down
20 changes: 8 additions & 12 deletions gix-status/src/index_as_worktree/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,14 @@ impl CompareBlobs for HashEq {
stream.read_to_end(buf)?;
gix_object::compute_hash(entry.id.kind(), gix_object::Kind::Blob, buf)
}
Some(len) => {
let header = gix_object::encode::loose_header(gix_object::Kind::Blob, len);
let mut hasher = gix_features::hash::hasher(entry.id.kind());
hasher.update(&header);
gix_features::hash::bytes_with_hasher(
&mut stream,
len,
hasher,
&mut gix_features::progress::Discard,
&AtomicBool::default(),
)?
}
Some(len) => gix_object::compute_stream_hash(
entry.id.kind(),
gix_object::Kind::Blob,
&mut stream,
len,
&mut gix_features::progress::Discard,
&AtomicBool::default(),
)?,
};
Ok((entry.id != file_hash).then_some(file_hash))
}
Expand Down
4 changes: 4 additions & 0 deletions gix/src/head/peel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ impl<'repo> Head<'repo> {
}

// TODO: tests
// TODO: Fix this! It's not consistently peeling tags. The whole peeling business should be reconsidered to do what people usually
// want which is to peel references, if present, and then peel objects with control over which object type to end at.
// Finding a good interface for that isn't easy as ideally, it's an iterator that shows the intermediate objects so the user
// can select which tag of a chain to choose.
/// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no
/// more object to follow, and return that object id.
///
Expand Down
8 changes: 7 additions & 1 deletion gix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
//! * [`git2::build::CheckoutBuilder::disable_filters()](https://docs.rs/git2/*/git2/build/struct.CheckoutBuilder.html#method.disable_filters) ➡ ❌ *(filters are always applied during checkouts)*
//! * [`git2::Repository::submodule_status()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.submodule_status) ➡ [`Submodule::state()`] - status provides more information and conveniences though, and an actual worktree status isn't performed.
//!
//! #### Integrity checks
//!
//! `git2` by default performs integrity checks via [`strict_hash_verification()`](https://docs.rs/git2/latest/git2/opts/fn.strict_hash_verification.html) and
//! [`strict_object_creation`](https://docs.rs/git2/latest/git2/opts/fn.strict_object_creation.html) which `gitoxide` *currently* **does not have**.
//!
//! ### Feature Flags
#![cfg_attr(
feature = "document-features",
Expand Down Expand Up @@ -158,7 +163,8 @@ mod types;
#[cfg(any(feature = "excludes", feature = "attributes"))]
pub use types::AttributeStack;
pub use types::{
Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree, Worktree,
Blob, Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree,
Worktree,
};
#[cfg(feature = "attributes")]
pub use types::{Pathspec, PathspecDetached, Submodule};
Expand Down
30 changes: 23 additions & 7 deletions gix/src/object/impls.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::convert::TryFrom;

use crate::{object, Commit, Object, ObjectDetached, Tag, Tree};
use crate::{object, Blob, Commit, Object, ObjectDetached, Tag, Tree};

impl<'repo> From<Object<'repo>> for ObjectDetached {
fn from(mut v: Object<'repo>) -> Self {
Expand Down Expand Up @@ -59,11 +59,11 @@ impl<'repo> TryFrom<Object<'repo>> for Commit<'repo> {
type Error = Object<'repo>;

fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
let handle = value.repo;
let repo = value.repo;
match value.kind {
object::Kind::Commit => Ok(Commit {
id: value.id,
repo: handle,
repo,
data: steal_from_freelist(&mut value.data),
}),
_ => Err(value),
Expand All @@ -75,11 +75,11 @@ impl<'repo> TryFrom<Object<'repo>> for Tag<'repo> {
type Error = Object<'repo>;

fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
let handle = value.repo;
let repo = value.repo;
match value.kind {
object::Kind::Tag => Ok(Tag {
id: value.id,
repo: handle,
repo,
data: steal_from_freelist(&mut value.data),
}),
_ => Err(value),
Expand All @@ -91,11 +91,27 @@ impl<'repo> TryFrom<Object<'repo>> for Tree<'repo> {
type Error = Object<'repo>;

fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
let handle = value.repo;
let repo = value.repo;
match value.kind {
object::Kind::Tree => Ok(Tree {
id: value.id,
repo: handle,
repo,
data: steal_from_freelist(&mut value.data),
}),
_ => Err(value),
}
}
}

impl<'repo> TryFrom<Object<'repo>> for Blob<'repo> {
type Error = Object<'repo>;

fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
let repo = value.repo;
match value.kind {
object::Kind::Blob => Ok(Blob {
id: value.id,
repo,
data: steal_from_freelist(&mut value.data),
}),
_ => Err(value),
Expand Down
19 changes: 18 additions & 1 deletion gix/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::convert::TryInto;
use gix_hash::ObjectId;
pub use gix_object::Kind;

use crate::{Commit, Id, Object, ObjectDetached, Tag, Tree};
use crate::{Blob, Commit, Id, Object, ObjectDetached, Tag, Tree};

mod errors;
pub(crate) mod cache {
Expand Down Expand Up @@ -74,6 +74,14 @@ impl<'repo> Object<'repo> {
}
}

/// Transform this object into a blob, or panic if it is none.
pub fn into_blob(self) -> Blob<'repo> {
match self.try_into() {
Ok(tree) => tree,
Err(this) => panic!("Tried to use {} as tree, but was {}", this.id, this.kind),
}
}

/// Transform this object into a tree, or panic if it is none.
pub fn into_tree(self) -> Tree<'repo> {
match self.try_into() {
Expand Down Expand Up @@ -124,6 +132,15 @@ impl<'repo> Object<'repo> {
expected: gix_object::Kind::Tree,
})
}

/// Transform this object into a blob, or return it as part of the `Err` if it is no blob.
pub fn try_into_blob(self) -> Result<Blob<'repo>, try_into::Error> {
self.try_into().map_err(|this: Self| try_into::Error {
id: this.id,
actual: this.kind,
expected: gix_object::Kind::Blob,
})
}
}

impl<'repo> Object<'repo> {
Expand Down
23 changes: 19 additions & 4 deletions gix/src/reference/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub mod edit {

///
pub mod peel {
/// The error returned by [`Reference::peel_to_id_in_place(…)`][crate::Reference::peel_to_id_in_place()] and
/// [`Reference::into_fully_peeled_id(…)`][crate::Reference::into_fully_peeled_id()].
/// The error returned by [`Reference::peel_to_id_in_place(…)`](crate::Reference::peel_to_id_in_place()) and
/// [`Reference::into_fully_peeled_id(…)`](crate::Reference::into_fully_peeled_id()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
Expand All @@ -36,7 +36,7 @@ pub mod peel {

///
pub mod head_id {
/// The error returned by [`Repository::head_id(…)`][crate::Repository::head_id()].
/// The error returned by [`Repository::head_id(…)`](crate::Repository::head_id()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
Expand All @@ -51,7 +51,7 @@ pub mod head_id {

///
pub mod head_commit {
/// The error returned by [`Repository::head_commit`(…)][crate::Repository::head_commit()].
/// The error returned by [`Repository::head_commit`(…)](crate::Repository::head_commit()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
Expand All @@ -62,6 +62,21 @@ pub mod head_commit {
}
}

///
pub mod head_tree_id {
/// The error returned by [`Repository::head_tree_id`(…)](crate::Repository::head_tree_id()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
Head(#[from] crate::reference::find::existing::Error),
#[error(transparent)]
PeelToCommit(#[from] crate::head::peel::to_commit::Error),
#[error(transparent)]
DecodeCommit(#[from] gix_object::decode::Error),
}
}

///
pub mod find {
///
Expand Down
2 changes: 1 addition & 1 deletion gix/src/reference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub mod iter;
pub mod remote;

mod errors;
pub use errors::{edit, find, head_commit, head_id, peel};
pub use errors::{edit, find, head_commit, head_id, head_tree_id, peel};

use crate::ext::ObjectIdExt;

Expand Down
1 change: 1 addition & 0 deletions gix/src/repository/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl crate::Repository {
}

/// Return the work tree containing all checked out files, if there is one.
#[doc(alias = "workdir", alias = "git2")]
pub fn work_dir(&self) -> Option<&std::path::Path> {
self.work_tree.as_deref()
}
Expand Down

0 comments on commit 429e7b2

Please sign in to comment.