Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions gix-hash/src/change_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,29 @@ impl std::fmt::Display for ChangeId {
}
}

impl ChangeId {
fn eq_str(&self, other: &str) -> bool {
self.to_reverse_hex().eq_str(other)
}
}

impl_partial_eq_str!(ChangeId);

impl ReverseHexDisplay<'_> {
pub(crate) fn new(inner: &oid, hex_len: usize) -> ReverseHexDisplay<'_> {
ReverseHexDisplay { inner, hex_len }
}

fn eq_str(&self, other: &str) -> bool {
let mut buf = Kind::hex_buf();
let reverse_hex = encode_reverse_hex(self.inner, &mut buf);
reverse_hex[..self.hex_len.min(reverse_hex.len())] == *other
}
}

// Keep this directional as truncated displays aren't uniquely identified by their text.
impl_partial_eq_str_one_way!(ReverseHexDisplay<'_>);

impl std::fmt::Display for ReverseHexDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = Kind::hex_buf();
Expand Down
46 changes: 46 additions & 0 deletions gix-hash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,52 @@
#[cfg(all(not(feature = "sha1"), not(feature = "sha256")))]
compile_error!("Please set either the `sha1` or the `sha256` feature flag");

macro_rules! impl_partial_eq_str_one_way {
($type:ty) => {
impl PartialEq<str> for $type {
fn eq(&self, other: &str) -> bool {
self.eq_str(other)
}
}

impl PartialEq<&str> for $type {
fn eq(&self, other: &&str) -> bool {
self.eq_str(other)
}
}

impl PartialEq<String> for $type {
fn eq(&self, other: &String) -> bool {
self.eq_str(other)
}
}
};
}

macro_rules! impl_partial_eq_str {
($type:ty) => {
impl_partial_eq_str_one_way!($type);

impl PartialEq<$type> for str {
fn eq(&self, other: &$type) -> bool {
other.eq_str(self)
}
}

impl PartialEq<$type> for &str {
fn eq(&self, other: &$type) -> bool {
other.eq_str(self)
}
}

impl PartialEq<$type> for String {
fn eq(&self, other: &$type) -> bool {
other.eq_str(self)
}
}
};
}

#[path = "oid.rs"]
mod borrowed;
pub use borrowed::{Error, oid};
Expand Down
8 changes: 8 additions & 0 deletions gix-hash/src/object_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ impl std::fmt::Display for ObjectId {
}
}

impl ObjectId {
fn eq_str(&self, other: &str) -> bool {
self.as_ref().eq_str(other)
}
}

impl_partial_eq_str!(ObjectId);

impl PartialEq<&oid> for ObjectId {
fn eq(&self, other: &&oid) -> bool {
self.as_ref() == *other
Expand Down
29 changes: 29 additions & 0 deletions gix-hash/src/oid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ impl std::fmt::Display for HexDisplay<'_> {
}
}

impl HexDisplay<'_> {
pub(crate) fn eq_str(&self, other: &str) -> bool {
let mut hex = Kind::hex_buf();
let hex = self.inner.hex_to_buf(hex.as_mut());
hex[..self.hex_len.min(hex.len())] == *other
}
}

// Keep this directional as truncated displays aren't uniquely identified by their text.
impl_partial_eq_str_one_way!(HexDisplay<'_>);

impl std::fmt::Debug for oid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
Expand Down Expand Up @@ -177,6 +188,10 @@ impl oid {
out.write_all(&hex[..hex_len])
}

pub(crate) fn eq_str(&self, other: &str) -> bool {
self.to_hex().eq_str(other)
}

/// Returns `true` if this hash consists of all null bytes.
#[inline]
#[doc(alias = "is_zero", alias = "git2")]
Expand Down Expand Up @@ -311,6 +326,20 @@ impl PartialEq<ObjectId> for &oid {
}
}

impl PartialEq<String> for &oid {
fn eq(&self, other: &String) -> bool {
self.eq_str(other)
}
}

impl PartialEq<&oid> for String {
fn eq(&self, other: &&oid) -> bool {
other.eq_str(self)
}
}

impl_partial_eq_str!(oid);

/// 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.
Expand Down
9 changes: 9 additions & 0 deletions gix-hash/src/prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ impl std::fmt::Display for Prefix {
}
}

impl Prefix {
fn eq_str(&self, other: &str) -> bool {
self.bytes.to_hex_with_len(self.hex_len).eq_str(other)
}
}

// Keep this directional as the hash kind and unused suffix aren't uniquely identified by the displayed prefix.
impl_partial_eq_str_one_way!(Prefix);

impl From<ObjectId> for Prefix {
fn from(oid: ObjectId) -> Self {
Prefix {
Expand Down
121 changes: 121 additions & 0 deletions gix-hash/tests/hash/comparisons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use gix_hash::{ChangeId, Prefix};

use crate::hex_to_id;

macro_rules! assert_text_eq {
($value:expr, $text:expr) => {{
let value = $value;
let text = $text;
let owned = text.to_owned();
assert!(value == text, "the value compares with a string literal");
assert!(text == value, "string-literal comparison is symmetric");
assert!(&value == text, "a borrowed value compares with str");
assert!(text == &value, "str comparison with a borrowed value is symmetric");
assert!(value == owned, "the value compares with String");
assert!(owned == value, "String comparison is symmetric");
}};
}

macro_rules! assert_text_ne {
($value:expr, $text:expr) => {{
let value = $value;
let text = $text;
assert!(value != text, "different text does not compare equal");
assert!(text != value, "inequality is symmetric");
}};
}

macro_rules! assert_text_eq_one_way {
($value:expr, $text:expr) => {{
let value = $value;
let text = $text;
let owned = text.to_owned();
assert!(value == text, "the value compares with a string literal");
assert!(&value == text, "a borrowed value compares with str");
assert!(value == owned, "the value compares with String");
}};
}

macro_rules! assert_text_ne_one_way {
($value:expr, $text:expr) => {{
let value = $value;
let text = $text;
assert!(value != text, "different text does not match the value");
}};
}

fn compare_all(object_hex: &str, reverse_hex: &str) {
let id = hex_to_id(object_hex);
let object_hex_upper = object_hex.to_ascii_uppercase();
assert_text_eq!(id, object_hex);
assert_text_ne!(id, object_hex_upper.as_str());
assert_text_ne!(id, &object_hex[..object_hex.len() - 1]);
let invalid_object_hex = format!("g{}", &object_hex[1..]);
assert_text_ne!(id, invalid_object_hex.as_str());

let borrowed = id.as_ref();
assert!(borrowed == object_hex, "oid compares with str");
assert!(object_hex == borrowed, "str comparison with oid is symmetric");
assert!(
borrowed != object_hex_upper,
"oid only matches canonical lowercase text"
);
assert!(object_hex_upper != borrowed, "non-canonical comparison is symmetric");
assert!(
borrowed != &object_hex[..object_hex.len() - 1],
"oid requires the exact length"
);
assert!(borrowed != invalid_object_hex.as_str(), "oid rejects invalid hex");

let change_id = ChangeId::from(id);
let reverse_hex_upper = reverse_hex.to_ascii_uppercase();
assert_text_eq!(change_id, reverse_hex);
assert_text_ne!(change_id, reverse_hex_upper.as_str());
assert_text_ne!(change_id, &reverse_hex[..reverse_hex.len() - 1]);
let invalid_reverse_hex = format!("j{}", &reverse_hex[1..]);
assert_text_ne!(change_id, invalid_reverse_hex.as_str());

let prefix_len = 17;
let prefix = Prefix::new(&id, prefix_len).expect("the requested prefix length is valid");
let object_prefix = &object_hex[..prefix_len];
let object_prefix_upper = object_prefix.to_ascii_uppercase();
assert_text_eq_one_way!(prefix, object_prefix);
assert_text_ne_one_way!(prefix, object_prefix_upper.as_str());
assert_text_ne_one_way!(prefix, &object_hex[..prefix_len + 1]);
assert_text_ne_one_way!(prefix, "abcdefg");

assert_text_eq_one_way!(id.to_hex_with_len(prefix_len), object_prefix);
assert_text_ne_one_way!(id.to_hex_with_len(prefix_len), object_prefix_upper.as_str());
assert_text_ne_one_way!(id.to_hex_with_len(prefix_len), &object_hex[..prefix_len + 1]);
assert_text_ne_one_way!(id.to_hex_with_len(prefix_len), "abcdefg");

let reverse_prefix = &reverse_hex[..prefix_len];
let reverse_prefix_upper = reverse_prefix.to_ascii_uppercase();
assert_text_eq_one_way!(change_id.to_reverse_hex_with_len(prefix_len), reverse_prefix);
assert_text_ne_one_way!(
change_id.to_reverse_hex_with_len(prefix_len),
reverse_prefix_upper.as_str()
);
assert_text_ne_one_way!(
change_id.to_reverse_hex_with_len(prefix_len),
&reverse_hex[..prefix_len + 1]
);
assert_text_ne_one_way!(change_id.to_reverse_hex_with_len(prefix_len), "abcdefg");
}

#[test]
fn compares_sha1_with_text() {
compare_all(
"0123456789abcdef0123456789abcdef01234567",
"zyxwvutsrqponmlkzyxwvutsrqponmlkzyxwvuts",
);
}

#[test]
#[cfg(feature = "sha256")]
fn compares_sha256_with_text() {
compare_all(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"zyxwvutsrqponmlkzyxwvutsrqponmlkzyxwvutsrqponmlkzyxwvutsrqponmlk",
);
}
1 change: 1 addition & 0 deletions gix-hash/tests/hash/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use gix_hash::ObjectId;

mod change_id;
mod comparisons;
mod hasher;
mod kind;
mod object_id;
Expand Down
2 changes: 1 addition & 1 deletion gix-merge/tests/merge/blob/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ theirs
})
.unwrap()
.unwrap(),
hex_to_id("424860eef4edb9f5a2dacbbd6dc8c2d2e7645035"),
"424860eef4edb9f5a2dacbbd6dc8c2d2e7645035",
"there is no need to write a buffer here, it just returns one of our inputs"
);

Expand Down
4 changes: 2 additions & 2 deletions gix-object/tests/object/commit/from_bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use smallvec::SmallVec;

use crate::{
commit::{LONG_MESSAGE, MERGE_TAG, SIGNATURE},
fixture_name, fixture_oid, hex_to_id, linus_signature,
fixture_name, fixture_oid, linus_signature,
};

#[test]
Expand Down Expand Up @@ -104,7 +104,7 @@ committer Name <name@example.com> 1312735823 +0518
message";
let commit = CommitRef::from_bytes(input, gix_hash::Kind::Sha1)?;
assert_eq!(commit.tree, b"7989DFB2EC2F41914611A22FB30BBC2B3849DF9A".as_bstr());
assert_eq!(commit.tree(), hex_to_id("7989dfb2ec2f41914611a22fb30bbc2b3849df9a"));
assert_eq!(commit.tree(), "7989dfb2ec2f41914611a22fb30bbc2b3849df9a");
Ok(())
}

Expand Down
6 changes: 3 additions & 3 deletions gix-object/tests/object/commit/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ fn signed_singleline() -> crate::Result {
CommitRefIter::from_bytes(&fixture_name("commit", "signed-singleline.txt"), gix_hash::Kind::Sha1)
.parent_ids()
.collect::<Vec<_>>(),
vec![hex_to_id("09d8d3a12e161a7f6afb522dbe8900a9c09bce06")]
vec!["09d8d3a12e161a7f6afb522dbe8900a9c09bce06"]
);
Ok(())
}
Expand Down Expand Up @@ -166,8 +166,8 @@ fn mergetag() -> crate::Result {
assert_eq!(
iter.parent_ids().collect::<Vec<_>>(),
vec![
hex_to_id("44ebe016df3aad96e3be8f95ec52397728dd7701"),
hex_to_id("8d485da0ddee79d0e6713405694253d401e41b93")
"44ebe016df3aad96e3be8f95ec52397728dd7701",
"8d485da0ddee79d0e6713405694253d401e41b93"
]
);
assert_eq!(iter.message().ok(), Some(LONG_MESSAGE.into()));
Expand Down
4 changes: 2 additions & 2 deletions gix-object/tests/object/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ mod method {
use gix_object::CommitRef;
use pretty_assertions::assert_eq;

use crate::{fixture_name, hex_to_id, signature};
use crate::{fixture_name, signature};

#[test]
fn tree() -> crate::Result {
let fixture = fixture_name("commit", "unsigned.txt");
let commit = CommitRef::from_bytes(&fixture, gix_hash::Kind::Sha1)?;
assert_eq!(commit.tree(), hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d"));
assert_eq!(commit.tree(), "1b2dfb4ac5e42080b682fc676e9738c94ce6d54d");
assert_eq!(commit.tree, "1b2dfb4ac5e42080b682fc676e9738c94ce6d54d");
Ok(())
}
Expand Down
11 changes: 4 additions & 7 deletions gix-object/tests/object/tag/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{hex_to_id, signature};
use crate::signature;
use gix_object::{Kind, TagRef, TagRefIter, bstr::ByteSlice};

use crate::fixture_name;
Expand Down Expand Up @@ -189,7 +189,7 @@ sha256-tag-signature
fn target() -> crate::Result {
let fixture = fixture_name("tag", "signed.txt");
let tag_ref = TagRef::from_bytes(&fixture, gix_hash::Kind::Sha1)?;
assert_eq!(tag_ref.target(), hex_to_id("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec"));
assert_eq!(tag_ref.target(), "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec");
assert_eq!(tag_ref.target, "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bytes());

let gix_object::Tag {
Expand All @@ -200,7 +200,7 @@ fn target() -> crate::Result {
message,
signature,
} = tag_ref.into_owned()?;
assert_eq!(target.to_string(), tag_ref.target);
assert_eq!(target, tag_ref.target.to_str()?);
assert_eq!(target_kind, tag_ref.target_kind);
assert_eq!(name, tag_ref.name);
let expected_tagger = tag_ref.tagger()?.map(Into::into);
Expand Down Expand Up @@ -362,10 +362,7 @@ tag uppercase-target
message";
let tag = TagRef::from_bytes(input, gix_hash::Kind::Sha1)?;
assert_eq!(tag.target, b"FFA700B4ACA13B80CB6B98A078E7C96804F8E0EC".as_bstr());
assert_eq!(
tag.target(),
crate::hex_to_id("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec")
);
assert_eq!(tag.target(), "ffa700b4aca13b80cb6b98a078e7c96804f8e0ec");
Ok(())
}

Expand Down
Loading
Loading