From 32d7e57eed2311ddb101a07cde8f80902001dfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Fri, 9 Jan 2026 20:51:34 +0100 Subject: [PATCH 1/3] feat: add `sha256` feature to `gix-object` The flag mostly enables `sha256` in `gix-hash`. --- gix-object/Cargo.toml | 2 ++ gix-object/tests/object/encode.rs | 34 +++++++++++++++++++ gix-object/tests/object/main.rs | 55 +++++++++++++++++++++++++++++++ justfile | 2 ++ 4 files changed, 93 insertions(+) diff --git a/gix-object/Cargo.toml b/gix-object/Cargo.toml index 6b5c842a08c..a1eb7c00c1f 100644 --- a/gix-object/Cargo.toml +++ b/gix-object/Cargo.toml @@ -26,6 +26,8 @@ path = "./benches/edit_tree.rs" [features] +## Support for SHA256 hashes and digests. +sha256 = ["gix-hash/sha256"] ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde = [ "dep:serde", diff --git a/gix-object/tests/object/encode.rs b/gix-object/tests/object/encode.rs index a77c58857cf..5ae338bd26b 100644 --- a/gix-object/tests/object/encode.rs +++ b/gix-object/tests/object/encode.rs @@ -123,6 +123,24 @@ mod tree { .expect("write succeeds, no validation is performed"); } + #[test] + #[cfg(feature = "sha256")] + fn write_to_does_not_validate_sha256() { + let mut tree = gix_object::Tree::empty(); + tree.entries.push(tree::Entry { + mode: EntryKind::Blob.into(), + filename: "".into(), + oid: gix_hash::Kind::Sha256.null(), + }); + tree.entries.push(tree::Entry { + mode: EntryKind::Tree.into(), + filename: "something\nwith\newlines\n".into(), + oid: gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha256), + }); + tree.write_to(&mut std::io::sink()) + .expect("write succeeds, no validation is performed"); + } + #[test] fn write_to_does_not_allow_separator() { let mut tree = gix_object::Tree::empty(); @@ -138,6 +156,22 @@ mod tree { ); } + #[test] + #[cfg(feature = "sha256")] + fn write_to_does_not_allow_separator_sha256() { + let mut tree = gix_object::Tree::empty(); + tree.entries.push(tree::Entry { + mode: EntryKind::Blob.into(), + filename: "hi\0ho".into(), + oid: gix_hash::Kind::Sha256.null(), + }); + let err = tree.write_to(&mut std::io::sink()).unwrap_err(); + assert_eq!( + err.to_string(), + r#"Nullbytes are invalid in file paths as they are separators: "hi\0ho""# + ); + } + round_trip!(gix_object::Tree, gix_object::TreeRef, "tree/everything.tree"); } diff --git a/gix-object/tests/object/main.rs b/gix-object/tests/object/main.rs index bf7c8eeea55..a81fd0e7dff 100644 --- a/gix-object/tests/object/main.rs +++ b/gix-object/tests/object/main.rs @@ -21,6 +21,20 @@ fn compute_hash() { ); } +#[test] +#[cfg(feature = "sha256")] +fn compute_hash_sha256() { + let hk = gix_hash::Kind::Sha256; + assert_eq!( + gix_object::compute_hash(hk, gix_object::Kind::Blob, &[]).expect("empty hash doesn’t collide"), + gix_hash::ObjectId::empty_blob(hk) + ); + assert_eq!( + gix_object::compute_hash(hk, gix_object::Kind::Tree, &[]).expect("empty hash doesn’t collide"), + gix_hash::ObjectId::empty_tree(hk) + ); +} + #[test] fn compute_stream_hash() { let hk = gix_hash::Kind::Sha1; @@ -50,6 +64,36 @@ fn compute_stream_hash() { ); } +#[test] +#[cfg(feature = "sha256")] +fn compute_stream_hash_sha256() { + let hk = gix_hash::Kind::Sha256; + 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))] @@ -78,6 +122,7 @@ fn fixture_name(kind: &str, path: &str) -> Vec { } #[test] +#[cfg(not(feature = "sha256"))] fn size_in_memory() { let actual = std::mem::size_of::(); assert!( @@ -86,6 +131,16 @@ fn size_in_memory() { ); } +#[test] +#[cfg(feature = "sha256")] +fn size_in_memory() { + let actual = std::mem::size_of::(); + assert!( + actual <= 288, + "{actual} <= 288: Prevent unexpected growth of what should be lightweight objects" + ); +} + fn hex_to_id(hex: &str) -> ObjectId { ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex") } diff --git a/justfile b/justfile index 5a23f6317fb..e9a8d42db90 100755 --- a/justfile +++ b/justfile @@ -74,6 +74,7 @@ check: cargo check -p gix-hash --no-default-features --features sha256 cargo check -p gix-object --all-features cargo check -p gix-object --features verbose-object-parsing-errors + cargo check -p gix-object --features sha256 cargo check -p gix-attributes --features serde cargo check -p gix-glob --features serde cargo check -p gix-worktree --features serde @@ -162,6 +163,7 @@ unit-tests: cargo nextest run -p gix-hash --no-default-features --features sha256 --no-fail-fast # TODO: make this actually work by removing 'sha1' from default features. cargo nextest run -p gix-object --no-fail-fast cargo nextest run -p gix-object --features verbose-object-parsing-errors --no-fail-fast + cargo nextest run -p gix-object --features sha256 --no-fail-fast cargo nextest run -p gix-tempfile --features signals --no-fail-fast cargo nextest run -p gix-features --all-features --no-fail-fast cargo nextest run -p gix-ref-tests --all-features --no-fail-fast From 6d950f036366ac3f53743c808be5cb149e4c9460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Tue, 13 Jan 2026 19:08:20 +0100 Subject: [PATCH 2/3] feat: add `Kind::all()` --- gix-hash/src/kind.rs | 17 +++++++++++++++++ gix-hash/tests/hash/kind.rs | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/gix-hash/src/kind.rs b/gix-hash/src/kind.rs index 8d8e01681fa..3cede752386 100644 --- a/gix-hash/src/kind.rs +++ b/gix-hash/src/kind.rs @@ -174,4 +174,21 @@ impl Kind { pub const fn empty_tree(&self) -> ObjectId { ObjectId::empty_tree(*self) } + + /// Return a list of available hash kinds. + #[inline] + pub const fn all() -> &'static [Self] { + #[cfg(all(feature = "sha1", not(feature = "sha256")))] + { + &[Self::Sha1] + } + #[cfg(all(not(feature = "sha1"), feature = "sha256"))] + { + &[Self::Sha256] + } + #[cfg(all(feature = "sha1", feature = "sha256"))] + { + &[Self::Sha1, Self::Sha256] + } + } } diff --git a/gix-hash/tests/hash/kind.rs b/gix-hash/tests/hash/kind.rs index 1a6ea642e6e..fa0b0227183 100644 --- a/gix-hash/tests/hash/kind.rs +++ b/gix-hash/tests/hash/kind.rs @@ -103,3 +103,21 @@ fn longest_sha1_and_sha256() { let longest = Kind::longest(); assert_eq!(longest, Kind::Sha256); } + +#[test] +#[cfg(all(feature = "sha1", not(feature = "sha256")))] +fn all() { + assert_eq!(Kind::all(), &[Kind::Sha1]); +} + +#[test] +#[cfg(all(not(feature = "sha1"), feature = "sha256"))] +fn all() { + assert_eq!(Kind::all(), &[Kind::Sha256]); +} + +#[test] +#[cfg(all(feature = "sha1", feature = "sha256"))] +fn all() { + assert_eq!(Kind::all(), &[Kind::Sha1, Kind::Sha256]); +} From 63d82b01ce9d12d0a014c042bda9d024cc020cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Tue, 13 Jan 2026 19:08:57 +0100 Subject: [PATCH 3/3] Simplify tests using `Kind::all()` --- gix-object/tests/object/encode.rs | 86 ++++++++++--------------------- 1 file changed, 28 insertions(+), 58 deletions(-) diff --git a/gix-object/tests/object/encode.rs b/gix-object/tests/object/encode.rs index 5ae338bd26b..eea30e69508 100644 --- a/gix-object/tests/object/encode.rs +++ b/gix-object/tests/object/encode.rs @@ -108,68 +108,38 @@ mod tree { #[test] fn write_to_does_not_validate() { - let mut tree = gix_object::Tree::empty(); - tree.entries.push(tree::Entry { - mode: EntryKind::Blob.into(), - filename: "".into(), - oid: gix_hash::Kind::Sha1.null(), - }); - tree.entries.push(tree::Entry { - mode: EntryKind::Tree.into(), - filename: "something\nwith\newlines\n".into(), - oid: gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha1), - }); - tree.write_to(&mut std::io::sink()) - .expect("write succeeds, no validation is performed"); - } - - #[test] - #[cfg(feature = "sha256")] - fn write_to_does_not_validate_sha256() { - let mut tree = gix_object::Tree::empty(); - tree.entries.push(tree::Entry { - mode: EntryKind::Blob.into(), - filename: "".into(), - oid: gix_hash::Kind::Sha256.null(), - }); - tree.entries.push(tree::Entry { - mode: EntryKind::Tree.into(), - filename: "something\nwith\newlines\n".into(), - oid: gix_hash::ObjectId::empty_tree(gix_hash::Kind::Sha256), - }); - tree.write_to(&mut std::io::sink()) - .expect("write succeeds, no validation is performed"); + for hash_kind in gix_hash::Kind::all() { + let mut tree = gix_object::Tree::empty(); + tree.entries.push(tree::Entry { + mode: EntryKind::Blob.into(), + filename: "".into(), + oid: hash_kind.null(), + }); + tree.entries.push(tree::Entry { + mode: EntryKind::Tree.into(), + filename: "something\nwith\newlines\n".into(), + oid: gix_hash::ObjectId::empty_tree(*hash_kind), + }); + tree.write_to(&mut std::io::sink()) + .expect("write succeeds, no validation is performed"); + } } #[test] fn write_to_does_not_allow_separator() { - let mut tree = gix_object::Tree::empty(); - tree.entries.push(tree::Entry { - mode: EntryKind::Blob.into(), - filename: "hi\0ho".into(), - oid: gix_hash::Kind::Sha1.null(), - }); - let err = tree.write_to(&mut std::io::sink()).unwrap_err(); - assert_eq!( - err.to_string(), - r#"Nullbytes are invalid in file paths as they are separators: "hi\0ho""# - ); - } - - #[test] - #[cfg(feature = "sha256")] - fn write_to_does_not_allow_separator_sha256() { - let mut tree = gix_object::Tree::empty(); - tree.entries.push(tree::Entry { - mode: EntryKind::Blob.into(), - filename: "hi\0ho".into(), - oid: gix_hash::Kind::Sha256.null(), - }); - let err = tree.write_to(&mut std::io::sink()).unwrap_err(); - assert_eq!( - err.to_string(), - r#"Nullbytes are invalid in file paths as they are separators: "hi\0ho""# - ); + for hash_kind in gix_hash::Kind::all() { + let mut tree = gix_object::Tree::empty(); + tree.entries.push(tree::Entry { + mode: EntryKind::Blob.into(), + filename: "hi\0ho".into(), + oid: hash_kind.null(), + }); + let err = tree.write_to(&mut std::io::sink()).unwrap_err(); + assert_eq!( + err.to_string(), + r#"Nullbytes are invalid in file paths as they are separators: "hi\0ho""# + ); + } } round_trip!(gix_object::Tree, gix_object::TreeRef, "tree/everything.tree");