Skip to content

Commit 429e7b2

Browse files
committed
Merge branch 'improvements'
2 parents 0f3a4b0 + f478a37 commit 429e7b2

File tree

18 files changed

+221
-47
lines changed

18 files changed

+221
-47
lines changed

crate-status.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,8 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
615615
* [x] utilities for applications to make long running operations interruptible gracefully and to support timeouts in servers.
616616
* [x] handle `core.repositoryFormatVersion` and extensions
617617
* [x] support for unicode-precomposition of command-line arguments (needs explicit use in parent application)
618+
* [ ] strict object creation (validate objects referenced by newly created objects exist)
619+
* [ ] strict hash verification (validate that objects actually have the hashes they claim to have)
618620
* **Repository**
619621
* [x] discovery
620622
* [x] option to not cross file systems (default)

gix-hash/src/object_id.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ impl ObjectId {
123123

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

132133
/// Returns `true` if this hash consists of all null bytes.
133134
#[inline]
135+
#[doc(alias = "is_zero", alias = "git2")]
134136
pub fn is_null(&self) -> bool {
135137
match self {
136138
ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),

gix-object/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-act
2828
verbose-object-parsing-errors = []
2929

3030
[dependencies]
31-
gix-features = { version = "^0.35.0", path = "../gix-features", features = ["rustsha1"] }
31+
gix-features = { version = "^0.35.0", path = "../gix-features", features = ["rustsha1", "progress"] }
3232
gix-hash = { version = "^0.13.0", path = "../gix-hash" }
3333
gix-validate = { version = "^0.8.0", path = "../gix-validate" }
3434
gix-actor = { version = "^0.27.0", path = "../gix-actor" }

gix-object/src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ pub mod decode {
364364
}
365365
}
366366

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

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

375376
hasher.digest().into()
376377
}
378+
379+
/// A function to compute a hash of kind `hash_kind` for an object of `object_kind` and its data read from `stream`
380+
/// which has to yield exactly `stream_len` bytes.
381+
/// Use `progress` to learn about progress in bytes processed and `should_interrupt` to be able to abort the operation
382+
/// if set to `true`.
383+
#[doc(alias = "hash_file", alias = "git2")]
384+
pub fn compute_stream_hash(
385+
hash_kind: gix_hash::Kind,
386+
object_kind: Kind,
387+
stream: &mut dyn std::io::Read,
388+
stream_len: u64,
389+
progress: &mut dyn gix_features::progress::Progress,
390+
should_interrupt: &std::sync::atomic::AtomicBool,
391+
) -> std::io::Result<gix_hash::ObjectId> {
392+
let header = encode::loose_header(object_kind, stream_len);
393+
let mut hasher = gix_features::hash::hasher(hash_kind);
394+
395+
hasher.update(&header);
396+
gix_features::hash::bytes_with_hasher(stream, stream_len, hasher, progress, should_interrupt)
397+
}

gix-object/tests/object.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::path::PathBuf;
2+
use std::sync::atomic::AtomicBool;
23

34
use gix_hash::ObjectId;
45

@@ -21,6 +22,35 @@ fn compute_hash() {
2122
);
2223
}
2324

25+
#[test]
26+
fn compute_stream_hash() {
27+
let hk = gix_hash::Kind::Sha1;
28+
assert_eq!(
29+
gix_object::compute_stream_hash(
30+
hk,
31+
gix_object::Kind::Blob,
32+
&mut &[][..],
33+
0,
34+
&mut gix_features::progress::Discard,
35+
&AtomicBool::default()
36+
)
37+
.expect("in-memory works"),
38+
gix_hash::ObjectId::empty_blob(hk)
39+
);
40+
assert_eq!(
41+
gix_object::compute_stream_hash(
42+
hk,
43+
gix_object::Kind::Tree,
44+
&mut &[][..],
45+
0,
46+
&mut gix_features::progress::Discard,
47+
&AtomicBool::default()
48+
)
49+
.expect("in-memory works"),
50+
gix_hash::ObjectId::empty_tree(hk)
51+
);
52+
}
53+
2454
use gix_testtools::Result;
2555

2656
#[cfg(not(windows))]

gix-status/src/index_as_worktree/traits.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,14 @@ impl CompareBlobs for HashEq {
148148
stream.read_to_end(buf)?;
149149
gix_object::compute_hash(entry.id.kind(), gix_object::Kind::Blob, buf)
150150
}
151-
Some(len) => {
152-
let header = gix_object::encode::loose_header(gix_object::Kind::Blob, len);
153-
let mut hasher = gix_features::hash::hasher(entry.id.kind());
154-
hasher.update(&header);
155-
gix_features::hash::bytes_with_hasher(
156-
&mut stream,
157-
len,
158-
hasher,
159-
&mut gix_features::progress::Discard,
160-
&AtomicBool::default(),
161-
)?
162-
}
151+
Some(len) => gix_object::compute_stream_hash(
152+
entry.id.kind(),
153+
gix_object::Kind::Blob,
154+
&mut stream,
155+
len,
156+
&mut gix_features::progress::Discard,
157+
&AtomicBool::default(),
158+
)?,
163159
};
164160
Ok((entry.id != file_hash).then_some(file_hash))
165161
}

gix/src/head/peel.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ impl<'repo> Head<'repo> {
4747
}
4848

4949
// TODO: tests
50+
// TODO: Fix this! It's not consistently peeling tags. The whole peeling business should be reconsidered to do what people usually
51+
// want which is to peel references, if present, and then peel objects with control over which object type to end at.
52+
// Finding a good interface for that isn't easy as ideally, it's an iterator that shows the intermediate objects so the user
53+
// can select which tag of a chain to choose.
5054
/// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no
5155
/// more object to follow, and return that object id.
5256
///

gix/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@
7575
//! * [`git2::build::CheckoutBuilder::disable_filters()](https://docs.rs/git2/*/git2/build/struct.CheckoutBuilder.html#method.disable_filters) ➡ ❌ *(filters are always applied during checkouts)*
7676
//! * [`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.
7777
//!
78+
//! #### Integrity checks
79+
//!
80+
//! `git2` by default performs integrity checks via [`strict_hash_verification()`](https://docs.rs/git2/latest/git2/opts/fn.strict_hash_verification.html) and
81+
//! [`strict_object_creation`](https://docs.rs/git2/latest/git2/opts/fn.strict_object_creation.html) which `gitoxide` *currently* **does not have**.
82+
//!
7883
//! ### Feature Flags
7984
#![cfg_attr(
8085
feature = "document-features",
@@ -158,7 +163,8 @@ mod types;
158163
#[cfg(any(feature = "excludes", feature = "attributes"))]
159164
pub use types::AttributeStack;
160165
pub use types::{
161-
Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree, Worktree,
166+
Blob, Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree,
167+
Worktree,
162168
};
163169
#[cfg(feature = "attributes")]
164170
pub use types::{Pathspec, PathspecDetached, Submodule};

gix/src/object/impls.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::convert::TryFrom;
22

3-
use crate::{object, Commit, Object, ObjectDetached, Tag, Tree};
3+
use crate::{object, Blob, Commit, Object, ObjectDetached, Tag, Tree};
44

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

6161
fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
62-
let handle = value.repo;
62+
let repo = value.repo;
6363
match value.kind {
6464
object::Kind::Commit => Ok(Commit {
6565
id: value.id,
66-
repo: handle,
66+
repo,
6767
data: steal_from_freelist(&mut value.data),
6868
}),
6969
_ => Err(value),
@@ -75,11 +75,11 @@ impl<'repo> TryFrom<Object<'repo>> for Tag<'repo> {
7575
type Error = Object<'repo>;
7676

7777
fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
78-
let handle = value.repo;
78+
let repo = value.repo;
7979
match value.kind {
8080
object::Kind::Tag => Ok(Tag {
8181
id: value.id,
82-
repo: handle,
82+
repo,
8383
data: steal_from_freelist(&mut value.data),
8484
}),
8585
_ => Err(value),
@@ -91,11 +91,27 @@ impl<'repo> TryFrom<Object<'repo>> for Tree<'repo> {
9191
type Error = Object<'repo>;
9292

9393
fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
94-
let handle = value.repo;
94+
let repo = value.repo;
9595
match value.kind {
9696
object::Kind::Tree => Ok(Tree {
9797
id: value.id,
98-
repo: handle,
98+
repo,
99+
data: steal_from_freelist(&mut value.data),
100+
}),
101+
_ => Err(value),
102+
}
103+
}
104+
}
105+
106+
impl<'repo> TryFrom<Object<'repo>> for Blob<'repo> {
107+
type Error = Object<'repo>;
108+
109+
fn try_from(mut value: Object<'repo>) -> Result<Self, Self::Error> {
110+
let repo = value.repo;
111+
match value.kind {
112+
object::Kind::Blob => Ok(Blob {
113+
id: value.id,
114+
repo,
99115
data: steal_from_freelist(&mut value.data),
100116
}),
101117
_ => Err(value),

gix/src/object/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::convert::TryInto;
44
use gix_hash::ObjectId;
55
pub use gix_object::Kind;
66

7-
use crate::{Commit, Id, Object, ObjectDetached, Tag, Tree};
7+
use crate::{Blob, Commit, Id, Object, ObjectDetached, Tag, Tree};
88

99
mod errors;
1010
pub(crate) mod cache {
@@ -74,6 +74,14 @@ impl<'repo> Object<'repo> {
7474
}
7575
}
7676

77+
/// Transform this object into a blob, or panic if it is none.
78+
pub fn into_blob(self) -> Blob<'repo> {
79+
match self.try_into() {
80+
Ok(tree) => tree,
81+
Err(this) => panic!("Tried to use {} as tree, but was {}", this.id, this.kind),
82+
}
83+
}
84+
7785
/// Transform this object into a tree, or panic if it is none.
7886
pub fn into_tree(self) -> Tree<'repo> {
7987
match self.try_into() {
@@ -124,6 +132,15 @@ impl<'repo> Object<'repo> {
124132
expected: gix_object::Kind::Tree,
125133
})
126134
}
135+
136+
/// Transform this object into a blob, or return it as part of the `Err` if it is no blob.
137+
pub fn try_into_blob(self) -> Result<Blob<'repo>, try_into::Error> {
138+
self.try_into().map_err(|this: Self| try_into::Error {
139+
id: this.id,
140+
actual: this.kind,
141+
expected: gix_object::Kind::Blob,
142+
})
143+
}
127144
}
128145

129146
impl<'repo> Object<'repo> {

0 commit comments

Comments
 (0)