Make sha1 optional in `gix-hash`
Unified · Split
| @@ -18,7 +18,7 @@ test = false · 17 unmodified lines | |||
| 18 | [features] | 18 | [features] |
| 19 | default = ["sha1"] | 19 | default = ["sha1"] |
| 20 | ## Support for SHA1 hashes and digests. | 20 | ## Support for SHA1 hashes and digests. |
| 21 | sha1 = [] | 21 | sha1 = ["dep:sha1-checked"] |
| 22 | ## Support for SHA256 hashes and digests. | 22 | ## Support for SHA256 hashes and digests. |
| 23 | sha256 = ["dep:sha2"] | 23 | sha256 = ["dep:sha2"] |
| 24 | ## Data structures implement `serde::Serialize` and `serde::Deserialize`. | 24 | ## Data structures implement `serde::Serialize` and `serde::Deserialize`. |
| @@ -30,7 +30,7 @@ gix-features = { version = "^0.45.1", path = "../gix-features", features = ["pro · 5 unmodified lines | |||
| 30 | thiserror = "2.0.17" | 30 | thiserror = "2.0.17" |
| 31 | faster-hex = { version = "0.10.0", default-features = false, features = ["std"] } | 31 | faster-hex = { version = "0.10.0", default-features = false, features = ["std"] } |
| 32 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } | 32 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } |
| 33 | sha1-checked = { version = "0.10.0", default-features = false } | 33 | sha1-checked = { version = "0.10.0", optional = true, default-features = false } |
| 34 | sha2 = { version = "0.10.0", optional = true, default-features = false } | 34 | sha2 = { version = "0.10.0", optional = true, default-features = false } |
| 35 | 35 | ||
| 36 | document-features = { version = "0.2.0", optional = true } | 36 | document-features = { version = "0.2.0", optional = true } |
| @@ -7,8 +7,12 @@ pub enum Error { · 6 unmodified lines | |||
| 7 | } | 7 | } |
| 8 | 8 | ||
| 9 | pub(super) mod _impl { | 9 | pub(super) mod _impl { |
| 10 | #[cfg(feature = "sha1")] | ||
| 10 | use sha1_checked::{CollisionResult, Digest}; | 11 | use sha1_checked::{CollisionResult, Digest}; |
| 11 | 12 | ||
| 13 | #[cfg(all(not(feature = "sha1"), feature = "sha256"))] | ||
| 14 | use sha2::Digest; | ||
| 15 | |||
| 12 | use crate::hasher::Error; | 16 | use crate::hasher::Error; |
| 13 | 17 | ||
| 14 | /// Hash implementations that can be used once. | 18 | /// Hash implementations that can be used once. |
| @@ -17,6 +21,7 @@ pub(super) mod _impl { · 2 unmodified lines | |||
| 17 | /// An implementation of the SHA1 hash. | 21 | /// An implementation of the SHA1 hash. |
| 18 | /// | 22 | /// |
| 19 | /// We use [`sha1_checked`] to implement the same collision detection algorithm as Git. | 23 | /// We use [`sha1_checked`] to implement the same collision detection algorithm as Git. |
| 24 | #[cfg(feature = "sha1")] | ||
| 20 | Sha1(sha1_checked::Sha1), | 25 | Sha1(sha1_checked::Sha1), |
| 21 | /// An implementation of the SHA256 hash. | 26 | /// An implementation of the SHA256 hash. |
| 22 | #[cfg(feature = "sha256")] | 27 | #[cfg(feature = "sha256")] |
| @@ -25,6 +30,7 @@ pub(super) mod _impl { · 2 unmodified lines | |||
| 25 | 30 | ||
| 26 | impl Hasher { | 31 | impl Hasher { |
| 27 | /// Let's not make this public to force people to go through [`hasher()`]. | 32 | /// Let's not make this public to force people to go through [`hasher()`]. |
| 33 | #[cfg(feature = "sha1")] | ||
| 28 | fn new_sha1() -> Self { | 34 | fn new_sha1() -> Self { |
| 29 | // This matches the configuration used by Git, which only uses | 35 | // This matches the configuration used by Git, which only uses |
| 30 | // the collision detection to bail out, rather than computing | 36 | // the collision detection to bail out, rather than computing |
| @@ -44,6 +50,7 @@ pub(super) mod _impl { · 13 unmodified lines | |||
| 44 | /// Digest the given `bytes`. | 50 | /// Digest the given `bytes`. |
| 45 | pub fn update(&mut self, bytes: &[u8]) { | 51 | pub fn update(&mut self, bytes: &[u8]) { |
| 46 | match self { | 52 | match self { |
| 53 | #[cfg(feature = "sha1")] | ||
| 47 | Hasher::Sha1(sha1) => sha1.update(bytes), | 54 | Hasher::Sha1(sha1) => sha1.update(bytes), |
| 48 | #[cfg(feature = "sha256")] | 55 | #[cfg(feature = "sha256")] |
| 49 | Hasher::Sha256(sha256) => sha256.update(bytes), | 56 | Hasher::Sha256(sha256) => sha256.update(bytes), |
| @@ -59,6 +66,7 @@ pub(super) mod _impl { · 9 unmodified lines | |||
| 59 | #[inline] | 66 | #[inline] |
| 60 | pub fn try_finalize(self) -> Result<crate::ObjectId, Error> { | 67 | pub fn try_finalize(self) -> Result<crate::ObjectId, Error> { |
| 61 | match self { | 68 | match self { |
| 69 | #[cfg(feature = "sha1")] | ||
| 62 | Hasher::Sha1(sha1) => match sha1.try_finalize() { | 70 | Hasher::Sha1(sha1) => match sha1.try_finalize() { |
| 63 | CollisionResult::Ok(digest) => Ok(crate::ObjectId::Sha1(digest.into())), | 71 | CollisionResult::Ok(digest) => Ok(crate::ObjectId::Sha1(digest.into())), |
| 64 | CollisionResult::Mitigated(_) => { | 72 | CollisionResult::Mitigated(_) => { |
| @@ -89,6 +97,7 @@ pub(super) mod _impl { · 24 unmodified lines | |||
| 89 | #[inline] | 97 | #[inline] |
| 90 | pub fn hasher(kind: crate::Kind) -> Hasher { | 98 | pub fn hasher(kind: crate::Kind) -> Hasher { |
| 91 | match kind { | 99 | match kind { |
| 100 | #[cfg(feature = "sha1")] | ||
| 92 | crate::Kind::Sha1 => Hasher::new_sha1(), | 101 | crate::Kind::Sha1 => Hasher::new_sha1(), |
| 93 | #[cfg(feature = "sha256")] | 102 | #[cfg(feature = "sha256")] |
| 94 | crate::Kind::Sha256 => Hasher::new_sha256(), | 103 | crate::Kind::Sha256 => Hasher::new_sha256(), |
| @@ -114,6 +114,7 @@ pub(super) mod _impl { · 113 unmodified lines | |||
| 114 | /// Create a new hash writer which hashes all bytes written to `inner` with a hash of `kind`. | 114 | /// Create a new hash writer which hashes all bytes written to `inner` with a hash of `kind`. |
| 115 | pub fn new(inner: T, object_hash: crate::Kind) -> Self { | 115 | pub fn new(inner: T, object_hash: crate::Kind) -> Self { |
| 116 | match object_hash { | 116 | match object_hash { |
| 117 | #[cfg(feature = "sha1")] | ||
| 117 | crate::Kind::Sha1 => Write { | 118 | crate::Kind::Sha1 => Write { |
| 118 | inner, | 119 | inner, |
| 119 | hash: crate::hasher(object_hash), | 120 | hash: crate::hasher(object_hash), |
| @@ -1,6 +1,9 @@ | |||
| 1 | use std::str::FromStr; | 1 | use std::str::FromStr; |
| 2 | 2 | ||
| 3 | use crate::{oid, Kind, ObjectId, SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; | 3 | use crate::{oid, Kind, ObjectId}; |
| 4 | |||
| 5 | #[cfg(feature = "sha1")] | ||
| 6 | use crate::{SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; | ||
| 4 | 7 | ||
| 5 | #[cfg(feature = "sha256")] | 8 | #[cfg(feature = "sha256")] |
| 6 | use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; | 9 | use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; |
| @@ -10,6 +13,7 @@ impl TryFrom<u8> for Kind { · 3 unmodified lines | |||
| 10 | 13 | ||
| 11 | fn try_from(value: u8) -> Result<Self, Self::Error> { | 14 | fn try_from(value: u8) -> Result<Self, Self::Error> { |
| 12 | Ok(match value { | 15 | Ok(match value { |
| 16 | #[cfg(feature = "sha1")] | ||
| 13 | 1 => Kind::Sha1, | 17 | 1 => Kind::Sha1, |
| 14 | #[cfg(feature = "sha256")] | 18 | #[cfg(feature = "sha256")] |
| 15 | 2 => Kind::Sha256, | 19 | 2 => Kind::Sha256, |
| @@ -23,6 +27,7 @@ impl FromStr for Kind { · 7 unmodified lines | |||
| 23 | 27 | ||
| 24 | fn from_str(s: &str) -> Result<Self, Self::Err> { | 28 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 25 | Ok(match s { | 29 | Ok(match s { |
| 30 | #[cfg(feature = "sha1")] | ||
| 26 | "sha1" | "SHA1" | "SHA-1" => Kind::Sha1, | 31 | "sha1" | "SHA1" | "SHA-1" => Kind::Sha1, |
| 27 | #[cfg(feature = "sha256")] | 32 | #[cfg(feature = "sha256")] |
| 28 | "sha256" | "SHA256" | "SHA-256" => Kind::Sha256, | 33 | "sha256" | "SHA256" | "SHA-256" => Kind::Sha256, |
| @@ -34,6 +39,7 @@ impl FromStr for Kind { · 5 unmodified lines | |||
| 34 | impl std::fmt::Display for Kind { | 39 | impl std::fmt::Display for Kind { |
| 35 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 36 | match self { | 41 | match self { |
| 42 | #[cfg(feature = "sha1")] | ||
| 37 | Kind::Sha1 => f.write_str("sha1"), | 43 | Kind::Sha1 => f.write_str("sha1"), |
| 38 | #[cfg(feature = "sha256")] | 44 | #[cfg(feature = "sha256")] |
| 39 | Kind::Sha256 => f.write_str("sha256"), | 45 | Kind::Sha256 => f.write_str("sha256"), |
| @@ -84,6 +90,7 @@ impl Kind { · 44 unmodified lines | |||
| 84 | #[inline] | 90 | #[inline] |
| 85 | pub const fn len_in_hex(&self) -> usize { | 91 | pub const fn len_in_hex(&self) -> usize { |
| 86 | match self { | 92 | match self { |
| 93 | #[cfg(feature = "sha1")] | ||
| 87 | Kind::Sha1 => SIZE_OF_SHA1_HEX_DIGEST, | 94 | Kind::Sha1 => SIZE_OF_SHA1_HEX_DIGEST, |
| 88 | #[cfg(feature = "sha256")] | 95 | #[cfg(feature = "sha256")] |
| 89 | Kind::Sha256 => SIZE_OF_SHA256_HEX_DIGEST, | 96 | Kind::Sha256 => SIZE_OF_SHA256_HEX_DIGEST, |
| @@ -94,6 +101,7 @@ impl Kind { · 4 unmodified lines | |||
| 94 | #[inline] | 101 | #[inline] |
| 95 | pub const fn len_in_bytes(&self) -> usize { | 102 | pub const fn len_in_bytes(&self) -> usize { |
| 96 | match self { | 103 | match self { |
| 104 | #[cfg(feature = "sha1")] | ||
| 97 | Kind::Sha1 => SIZE_OF_SHA1_DIGEST, | 105 | Kind::Sha1 => SIZE_OF_SHA1_DIGEST, |
| 98 | #[cfg(feature = "sha256")] | 106 | #[cfg(feature = "sha256")] |
| 99 | Kind::Sha256 => SIZE_OF_SHA256_DIGEST, | 107 | Kind::Sha256 => SIZE_OF_SHA256_DIGEST, |
| @@ -106,6 +114,7 @@ impl Kind { · 6 unmodified lines | |||
| 106 | #[inline] | 114 | #[inline] |
| 107 | pub const fn from_hex_len(hex_len: usize) -> Option<Self> { | 115 | pub const fn from_hex_len(hex_len: usize) -> Option<Self> { |
| 108 | Some(match hex_len { | 116 | Some(match hex_len { |
| 117 | #[cfg(feature = "sha1")] | ||
| 109 | 0..=SIZE_OF_SHA1_HEX_DIGEST => Kind::Sha1, | 118 | 0..=SIZE_OF_SHA1_HEX_DIGEST => Kind::Sha1, |
| 110 | #[cfg(feature = "sha256")] | 119 | #[cfg(feature = "sha256")] |
| 111 | 0..=SIZE_OF_SHA256_HEX_DIGEST => Kind::Sha256, | 120 | 0..=SIZE_OF_SHA256_HEX_DIGEST => Kind::Sha256, |
| @@ -124,6 +133,7 @@ impl Kind { · 12 unmodified lines | |||
| 124 | #[inline] | 133 | #[inline] |
| 125 | pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { | 134 | pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { |
| 126 | match bytes { | 135 | match bytes { |
| 136 | #[cfg(feature = "sha1")] | ||
| 127 | SIZE_OF_SHA1_DIGEST => Kind::Sha1, | 137 | SIZE_OF_SHA1_DIGEST => Kind::Sha1, |
| 128 | #[cfg(feature = "sha256")] | 138 | #[cfg(feature = "sha256")] |
| 129 | SIZE_OF_SHA256_DIGEST => Kind::Sha256, | 139 | SIZE_OF_SHA256_DIGEST => Kind::Sha256, |
| @@ -135,6 +145,7 @@ impl Kind { · 5 unmodified lines | |||
| 135 | #[inline] | 145 | #[inline] |
| 136 | pub fn null_ref(&self) -> &'static oid { | 146 | pub fn null_ref(&self) -> &'static oid { |
| 137 | match self { | 147 | match self { |
| 148 | #[cfg(feature = "sha1")] | ||
| 138 | Kind::Sha1 => oid::null_sha1(), | 149 | Kind::Sha1 => oid::null_sha1(), |
| 139 | #[cfg(feature = "sha256")] | 150 | #[cfg(feature = "sha256")] |
| 140 | Kind::Sha256 => oid::null_sha256(), | 151 | Kind::Sha256 => oid::null_sha256(), |
| @@ -145,6 +156,7 @@ impl Kind { · 4 unmodified lines | |||
| 145 | #[inline] | 156 | #[inline] |
| 146 | pub const fn null(&self) -> ObjectId { | 157 | pub const fn null(&self) -> ObjectId { |
| 147 | match self { | 158 | match self { |
| 159 | #[cfg(feature = "sha1")] | ||
| 148 | Kind::Sha1 => ObjectId::null_sha1(), | 160 | Kind::Sha1 => ObjectId::null_sha1(), |
| 149 | #[cfg(feature = "sha256")] | 161 | #[cfg(feature = "sha256")] |
| 150 | Kind::Sha256 => ObjectId::null_sha256(), | 162 | Kind::Sha256 => ObjectId::null_sha256(), |
| @@ -9,10 +9,8 @@ · 8 unmodified lines | |||
| 9 | #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))] | 9 | #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))] |
| 10 | #![deny(missing_docs, rust_2018_idioms, unsafe_code)] | 10 | #![deny(missing_docs, rust_2018_idioms, unsafe_code)] |
| 11 | 11 | ||
| 12 | // Remove this once other hashes (e.g., SHA256, and potentially others) | 12 | #[cfg(all(not(feature = "sha1"), not(feature = "sha256")))] |
| 13 | // are supported, and this crate can build without [`ObjectId::Sha1`]. | 13 | compile_error!("Please set either the `sha1` or the `sha256` feature flag"); |
| 14 | #[cfg(not(feature = "sha1"))] | ||
| 15 | compile_error!("Please set the `sha1` feature flag"); | ||
| 16 | 14 | ||
| 17 | #[path = "oid.rs"] | 15 | #[path = "oid.rs"] |
| 18 | mod borrowed; | 16 | mod borrowed; |
| @@ -47,8 +45,10 @@ pub struct Prefix { · 28 unmodified lines | |||
| 47 | } | 45 | } |
| 48 | 46 | ||
| 49 | /// The size of a SHA1 hash digest in bytes. | 47 | /// The size of a SHA1 hash digest in bytes. |
| 48 | #[cfg(feature = "sha1")] | ||
| 50 | const SIZE_OF_SHA1_DIGEST: usize = 20; | 49 | const SIZE_OF_SHA1_DIGEST: usize = 20; |
| 51 | /// The size of a SHA1 hash digest in hex. | 50 | /// The size of a SHA1 hash digest in hex. |
| 51 | #[cfg(feature = "sha1")] | ||
| 52 | const SIZE_OF_SHA1_HEX_DIGEST: usize = 2 * SIZE_OF_SHA1_DIGEST; | 52 | const SIZE_OF_SHA1_HEX_DIGEST: usize = 2 * SIZE_OF_SHA1_DIGEST; |
| 53 | 53 | ||
| 54 | /// The size of a SHA256 hash digest in bytes. | 54 | /// The size of a SHA256 hash digest in bytes. |
| @@ -58,8 +58,10 @@ const SIZE_OF_SHA256_DIGEST: usize = 32; · 3 unmodified lines | |||
| 58 | #[cfg(feature = "sha256")] | 58 | #[cfg(feature = "sha256")] |
| 59 | const SIZE_OF_SHA256_HEX_DIGEST: usize = 2 * SIZE_OF_SHA256_DIGEST; | 59 | const SIZE_OF_SHA256_HEX_DIGEST: usize = 2 * SIZE_OF_SHA256_DIGEST; |
| 60 | 60 | ||
| 61 | #[cfg(feature = "sha1")] | ||
| 61 | const EMPTY_BLOB_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = | 62 | const EMPTY_BLOB_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = |
| 62 | b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"; | 63 | b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"; |
| 64 | #[cfg(feature = "sha1")] | ||
| 63 | const EMPTY_TREE_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = | 65 | const EMPTY_TREE_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = |
| 64 | b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"; | 66 | b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"; |
| 65 | 67 | ||
| @@ -74,9 +76,11 @@ const EMPTY_TREE_SHA256: &[u8; SIZE_OF_SHA256_DIGEST] = b"\x6e\xf1\x9b\x41\x22\x · 8 unmodified lines | |||
| 74 | #[non_exhaustive] | 76 | #[non_exhaustive] |
| 75 | pub enum Kind { | 77 | pub enum Kind { |
| 76 | /// The SHA1 hash with 160 bits. | 78 | /// The SHA1 hash with 160 bits. |
| 77 | #[default] | 79 | #[cfg_attr(feature = "sha1", default)] |
| 80 | #[cfg(feature = "sha1")] | ||
| 78 | Sha1 = 1, | 81 | Sha1 = 1, |
| 79 | /// The SHA256 hash with 256 bits. | 82 | /// The SHA256 hash with 256 bits. |
| 83 | #[cfg_attr(all(not(feature = "sha1"), feature = "sha256"), default)] | ||
| 80 | #[cfg(feature = "sha256")] | 84 | #[cfg(feature = "sha256")] |
| 81 | Sha256 = 2, | 85 | Sha256 = 2, |
| 82 | } | 86 | } |
| @@ -4,7 +4,10 @@ use std::{ · 3 unmodified lines | |||
| 4 | ops::Deref, | 4 | ops::Deref, |
| 5 | }; | 5 | }; |
| 6 | 6 | ||
| 7 | use crate::{borrowed::oid, Kind, EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; | 7 | use crate::{borrowed::oid, Kind}; |
| 8 | |||
| 9 | #[cfg(feature = "sha1")] | ||
| 10 | use crate::{EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; | ||
| 8 | 11 | ||
| 9 | #[cfg(feature = "sha256")] | 12 | #[cfg(feature = "sha256")] |
| 10 | use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; | 13 | use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; |
| @@ -15,6 +18,7 @@ use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; · 4 unmodified lines | |||
| 15 | #[non_exhaustive] | 18 | #[non_exhaustive] |
| 16 | pub enum ObjectId { | 19 | pub enum ObjectId { |
| 17 | /// A SHA1 hash digest | 20 | /// A SHA1 hash digest |
| 21 | #[cfg(feature = "sha1")] | ||
| 18 | Sha1([u8; SIZE_OF_SHA1_DIGEST]), | 22 | Sha1([u8; SIZE_OF_SHA1_DIGEST]), |
| 19 | /// A SHA256 hash digest | 23 | /// A SHA256 hash digest |
| 20 | #[cfg(feature = "sha256")] | 24 | #[cfg(feature = "sha256")] |
| @@ -38,7 +42,10 @@ impl Hash for ObjectId { · 17 unmodified lines | |||
| 38 | pub mod decode { | 42 | pub mod decode { |
| 39 | use std::str::FromStr; | 43 | use std::str::FromStr; |
| 40 | 44 | ||
| 41 | use crate::{object_id::ObjectId, SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; | 45 | use crate::object_id::ObjectId; |
| 46 | |||
| 47 | #[cfg(feature = "sha1")] | ||
| 48 | use crate::{SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; | ||
| 42 | 49 | ||
| 43 | #[cfg(feature = "sha256")] | 50 | #[cfg(feature = "sha256")] |
| 44 | use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; | 51 | use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; |
| @@ -62,6 +69,7 @@ pub mod decode { · 17 unmodified lines | |||
| 62 | /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] | 69 | /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] |
| 63 | pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> { | 70 | pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> { |
| 64 | match buffer.len() { | 71 | match buffer.len() { |
| 72 | #[cfg(feature = "sha1")] | ||
| 65 | SIZE_OF_SHA1_HEX_DIGEST => Ok({ | 73 | SIZE_OF_SHA1_HEX_DIGEST => Ok({ |
| 66 | ObjectId::Sha1({ | 74 | ObjectId::Sha1({ |
| 67 | let mut buf = [0; SIZE_OF_SHA1_DIGEST]; | 75 | let mut buf = [0; SIZE_OF_SHA1_DIGEST]; |
| @@ -107,6 +115,7 @@ impl ObjectId { · 39 unmodified lines | |||
| 107 | #[inline] | 115 | #[inline] |
| 108 | pub fn kind(&self) -> Kind { | 116 | pub fn kind(&self) -> Kind { |
| 109 | match self { | 117 | match self { |
| 118 | #[cfg(feature = "sha1")] | ||
| 110 | ObjectId::Sha1(_) => Kind::Sha1, | 119 | ObjectId::Sha1(_) => Kind::Sha1, |
| 111 | #[cfg(feature = "sha256")] | 120 | #[cfg(feature = "sha256")] |
| 112 | ObjectId::Sha256(_) => Kind::Sha256, | 121 | ObjectId::Sha256(_) => Kind::Sha256, |
| @@ -116,6 +125,7 @@ impl ObjectId { · 3 unmodified lines | |||
| 116 | #[inline] | 125 | #[inline] |
| 117 | pub fn as_slice(&self) -> &[u8] { | 126 | pub fn as_slice(&self) -> &[u8] { |
| 118 | match self { | 127 | match self { |
| 128 | #[cfg(feature = "sha1")] | ||
| 119 | Self::Sha1(b) => b.as_ref(), | 129 | Self::Sha1(b) => b.as_ref(), |
| 120 | #[cfg(feature = "sha256")] | 130 | #[cfg(feature = "sha256")] |
| 121 | Self::Sha256(b) => b.as_ref(), | 131 | Self::Sha256(b) => b.as_ref(), |
| @@ -125,6 +135,7 @@ impl ObjectId { · 3 unmodified lines | |||
| 125 | #[inline] | 135 | #[inline] |
| 126 | pub fn as_mut_slice(&mut self) -> &mut [u8] { | 136 | pub fn as_mut_slice(&mut self) -> &mut [u8] { |
| 127 | match self { | 137 | match self { |
| 138 | #[cfg(feature = "sha1")] | ||
| 128 | Self::Sha1(b) => b.as_mut(), | 139 | Self::Sha1(b) => b.as_mut(), |
| 129 | #[cfg(feature = "sha256")] | 140 | #[cfg(feature = "sha256")] |
| 130 | Self::Sha256(b) => b.as_mut(), | 141 | Self::Sha256(b) => b.as_mut(), |
| @@ -135,6 +146,7 @@ impl ObjectId { · 4 unmodified lines | |||
| 135 | #[inline] | 146 | #[inline] |
| 136 | pub const fn empty_blob(hash: Kind) -> ObjectId { | 147 | pub const fn empty_blob(hash: Kind) -> ObjectId { |
| 137 | match hash { | 148 | match hash { |
| 149 | #[cfg(feature = "sha1")] | ||
| 138 | Kind::Sha1 => ObjectId::Sha1(*EMPTY_BLOB_SHA1), | 150 | Kind::Sha1 => ObjectId::Sha1(*EMPTY_BLOB_SHA1), |
| 139 | #[cfg(feature = "sha256")] | 151 | #[cfg(feature = "sha256")] |
| 140 | Kind::Sha256 => ObjectId::Sha256(*EMPTY_BLOB_SHA256), | 152 | Kind::Sha256 => ObjectId::Sha256(*EMPTY_BLOB_SHA256), |
| @@ -145,6 +157,7 @@ impl ObjectId { · 4 unmodified lines | |||
| 145 | #[inline] | 157 | #[inline] |
| 146 | pub const fn empty_tree(hash: Kind) -> ObjectId { | 158 | pub const fn empty_tree(hash: Kind) -> ObjectId { |
| 147 | match hash { | 159 | match hash { |
| 160 | #[cfg(feature = "sha1")] | ||
| 148 | Kind::Sha1 => ObjectId::Sha1(*EMPTY_TREE_SHA1), | 161 | Kind::Sha1 => ObjectId::Sha1(*EMPTY_TREE_SHA1), |
| 149 | #[cfg(feature = "sha256")] | 162 | #[cfg(feature = "sha256")] |
| 150 | Kind::Sha256 => ObjectId::Sha256(*EMPTY_TREE_SHA256), | 163 | Kind::Sha256 => ObjectId::Sha256(*EMPTY_TREE_SHA256), |
| @@ -156,6 +169,7 @@ impl ObjectId { · 5 unmodified lines | |||
| 156 | #[doc(alias = "zero", alias = "git2")] | 169 | #[doc(alias = "zero", alias = "git2")] |
| 157 | pub const fn null(kind: Kind) -> ObjectId { | 170 | pub const fn null(kind: Kind) -> ObjectId { |
| 158 | match kind { | 171 | match kind { |
| 172 | #[cfg(feature = "sha1")] | ||
| 159 | Kind::Sha1 => Self::null_sha1(), | 173 | Kind::Sha1 => Self::null_sha1(), |
| 160 | #[cfg(feature = "sha256")] | 174 | #[cfg(feature = "sha256")] |
| 161 | Kind::Sha256 => Self::null_sha256(), | 175 | Kind::Sha256 => Self::null_sha256(), |
| @@ -167,6 +181,7 @@ impl ObjectId { · 5 unmodified lines | |||
| 167 | #[doc(alias = "is_zero", alias = "git2")] | 181 | #[doc(alias = "is_zero", alias = "git2")] |
| 168 | pub fn is_null(&self) -> bool { | 182 | pub fn is_null(&self) -> bool { |
| 169 | match self { | 183 | match self { |
| 184 | #[cfg(feature = "sha1")] | ||
| 170 | ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(), | 185 | ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(), |
| 171 | #[cfg(feature = "sha256")] | 186 | #[cfg(feature = "sha256")] |
| 172 | ObjectId::Sha256(digest) => &digest[..] == oid::null_sha256().as_bytes(), | 187 | ObjectId::Sha256(digest) => &digest[..] == oid::null_sha256().as_bytes(), |
| @@ -193,6 +208,7 @@ impl ObjectId { · 20 unmodified lines | |||
| 193 | /// Use `Self::try_from(bytes)` for a fallible version. | 208 | /// Use `Self::try_from(bytes)` for a fallible version. |
| 194 | pub fn from_bytes_or_panic(bytes: &[u8]) -> Self { | 209 | pub fn from_bytes_or_panic(bytes: &[u8]) -> Self { |
| 195 | match bytes.len() { | 210 | match bytes.len() { |
| 211 | #[cfg(feature = "sha1")] | ||
| 196 | SIZE_OF_SHA1_DIGEST => Self::Sha1(bytes.try_into().expect("prior length validation")), | 212 | SIZE_OF_SHA1_DIGEST => Self::Sha1(bytes.try_into().expect("prior length validation")), |
| 197 | #[cfg(feature = "sha256")] | 213 | #[cfg(feature = "sha256")] |
| 198 | SIZE_OF_SHA256_DIGEST => Self::Sha256(bytes.try_into().expect("prior length validation")), | 214 | SIZE_OF_SHA256_DIGEST => Self::Sha256(bytes.try_into().expect("prior length validation")), |
| @@ -205,6 +221,7 @@ impl ObjectId { · 6 unmodified lines | |||
| 205 | impl ObjectId { | 221 | impl ObjectId { |
| 206 | /// Instantiate an `ObjectId` from a 20 bytes SHA1 digest. | 222 | /// Instantiate an `ObjectId` from a 20 bytes SHA1 digest. |
| 207 | #[inline] | 223 | #[inline] |
| 224 | #[cfg(feature = "sha1")] | ||
| 208 | fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { | 225 | fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { |
| 209 | ObjectId::Sha1(id) | 226 | ObjectId::Sha1(id) |
| 210 | } | 227 | } |
| @@ -220,6 +237,7 @@ impl ObjectId { · 9 unmodified lines | |||
| 220 | /// | 237 | /// |
| 221 | /// Panics if the slice doesn't have a length of 20. | 238 | /// Panics if the slice doesn't have a length of 20. |
| 222 | #[inline] | 239 | #[inline] |
| 240 | #[cfg(feature = "sha1")] | ||
| 223 | pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId { | 241 | pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId { |
| 224 | let mut id = [0; SIZE_OF_SHA1_DIGEST]; | 242 | let mut id = [0; SIZE_OF_SHA1_DIGEST]; |
| 225 | id.copy_from_slice(b); | 243 | id.copy_from_slice(b); |
| @@ -239,6 +257,7 @@ impl ObjectId { · 13 unmodified lines | |||
| 239 | 257 | ||
| 240 | /// Returns an `ObjectId` representing a SHA1 whose memory is zeroed. | 258 | /// Returns an `ObjectId` representing a SHA1 whose memory is zeroed. |
| 241 | #[inline] | 259 | #[inline] |
| 260 | #[cfg(feature = "sha1")] | ||
| 242 | pub(crate) const fn null_sha1() -> ObjectId { | 261 | pub(crate) const fn null_sha1() -> ObjectId { |
| 243 | ObjectId::Sha1([0u8; SIZE_OF_SHA1_DIGEST]) | 262 | ObjectId::Sha1([0u8; SIZE_OF_SHA1_DIGEST]) |
| 244 | } | 263 | } |
| @@ -254,6 +273,7 @@ impl ObjectId { · 9 unmodified lines | |||
| 254 | impl std::fmt::Debug for ObjectId { | 273 | impl std::fmt::Debug for ObjectId { |
| 255 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 274 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 256 | match self { | 275 | match self { |
| 276 | #[cfg(feature = "sha1")] | ||
| 257 | ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, | 277 | ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, |
| 258 | #[cfg(feature = "sha256")] | 278 | #[cfg(feature = "sha256")] |
| 259 | ObjectId::Sha256(_) => f.write_str("Sha256(")?, | 279 | ObjectId::Sha256(_) => f.write_str("Sha256(")?, |
| @@ -265,6 +285,7 @@ impl std::fmt::Debug for ObjectId { · 5 unmodified lines | |||
| 265 | } | 285 | } |
| 266 | } | 286 | } |
| 267 | 287 | ||
| 288 | #[cfg(feature = "sha1")] | ||
| 268 | impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { | 289 | impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { |
| 269 | fn from(v: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { | 290 | fn from(v: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { |
| 270 | Self::new_sha1(v) | 291 | Self::new_sha1(v) |
| @@ -281,6 +302,7 @@ impl From<[u8; SIZE_OF_SHA256_DIGEST]> for ObjectId { · 10 unmodified lines | |||
| 281 | impl From<&oid> for ObjectId { | 302 | impl From<&oid> for ObjectId { |
| 282 | fn from(v: &oid) -> Self { | 303 | fn from(v: &oid) -> Self { |
| 283 | match v.kind() { | 304 | match v.kind() { |
| 305 | #[cfg(feature = "sha1")] | ||
| 284 | Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()), | 306 | Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()), |
| 285 | #[cfg(feature = "sha256")] | 307 | #[cfg(feature = "sha256")] |
| 286 | Kind::Sha256 => ObjectId::from_32_bytes(v.as_bytes()), | 308 | Kind::Sha256 => ObjectId::from_32_bytes(v.as_bytes()), |
| @@ -1,6 +1,9 @@ | |||
| 1 | use std::hash; | 1 | use std::hash; |
| 2 | 2 | ||
| 3 | use crate::{Kind, ObjectId, EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; | 3 | use crate::{Kind, ObjectId}; |
| 4 | |||
| 5 | #[cfg(feature = "sha1")] | ||
| 6 | use crate::{EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; | ||
| 4 | 7 | ||
| 5 | #[cfg(feature = "sha256")] | 8 | #[cfg(feature = "sha256")] |
| 6 | use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; | 9 | use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; |
| @@ -59,6 +62,7 @@ impl std::fmt::Debug for oid { · 52 unmodified lines | |||
| 59 | f, | 62 | f, |
| 60 | "{}({})", | 63 | "{}({})", |
| 61 | match self.kind() { | 64 | match self.kind() { |
| 65 | #[cfg(feature = "sha1")] | ||
| 62 | Kind::Sha1 => "Sha1", | 66 | Kind::Sha1 => "Sha1", |
| 63 | #[cfg(feature = "sha256")] | 67 | #[cfg(feature = "sha256")] |
| 64 | Kind::Sha256 => "Sha256", | 68 | Kind::Sha256 => "Sha256", |
| @@ -82,6 +86,7 @@ impl oid { · 17 unmodified lines | |||
| 82 | #[inline] | 86 | #[inline] |
| 83 | pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> { | 87 | pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> { |
| 84 | match digest.len() { | 88 | match digest.len() { |
| 89 | #[cfg(feature = "sha1")] | ||
| 85 | SIZE_OF_SHA1_DIGEST => Ok( | 90 | SIZE_OF_SHA1_DIGEST => Ok( |
| 86 | #[allow(unsafe_code)] | 91 | #[allow(unsafe_code)] |
| 87 | unsafe { | 92 | unsafe { |
| @@ -178,6 +183,7 @@ impl oid { · 90 unmodified lines | |||
| 178 | #[doc(alias = "is_zero", alias = "git2")] | 183 | #[doc(alias = "is_zero", alias = "git2")] |
| 179 | pub fn is_null(&self) -> bool { | 184 | pub fn is_null(&self) -> bool { |
| 180 | match self.kind() { | 185 | match self.kind() { |
| 186 | #[cfg(feature = "sha1")] | ||
| 181 | Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(), | 187 | Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(), |
| 182 | #[cfg(feature = "sha256")] | 188 | #[cfg(feature = "sha256")] |
| 183 | Kind::Sha256 => &self.bytes == oid::null_sha256().as_bytes(), | 189 | Kind::Sha256 => &self.bytes == oid::null_sha256().as_bytes(), |
| @@ -188,6 +194,7 @@ impl oid { · 4 unmodified lines | |||
| 188 | #[inline] | 194 | #[inline] |
| 189 | pub fn is_empty_blob(&self) -> bool { | 195 | pub fn is_empty_blob(&self) -> bool { |
| 190 | match self.kind() { | 196 | match self.kind() { |
| 197 | #[cfg(feature = "sha1")] | ||
| 191 | Kind::Sha1 => &self.bytes == oid::empty_blob_sha1().as_bytes(), | 198 | Kind::Sha1 => &self.bytes == oid::empty_blob_sha1().as_bytes(), |
| 192 | #[cfg(feature = "sha256")] | 199 | #[cfg(feature = "sha256")] |
| 193 | Kind::Sha256 => &self.bytes == oid::empty_blob_sha256().as_bytes(), | 200 | Kind::Sha256 => &self.bytes == oid::empty_blob_sha256().as_bytes(), |
| @@ -198,6 +205,7 @@ impl oid { · 4 unmodified lines | |||
| 198 | #[inline] | 205 | #[inline] |
| 199 | pub fn is_empty_tree(&self) -> bool { | 206 | pub fn is_empty_tree(&self) -> bool { |
| 200 | match self.kind() { | 207 | match self.kind() { |
| 208 | #[cfg(feature = "sha1")] | ||
| 201 | Kind::Sha1 => &self.bytes == oid::empty_tree_sha1().as_bytes(), | 209 | Kind::Sha1 => &self.bytes == oid::empty_tree_sha1().as_bytes(), |
| 202 | #[cfg(feature = "sha256")] | 210 | #[cfg(feature = "sha256")] |
| 203 | Kind::Sha256 => &self.bytes == oid::empty_tree_sha256().as_bytes(), | 211 | Kind::Sha256 => &self.bytes == oid::empty_tree_sha256().as_bytes(), |
| @@ -209,6 +217,7 @@ impl oid { · 5 unmodified lines | |||
| 209 | impl oid { | 217 | impl oid { |
| 210 | /// Returns a SHA1 digest with all bytes being initialized to zero. | 218 | /// Returns a SHA1 digest with all bytes being initialized to zero. |
| 211 | #[inline] | 219 | #[inline] |
| 220 | #[cfg(feature = "sha1")] | ||
| 212 | pub(crate) fn null_sha1() -> &'static Self { | 221 | pub(crate) fn null_sha1() -> &'static Self { |
| 213 | oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref()) | 222 | oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref()) |
| 214 | } | 223 | } |
| @@ -222,6 +231,7 @@ impl oid { · 7 unmodified lines | |||
| 222 | 231 | ||
| 223 | /// Returns an `oid` representing the SHA1 hash of an empty blob. | 232 | /// Returns an `oid` representing the SHA1 hash of an empty blob. |
| 224 | #[inline] | 233 | #[inline] |
| 234 | #[cfg(feature = "sha1")] | ||
| 225 | pub(crate) fn empty_blob_sha1() -> &'static Self { | 235 | pub(crate) fn empty_blob_sha1() -> &'static Self { |
| 226 | oid::from_bytes(EMPTY_BLOB_SHA1) | 236 | oid::from_bytes(EMPTY_BLOB_SHA1) |
| 227 | } | 237 | } |
| @@ -235,6 +245,7 @@ impl oid { · 7 unmodified lines | |||
| 235 | 245 | ||
| 236 | /// Returns an `oid` representing the SHA1 hash of an empty tree. | 246 | /// Returns an `oid` representing the SHA1 hash of an empty tree. |
| 237 | #[inline] | 247 | #[inline] |
| 248 | #[cfg(feature = "sha1")] | ||
| 238 | pub(crate) fn empty_tree_sha1() -> &'static Self { | 249 | pub(crate) fn empty_tree_sha1() -> &'static Self { |
| 239 | oid::from_bytes(EMPTY_TREE_SHA1) | 250 | oid::from_bytes(EMPTY_TREE_SHA1) |
| 240 | } | 251 | } |
| @@ -266,6 +277,7 @@ impl ToOwned for oid { · 25 unmodified lines | |||
| 266 | 277 | ||
| 267 | fn to_owned(&self) -> Self::Owned { | 278 | fn to_owned(&self) -> Self::Owned { |
| 268 | match self.kind() { | 279 | match self.kind() { |
| 280 | #[cfg(feature = "sha1")] | ||
| 269 | Kind::Sha1 => ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")), | 281 | Kind::Sha1 => ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")), |
| 270 | #[cfg(feature = "sha256")] | 282 | #[cfg(feature = "sha256")] |
| 271 | Kind::Sha256 => ObjectId::Sha256(self.bytes.try_into().expect("no bug in hash detection")), | 283 | Kind::Sha256 => ObjectId::Sha256(self.bytes.try_into().expect("no bug in hash detection")), |
| @@ -273,12 +285,20 @@ impl ToOwned for oid { · 1 unmodified line | |||
| 273 | } | 285 | } |
| 274 | } | 286 | } |
| 275 | 287 | ||
| 288 | #[cfg(feature = "sha1")] | ||
| 276 | impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid { | 289 | impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid { |
| 277 | fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self { | 290 | fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self { |
| 278 | oid::from_bytes(v.as_ref()) | 291 | oid::from_bytes(v.as_ref()) |
| 279 | } | 292 | } |
| 280 | } | 293 | } |
| 281 | 294 | ||
| 295 | #[cfg(feature = "sha256")] | ||
| 296 | impl<'a> From<&'a [u8; SIZE_OF_SHA256_DIGEST]> for &'a oid { | ||
| 297 | fn from(v: &'a [u8; SIZE_OF_SHA256_DIGEST]) -> Self { | ||
| 298 | oid::from_bytes(v.as_ref()) | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 282 | impl std::fmt::Display for &oid { | 302 | impl std::fmt::Display for &oid { |
| 283 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 303 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 284 | let mut buf = Kind::hex_buf(); | 304 | let mut buf = Kind::hex_buf(); |
| @@ -90,7 +90,6 @@ fn longest_sha1() { · 89 unmodified lines | |||
| 90 | assert_eq!(longest, Kind::Sha1); | 90 | assert_eq!(longest, Kind::Sha1); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | // TODO: make sure to test all permutations once `sha1` is fully optional | ||
| 94 | #[test] | 93 | #[test] |
| 95 | #[cfg(all(not(feature = "sha1"), feature = "sha256"))] | 94 | #[cfg(all(not(feature = "sha1"), feature = "sha256"))] |
| 96 | fn longest_sha256() { | 95 | fn longest_sha256() { |
| @@ -71,6 +71,7 @@ check: · 70 unmodified lines | |||
| 71 | cargo check -p gix-hash --all-features | 71 | cargo check -p gix-hash --all-features |
| 72 | cargo check -p gix-hash | 72 | cargo check -p gix-hash |
| 73 | cargo check -p gix-hash --features sha256 | 73 | cargo check -p gix-hash --features sha256 |
| 74 | cargo check -p gix-hash --no-default-features --features sha256 | ||
| 74 | cargo check -p gix-object --all-features | 75 | cargo check -p gix-object --all-features |
| 75 | cargo check -p gix-object --features verbose-object-parsing-errors | 76 | cargo check -p gix-object --features verbose-object-parsing-errors |
| 76 | cargo check -p gix-attributes --features serde | 77 | cargo check -p gix-attributes --features serde |
| @@ -158,6 +159,7 @@ unit-tests: · 81 unmodified lines | |||
| 158 | cargo nextest run -p gix-worktree-tests --features gix-features-parallel --no-fail-fast | 159 | cargo nextest run -p gix-worktree-tests --features gix-features-parallel --no-fail-fast |
| 159 | cargo nextest run -p gix-hash --no-fail-fast | 160 | cargo nextest run -p gix-hash --no-fail-fast |
| 160 | cargo nextest run -p gix-hash --features sha256 --no-fail-fast | 161 | cargo nextest run -p gix-hash --features sha256 --no-fail-fast |
| 162 | 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. | ||
| 161 | cargo nextest run -p gix-object --no-fail-fast | 163 | cargo nextest run -p gix-object --no-fail-fast |
| 162 | cargo nextest run -p gix-object --features verbose-object-parsing-errors --no-fail-fast | 164 | cargo nextest run -p gix-object --features verbose-object-parsing-errors --no-fail-fast |
| 163 | cargo nextest run -p gix-tempfile --features signals --no-fail-fast | 165 | cargo nextest run -p gix-tempfile --features signals --no-fail-fast |