Skip to main content

gix_hash/
change_id.rs

1use std::{borrow::Borrow, ops::Deref, str::FromStr};
2
3use crate::{ChangeId, Kind, ObjectId, oid};
4
5const REVERSE_HEX: &[u8; 16] = b"zyxwvutsrqponmlk";
6
7impl ChangeId {
8    /// Parse a complete SHA-1 or SHA-256 hash written with reverse-hex alphabet.
9    pub fn from_reverse_hex(buffer: &[u8]) -> Result<Self, crate::decode::Error> {
10        let len = buffer.len();
11        if crate::Kind::from_hex_len(len).is_none_or(|kind| kind.len_in_hex() != len) {
12            return Err(crate::decode::Error::InvalidHexEncodingLength(len));
13        }
14
15        let mut hex = Kind::hex_buf();
16        reverse_hex_to_hex(buffer, &mut hex[..len]).map_err(|()| crate::decode::Error::Invalid)?;
17        ObjectId::from_hex(&hex[..len]).map(ChangeId)
18    }
19
20    /// Return a type which displays this ID in reverse-hex notation.
21    pub fn to_reverse_hex(&self) -> ReverseHexDisplay<'_> {
22        ReverseHexDisplay::new(self, self.kind().len_in_hex())
23    }
24
25    /// Return a type which displays at most `len` characters of this ID in reverse-hex notation.
26    pub fn to_reverse_hex_with_len(&self, len: usize) -> ReverseHexDisplay<'_> {
27        ReverseHexDisplay::new(self, len)
28    }
29}
30
31/// A utility able to format a [`ChangeId`] or [`crate::Prefix`] with a given number of reverse-hex characters.
32#[derive(PartialEq, Eq, Hash, Ord, PartialOrd)]
33pub struct ReverseHexDisplay<'a> {
34    inner: &'a oid,
35    hex_len: usize,
36}
37
38impl FromStr for ChangeId {
39    type Err = crate::decode::Error;
40
41    fn from_str(value: &str) -> Result<Self, Self::Err> {
42        Self::from_reverse_hex(value.as_bytes())
43    }
44}
45
46impl From<ObjectId> for ChangeId {
47    fn from(value: ObjectId) -> Self {
48        ChangeId(value)
49    }
50}
51
52impl From<&oid> for ChangeId {
53    fn from(value: &oid) -> Self {
54        ChangeId(value.into())
55    }
56}
57
58impl From<ChangeId> for ObjectId {
59    fn from(value: ChangeId) -> Self {
60        value.0
61    }
62}
63
64impl Deref for ChangeId {
65    type Target = oid;
66
67    fn deref(&self) -> &Self::Target {
68        self.0.as_ref()
69    }
70}
71
72impl AsRef<oid> for ChangeId {
73    fn as_ref(&self) -> &oid {
74        self
75    }
76}
77
78impl Borrow<oid> for ChangeId {
79    fn borrow(&self) -> &oid {
80        self
81    }
82}
83
84impl std::fmt::Display for ChangeId {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        self.to_reverse_hex().fmt(f)
87    }
88}
89
90impl ChangeId {
91    fn eq_str(&self, other: &str) -> bool {
92        self.to_reverse_hex().eq_str(other)
93    }
94}
95
96impl_partial_eq_str!(ChangeId);
97
98impl ReverseHexDisplay<'_> {
99    pub(crate) fn new(inner: &oid, hex_len: usize) -> ReverseHexDisplay<'_> {
100        ReverseHexDisplay { inner, hex_len }
101    }
102
103    fn eq_str(&self, other: &str) -> bool {
104        let mut buf = Kind::hex_buf();
105        let reverse_hex = encode_reverse_hex(self.inner, &mut buf);
106        reverse_hex[..self.hex_len.min(reverse_hex.len())] == *other
107    }
108}
109
110// Keep this directional as truncated displays aren't uniquely identified by their text.
111impl_partial_eq_str_one_way!(ReverseHexDisplay<'_>);
112
113impl std::fmt::Display for ReverseHexDisplay<'_> {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        let mut buf = Kind::hex_buf();
116        let reverse_hex = encode_reverse_hex(self.inner, &mut buf);
117        f.write_str(&reverse_hex[..self.hex_len.min(reverse_hex.len())])
118    }
119}
120
121pub(crate) fn reverse_hex_to_hex(reverse_hex: &[u8], hex: &mut [u8]) -> Result<(), ()> {
122    for (src, dst) in reverse_hex.iter().zip(hex) {
123        let nibble = match src {
124            b'k'..=b'z' => b'z' - src,
125            b'K'..=b'Z' => b'Z' - src,
126            _ => return Err(()),
127        };
128        *dst = b"0123456789abcdef"[usize::from(nibble)];
129    }
130    Ok(())
131}
132
133fn encode_reverse_hex<'a>(id: &oid, buf: &'a mut [u8]) -> &'a str {
134    for (byte, pair) in id.as_bytes().iter().zip(buf.chunks_exact_mut(2)) {
135        pair[0] = REVERSE_HEX[usize::from(byte >> 4)];
136        pair[1] = REVERSE_HEX[usize::from(byte & 0x0f)];
137    }
138    let len = id.kind().len_in_hex();
139    std::str::from_utf8(&buf[..len]).expect("reverse hex is always ASCII")
140}