gix-hash-0.13.3/.cargo_vcs_info.json0000644000000001460000000000100126310ustar { "git": { "sha1": "98b08f4d0d9237be0e0c2caa9bf5c13ae8bbf9d8" }, "path_in_vcs": "gix-hash" }gix-hash-0.13.3/Cargo.toml0000644000000023450000000000100106320ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "gix-hash" version = "0.13.3" authors = ["Sebastian Thiel "] include = [ "src/**/*", "LICENSE-*", ] description = "Borrowed and owned git hash digests used to identify git objects" license = "MIT OR Apache-2.0" repository = "https://github.com/Byron/gitoxide" [package.metadata.docs.rs] all-features = true features = ["document-features"] [lib] test = false doctest = false [dependencies.document-features] version = "0.2.0" optional = true [dependencies.faster-hex] version = "0.9.0" [dependencies.serde] version = "1.0.114" features = ["derive"] optional = true default-features = false [dependencies.thiserror] version = "1.0.33" [dev-dependencies] [features] serde = ["dep:serde"] gix-hash-0.13.3/Cargo.toml.orig000064400000000000000000000016361046102023000143150ustar 00000000000000[package] name = "gix-hash" version = "0.13.3" description = "Borrowed and owned git hash digests used to identify git objects" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" license = "MIT OR Apache-2.0" edition = "2021" include = ["src/**/*", "LICENSE-*"] rust-version = "1.65" [lib] doctest = false test = false [features] ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde= ["dep:serde"] [dependencies] thiserror = "1.0.33" faster-hex = { workspace = true } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } document-features = { version = "0.2.0", optional = true } [dev-dependencies] gix-testtools = { path = "../tests/tools"} gix-features = { path = "../gix-features", features = ["rustsha1"] } [package.metadata.docs.rs] all-features = true features = ["document-features"] gix-hash-0.13.3/LICENSE-APACHE000064400000000000000000000251221046102023000133460ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gix-hash-0.13.3/LICENSE-MIT000064400000000000000000000021551046102023000130570ustar 00000000000000Copyright (c) 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-hash-0.13.3/src/kind.rs000064400000000000000000000066401046102023000135100ustar 00000000000000use std::{convert::TryFrom, str::FromStr}; use crate::{oid, Kind, ObjectId}; impl TryFrom for Kind { type Error = u8; fn try_from(value: u8) -> Result { Ok(match value { 1 => Kind::Sha1, unknown => return Err(unknown), }) } } impl FromStr for Kind { type Err = String; fn from_str(s: &str) -> Result { Ok(match s { "sha1" | "SHA1" => Kind::Sha1, other => return Err(other.into()), }) } } impl std::fmt::Display for Kind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Kind::Sha1 => f.write_str("SHA1"), } } } impl Kind { /// Returns the shortest hash we support. #[inline] pub const fn shortest() -> Self { Self::Sha1 } /// Returns the longest hash we support. #[inline] pub const fn longest() -> Self { Self::Sha1 } /// Returns a buffer suitable to hold the longest possible hash in hex. #[inline] pub const fn hex_buf() -> [u8; Kind::longest().len_in_hex()] { [0u8; Kind::longest().len_in_hex()] } /// Returns a buffer suitable to hold the longest possible hash as raw bytes. #[inline] pub const fn buf() -> [u8; Kind::longest().len_in_bytes()] { [0u8; Kind::longest().len_in_bytes()] } /// Returns the amount of bytes needed to encode this instance as hexadecimal characters. #[inline] pub const fn len_in_hex(&self) -> usize { match self { Kind::Sha1 => 40, } } /// Returns the amount of bytes taken up by the hash of this instance. #[inline] pub const fn len_in_bytes(&self) -> usize { match self { Kind::Sha1 => 20, } } /// Returns the kind of hash that would fit the given `hex_len`, or `None` if there is no fitting hash. /// Note that `0` as `hex_len` up to 40 always yields `Sha1`. #[inline] pub const fn from_hex_len(hex_len: usize) -> Option { Some(match hex_len { 0..=40 => Kind::Sha1, _ => return None, }) } /// Converts a size in bytes as obtained by `Kind::len_in_bytes()` into the corresponding hash kind, if possible. /// /// **Panics** if the hash length doesn't match a known hash. /// /// NOTE that this method isn't public as it shouldn't be encouraged to assume all hashes have the same length. /// However, if there should be such a thing, our `oid` implementation will have to become an enum and it's pretty breaking /// to the way it's currently being used as auto-dereffing doesn't work anymore. Let's hope it won't happen. // TODO: make 'const' once Rust 1.57 is more readily available in projects using 'gitoxide'. #[inline] pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { match bytes { 20 => Kind::Sha1, _ => panic!("BUG: must be called only with valid hash lengths produced by len_in_bytes()"), } } /// Create a shared null-id of our hash kind. #[inline] pub fn null_ref(&self) -> &'static oid { match self { Kind::Sha1 => oid::null_sha1(), } } /// Create an owned null-id of our hash kind. #[inline] pub const fn null(&self) -> ObjectId { match self { Kind::Sha1 => ObjectId::null_sha1(), } } } gix-hash-0.13.3/src/lib.rs000064400000000000000000000026641046102023000133330ustar 00000000000000//! This crate provides types for identifying git objects using a hash digest. //! //! These are provided in [borrowed versions][oid] as well as an [owned one][ObjectId]. //! ## Feature Flags #![cfg_attr( all(doc, feature = "document-features"), doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms, unsafe_code)] #[path = "oid.rs"] mod borrowed; pub use borrowed::oid; mod object_id; pub use object_id::{decode, ObjectId}; /// pub mod prefix; /// An partial, owned hash possibly identifying an object uniquely, whose non-prefix bytes are zeroed. /// /// An example would `0000000000000000000000000000000032bd3242`, where `32bd3242` is the prefix, /// which would be able to match all hashes that *start with* `32bd3242`. #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Prefix { bytes: ObjectId, hex_len: usize, } /// The size of a SHA1 hash digest in bytes. const SIZE_OF_SHA1_DIGEST: usize = 20; /// Denotes the kind of function to produce a [`ObjectId`]. #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Kind { /// The Sha1 hash with 160 bits. #[default] Sha1 = 1, } mod kind; gix-hash-0.13.3/src/object_id.rs000064400000000000000000000155121046102023000145030ustar 00000000000000use std::{ borrow::Borrow, convert::TryInto, hash::{Hash, Hasher}, ops::Deref, }; use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; /// An owned hash identifying objects, most commonly `Sha1` #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ObjectId { /// A SHA 1 hash digest Sha1([u8; SIZE_OF_SHA1_DIGEST]), } // False positive: https://github.com/rust-lang/rust-clippy/issues/2627 // ignoring some fields while hashing is perfectly valid and just leads to // increased HashCollisions. One Sha1 being a prefix of another Sha256 is // extremely unlikely to begin with so it doesn't matter. // This implementation matches the `Hash` implementation for `oid` // and allows the usage of custom Hashers that only copy a truncated ShaHash #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for ObjectId { fn hash(&self, state: &mut H) { state.write(self.as_slice()) } } #[allow(missing_docs)] pub mod decode { use std::str::FromStr; use crate::object_id::ObjectId; /// An error returned by [`ObjectId::from_hex()`][crate::ObjectId::from_hex()] #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("A hash sized {0} hexadecimal characters is invalid")] InvalidHexEncodingLength(usize), #[error("Invalid character encountered")] Invalid, } /// Hash decoding impl ObjectId { /// Create an instance from a `buffer` of 40 bytes encoded with hexadecimal notation. /// /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] pub fn from_hex(buffer: &[u8]) -> Result { match buffer.len() { 40 => Ok({ ObjectId::Sha1({ let mut buf = [0; 20]; faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err { faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid, faster_hex::Error::InvalidLength(_) => { unreachable!("BUG: This is already checked") } })?; buf }) }), len => Err(Error::InvalidHexEncodingLength(len)), } } } impl FromStr for ObjectId { type Err = Error; fn from_str(s: &str) -> Result { Self::from_hex(s.as_bytes()) } } } /// Access and conversion impl ObjectId { /// Returns the kind of hash used in this instance. #[inline] pub fn kind(&self) -> Kind { match self { ObjectId::Sha1(_) => Kind::Sha1, } } /// Return the raw byte slice representing this hash. #[inline] pub fn as_slice(&self) -> &[u8] { match self { Self::Sha1(b) => b.as_ref(), } } /// Return the raw mutable byte slice representing this hash. #[inline] pub fn as_mut_slice(&mut self) -> &mut [u8] { match self { Self::Sha1(b) => b.as_mut(), } } /// The hash of an empty blob. #[inline] pub const fn empty_blob(hash: Kind) -> ObjectId { match hash { Kind::Sha1 => { ObjectId::Sha1(*b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91") } } } /// The hash of an empty tree. #[inline] pub const fn empty_tree(hash: Kind) -> ObjectId { match hash { Kind::Sha1 => { ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04") } } } /// 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(), } } /// 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(), } } /// Returns `true` if this hash is equal to an empty blob. #[inline] pub fn is_empty_blob(&self) -> bool { self == &Self::empty_blob(self.kind()) } /// Returns `true` if this hash is equal to an empty tree. #[inline] pub fn is_empty_tree(&self) -> bool { self == &Self::empty_tree(self.kind()) } } /// Sha1 hash specific methods impl ObjectId { /// Instantiate an Digest from 20 bytes of a Sha1 digest. #[inline] fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { ObjectId::Sha1(id) } /// Instantiate an Digest from a slice 20 borrowed bytes of a Sha1 digest. /// /// Panics of the slice doesn't have a length of 20. #[inline] pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId { let mut id = [0; SIZE_OF_SHA1_DIGEST]; id.copy_from_slice(b); ObjectId::Sha1(id) } /// Returns an Digest representing a Sha1 with whose memory is zeroed. #[inline] pub(crate) const fn null_sha1() -> ObjectId { ObjectId::Sha1([0u8; 20]) } } impl std::fmt::Debug for ObjectId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, } for b in self.as_bytes() { write!(f, "{b:02x}")?; } f.write_str(")") } } impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { fn from(v: [u8; 20]) -> Self { Self::new_sha1(v) } } impl From<&[u8]> for ObjectId { fn from(v: &[u8]) -> Self { match v.len() { 20 => Self::Sha1(v.try_into().expect("prior length validation")), other => panic!("BUG: unsupported hash len: {other}"), } } } impl From<&oid> for ObjectId { fn from(v: &oid) -> Self { match v.kind() { Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()), } } } impl Deref for ObjectId { type Target = oid; fn deref(&self) -> &Self::Target { self.as_ref() } } impl AsRef for ObjectId { fn as_ref(&self) -> &oid { oid::from_bytes_unchecked(self.as_slice()) } } impl Borrow for ObjectId { fn borrow(&self) -> &oid { self.as_ref() } } impl std::fmt::Display for ObjectId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_hex()) } } impl PartialEq<&oid> for ObjectId { fn eq(&self, other: &&oid) -> bool { self.as_ref() == *other } } gix-hash-0.13.3/src/oid.rs000064400000000000000000000220241046102023000133300ustar 00000000000000use std::{convert::TryInto, fmt, hash}; use crate::{Kind, ObjectId, SIZE_OF_SHA1_DIGEST}; /// A borrowed reference to a hash identifying objects. /// /// # Future Proofing /// /// In case we wish to support multiple hashes with the same length we cannot discriminate /// using the slice length anymore. To make that work, we will use the high bits of the /// internal `bytes` slice length (a fat pointer, pointing to data and its length in bytes) /// to encode additional information. Before accessing or returning the bytes, a new adjusted /// slice will be constructed, while the high bits will be used to help resolving the /// hash [`kind()`][oid::kind()]. /// We expect to have quite a few bits available for such 'conflict resolution' as most hashes aren't longer /// than 64 bytes. #[derive(PartialEq, Eq, Ord, PartialOrd)] #[repr(transparent)] #[allow(non_camel_case_types)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct oid { bytes: [u8], } // False positive: // Using an automatic implementation of `Hash` for `oid` would lead to // it attempting to hash the length of the slice first. On 32 bit systems // this can lead to issues with the custom `gix_hashtable` `Hasher` implementation, // and it currently ends up being discarded there anyway. #[allow(clippy::derived_hash_with_manual_eq)] impl hash::Hash for oid { fn hash(&self, state: &mut H) { state.write(self.as_bytes()) } } /// A utility able to format itself with the given amount of characters in hex. #[derive(PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct HexDisplay<'a> { inner: &'a oid, hex_len: usize, } impl<'a> fmt::Display for HexDisplay<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut hex = crate::Kind::hex_buf(); let max_len = self.inner.hex_to_buf(hex.as_mut()); let hex = std::str::from_utf8(&hex[..self.hex_len.min(max_len)]).expect("ascii only in hex"); f.write_str(hex) } } impl fmt::Debug for oid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}({})", match self.kind() { crate::Kind::Sha1 => "Sha1", }, self.to_hex(), ) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Cannot instantiate git hash from a digest of length {0}")] InvalidByteSliceLength(usize), } /// Conversion impl oid { /// Try to create a shared object id from a slice of bytes representing a hash `digest` #[inline] pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> { match digest.len() { 20 => Ok( #[allow(unsafe_code)] unsafe { &*(digest as *const [u8] as *const oid) }, ), len => Err(Error::InvalidByteSliceLength(len)), } } /// Create an OID from the input `value` slice without performing any safety check. /// Use only once sure that `value` is a hash of valid length. pub fn from_bytes_unchecked(value: &[u8]) -> &Self { Self::from_bytes(value) } /// Only from code that statically assures correct sizes using array conversions. pub(crate) fn from_bytes(value: &[u8]) -> &Self { #[allow(unsafe_code)] unsafe { &*(value as *const [u8] as *const oid) } } } /// Access impl oid { /// The kind of hash used for this instance. #[inline] pub fn kind(&self) -> crate::Kind { crate::Kind::from_len_in_bytes(self.bytes.len()) } /// The first byte of the hash, commonly used to partition a set of object ids. #[inline] pub fn first_byte(&self) -> u8 { self.bytes[0] } /// Interpret this object id as raw byte slice. #[inline] pub fn as_bytes(&self) -> &[u8] { &self.bytes } /// Return a type which can display itself in hexadecimal form with the `len` amount of characters. #[inline] pub fn to_hex_with_len(&self, len: usize) -> HexDisplay<'_> { HexDisplay { inner: self, hex_len: len, } } /// Return a type which displays this oid as hex in full. #[inline] pub fn to_hex(&self) -> HexDisplay<'_> { HexDisplay { inner: self, hex_len: self.bytes.len() * 2, } } /// 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.kind() { Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(), } } } /// Sha1 specific methods impl oid { /// Write ourselves to the `out` in hexadecimal notation, returning the amount of written bytes. /// /// **Panics** if the buffer isn't big enough to hold twice as many bytes as the current binary size. #[inline] #[must_use] pub fn hex_to_buf(&self, buf: &mut [u8]) -> usize { let num_hex_bytes = self.bytes.len() * 2; faster_hex::hex_encode(&self.bytes, &mut buf[..num_hex_bytes]).expect("to count correctly"); num_hex_bytes } /// Write ourselves to `out` in hexadecimal notation. #[inline] pub fn write_hex_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> { let mut hex = crate::Kind::hex_buf(); let hex_len = self.hex_to_buf(&mut hex); out.write_all(&hex[..hex_len]) } /// Returns a Sha1 digest with all bytes being initialized to zero. #[inline] pub(crate) fn null_sha1() -> &'static Self { oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref()) } } impl AsRef for &oid { fn as_ref(&self) -> &oid { self } } impl ToOwned for oid { type Owned = crate::ObjectId; fn to_owned(&self) -> Self::Owned { match self.kind() { crate::Kind::Sha1 => crate::ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")), } } } impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid { fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self { oid::from_bytes(v.as_ref()) } } impl fmt::Display for &oid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for b in self.as_bytes() { write!(f, "{b:02x}")?; } Ok(()) } } impl PartialEq for &oid { fn eq(&self, other: &ObjectId) -> bool { *self == other.as_ref() } } /// Manually created from a version that uses a slice, and we forcefully try to convert it into a borrowed array of the desired size /// Could be improved by fitting this into serde. /// Unfortunately the `serde::Deserialize` derive wouldn't work for borrowed arrays. #[cfg(feature = "serde")] impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a oid { fn deserialize(deserializer: D) -> Result>::Error> where D: serde::Deserializer<'de>, { struct __Visitor<'de: 'a, 'a> { marker: std::marker::PhantomData<&'a oid>, lifetime: std::marker::PhantomData<&'de ()>, } impl<'de: 'a, 'a> serde::de::Visitor<'de> for __Visitor<'de, 'a> { type Value = &'a oid; fn expecting(&self, __formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Formatter::write_str(__formatter, "tuple struct Digest") } #[inline] fn visit_newtype_struct<__E>(self, __e: __E) -> std::result::Result where __E: serde::Deserializer<'de>, { let __field0: &'a [u8] = match <&'a [u8] as serde::Deserialize>::deserialize(__e) { Ok(__val) => __val, Err(__err) => { return Err(__err); } }; Ok(oid::try_from_bytes(__field0).expect("hash of known length")) } #[inline] fn visit_seq<__A>(self, mut __seq: __A) -> std::result::Result where __A: serde::de::SeqAccess<'de>, { let __field0 = match match serde::de::SeqAccess::next_element::<&'a [u8]>(&mut __seq) { Ok(__val) => __val, Err(__err) => { return Err(__err); } } { Some(__value) => __value, None => { return Err(serde::de::Error::invalid_length( 0usize, &"tuple struct Digest with 1 element", )); } }; Ok(oid::try_from_bytes(__field0).expect("hash of known length")) } } serde::Deserializer::deserialize_newtype_struct( deserializer, "Digest", __Visitor { marker: std::marker::PhantomData::<&'a oid>, lifetime: std::marker::PhantomData, }, ) } } gix-hash-0.13.3/src/prefix.rs000064400000000000000000000131541046102023000140560ustar 00000000000000use std::{cmp::Ordering, convert::TryFrom}; use crate::{oid, ObjectId, Prefix}; /// The error returned by [`Prefix::new()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error( "The minimum hex length of a short object id is {}, got {hex_len}", Prefix::MIN_HEX_LEN )] TooShort { hex_len: usize }, #[error("An object of kind {object_kind} cannot be larger than {} in hex, but {hex_len} was requested", object_kind.len_in_hex())] TooLong { object_kind: crate::Kind, hex_len: usize }, } /// pub mod from_hex { /// The error returned by [`Prefix::from_hex`][super::Prefix::from_hex()]. #[derive(Debug, Eq, PartialEq, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error( "The minimum hex length of a short object id is {}, got {hex_len}", super::Prefix::MIN_HEX_LEN )] TooShort { hex_len: usize }, #[error("An id cannot be larger than {} chars in hex, but {hex_len} was requested", crate::Kind::longest().len_in_hex())] TooLong { hex_len: usize }, #[error("Invalid hex character")] Invalid, } } impl Prefix { /// The smallest allowed prefix length below which chances for collisions are too high even in small repositories. pub const MIN_HEX_LEN: usize = 4; /// Create a new instance by taking a full `id` as input and truncating it to `hex_len`. /// /// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits /// wide, with all other bytes and bits set to zero. pub fn new(id: &oid, hex_len: usize) -> Result { if hex_len > id.kind().len_in_hex() { Err(Error::TooLong { object_kind: id.kind(), hex_len, }) } else if hex_len < Self::MIN_HEX_LEN { Err(Error::TooShort { hex_len }) } else { let mut prefix = ObjectId::null(id.kind()); let b = prefix.as_mut_slice(); let copy_len = (hex_len + 1) / 2; b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]); if hex_len % 2 == 1 { b[hex_len / 2] &= 0xf0; } Ok(Prefix { bytes: prefix, hex_len }) } } /// Returns the prefix as object id. /// /// Note that it may be deceptive to use given that it looks like a full /// object id, even though its post-prefix bytes/bits are set to zero. pub fn as_oid(&self) -> &oid { &self.bytes } /// Return the amount of hexadecimal characters that are set in the prefix. /// /// This gives the prefix a granularity of 4 bits. pub fn hex_len(&self) -> usize { self.hex_len } /// Provided with candidate id which is a full hash, determine how this prefix compares to it, /// only looking at the prefix bytes, ignoring everything behind that. pub fn cmp_oid(&self, candidate: &oid) -> Ordering { let common_len = self.hex_len / 2; self.bytes.as_bytes()[..common_len] .cmp(&candidate.as_bytes()[..common_len]) .then(if self.hex_len % 2 == 1 { let half_byte_idx = self.hex_len / 2; self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0)) } else { Ordering::Equal }) } /// Create an instance from the given hexadecimal prefix `value`, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8. pub fn from_hex(value: &str) -> Result { let hex_len = value.len(); if hex_len > crate::Kind::longest().len_in_hex() { return Err(from_hex::Error::TooLong { hex_len }); } else if hex_len < Self::MIN_HEX_LEN { return Err(from_hex::Error::TooShort { hex_len }); }; let src = if value.len() % 2 == 0 { let mut out = Vec::from_iter(std::iter::repeat(0).take(value.len() / 2)); faster_hex::hex_decode(value.as_bytes(), &mut out).map(move |_| out) } else { // TODO(perf): do without heap allocation here. let mut buf = [0u8; crate::Kind::longest().len_in_hex()]; buf[..value.len()].copy_from_slice(value.as_bytes()); buf[value.len()] = b'0'; let src = &buf[..=value.len()]; let mut out = Vec::from_iter(std::iter::repeat(0).take(src.len() / 2)); faster_hex::hex_decode(src, &mut out).map(move |_| out) } .map_err(|e| match e { faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => from_hex::Error::Invalid, faster_hex::Error::InvalidLength(_) => panic!("This is already checked"), })?; let mut bytes = ObjectId::null(crate::Kind::from_hex_len(value.len()).expect("hex-len is already checked")); let dst = bytes.as_mut_slice(); let copy_len = src.len(); dst[..copy_len].copy_from_slice(&src); Ok(Prefix { bytes, hex_len }) } } /// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` /// with `hex_len()` = 8. impl TryFrom<&str> for Prefix { type Error = from_hex::Error; fn try_from(value: &str) -> Result { Prefix::from_hex(value) } } impl std::fmt::Display for Prefix { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.bytes.to_hex_with_len(self.hex_len).fmt(f) } } impl From for Prefix { fn from(oid: ObjectId) -> Self { Prefix { bytes: oid, hex_len: oid.kind().len_in_hex(), } } }