Skip to content

Commit a8dfbc1

Browse files
bk2204gitster
authored andcommitted
rust: add additional helpers for ObjectID
Right now, users can internally access the contents of the ObjectID struct, which can lead to data that is not valid, such as invalid algorithms or non-zero-padded hash values. These can cause problems down the line as we use them more. Add a constructor for ObjectID that allows us to set these values and also provide an accessor for the algorithm so that we can access it. In addition, provide useful Display and Debug implementations that can format our data in a useful way. Now that we have the ability to work with these various components in a nice way, add some tests as well to make sure that ObjectID and HashAlgorithm work together as expected. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent eae08d8 commit a8dfbc1

File tree

1 file changed

+132
-1
lines changed

1 file changed

+132
-1
lines changed

src/hash.rs

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,35 @@ impl Error for InvalidHashAlgorithm {}
3232

3333
/// A binary object ID.
3434
#[repr(C)]
35-
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
35+
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
3636
pub struct ObjectID {
3737
pub hash: [u8; GIT_MAX_RAWSZ],
3838
pub algo: u32,
3939
}
4040

4141
#[allow(dead_code)]
4242
impl ObjectID {
43+
/// Return a new object ID with the given algorithm and hash.
44+
///
45+
/// `hash` must be exactly the proper length for `algo` and this function panics if it is not.
46+
/// The extra internal storage of `hash`, if any, is zero filled.
47+
pub fn new(algo: HashAlgorithm, hash: &[u8]) -> Self {
48+
let mut data = [0u8; GIT_MAX_RAWSZ];
49+
// This verifies that the length of `hash` is correct.
50+
data[0..algo.raw_len()].copy_from_slice(hash);
51+
Self {
52+
hash: data,
53+
algo: algo as u32,
54+
}
55+
}
56+
57+
/// Return the algorithm for this object ID.
58+
///
59+
/// If the algorithm set internally is not valid, this function panics.
60+
pub fn algo(&self) -> Result<HashAlgorithm, InvalidHashAlgorithm> {
61+
HashAlgorithm::from_u32(self.algo).ok_or(InvalidHashAlgorithm(self.algo))
62+
}
63+
4364
pub fn as_slice(&self) -> Result<&[u8], InvalidHashAlgorithm> {
4465
match HashAlgorithm::from_u32(self.algo) {
4566
Some(algo) => Ok(&self.hash[0..algo.raw_len()]),
@@ -55,6 +76,41 @@ impl ObjectID {
5576
}
5677
}
5778

79+
impl Display for ObjectID {
80+
/// Format this object ID as a hex object ID.
81+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82+
let hash = self.as_slice().unwrap();
83+
for x in hash {
84+
write!(f, "{:02x}", x)?;
85+
}
86+
Ok(())
87+
}
88+
}
89+
90+
impl Debug for ObjectID {
91+
/// Format this object ID as a hex object ID with a colon and name appended to it.
92+
///
93+
/// ```
94+
/// assert_eq!(
95+
/// format!("{:?}", HashAlgorithm::SHA256.null_oid()),
96+
/// "0000000000000000000000000000000000000000000000000000000000000000:sha256"
97+
/// );
98+
/// ```
99+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100+
let hash = match self.as_slice() {
101+
Ok(hash) => hash,
102+
Err(_) => &self.hash,
103+
};
104+
for x in hash {
105+
write!(f, "{:02x}", x)?;
106+
}
107+
match self.algo() {
108+
Ok(algo) => write!(f, ":{}", algo.name()),
109+
Err(e) => write!(f, ":invalid-hash-algo-{}", e.0),
110+
}
111+
}
112+
}
113+
58114
/// A hash algorithm,
59115
#[repr(C)]
60116
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
@@ -192,3 +248,78 @@ pub mod c {
192248
pub fn hash_algo_ptr_by_number(n: u32) -> *const c_void;
193249
}
194250
}
251+
252+
#[cfg(test)]
253+
mod tests {
254+
use super::HashAlgorithm;
255+
256+
fn all_algos() -> &'static [HashAlgorithm] {
257+
&[HashAlgorithm::SHA1, HashAlgorithm::SHA256]
258+
}
259+
260+
#[test]
261+
fn format_id_round_trips() {
262+
for algo in all_algos() {
263+
assert_eq!(
264+
*algo,
265+
HashAlgorithm::from_format_id(algo.format_id()).unwrap()
266+
);
267+
}
268+
}
269+
270+
#[test]
271+
fn offset_round_trips() {
272+
for algo in all_algos() {
273+
assert_eq!(*algo, HashAlgorithm::from_u32(*algo as u32).unwrap());
274+
}
275+
}
276+
277+
#[test]
278+
fn slices_have_correct_length() {
279+
for algo in all_algos() {
280+
for oid in [algo.null_oid(), algo.empty_blob(), algo.empty_tree()] {
281+
assert_eq!(oid.as_slice().unwrap().len(), algo.raw_len());
282+
}
283+
}
284+
}
285+
286+
#[test]
287+
fn object_ids_format_correctly() {
288+
let entries = &[
289+
(
290+
HashAlgorithm::SHA1.null_oid(),
291+
"0000000000000000000000000000000000000000",
292+
"0000000000000000000000000000000000000000:sha1",
293+
),
294+
(
295+
HashAlgorithm::SHA1.empty_blob(),
296+
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
297+
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:sha1",
298+
),
299+
(
300+
HashAlgorithm::SHA1.empty_tree(),
301+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
302+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904:sha1",
303+
),
304+
(
305+
HashAlgorithm::SHA256.null_oid(),
306+
"0000000000000000000000000000000000000000000000000000000000000000",
307+
"0000000000000000000000000000000000000000000000000000000000000000:sha256",
308+
),
309+
(
310+
HashAlgorithm::SHA256.empty_blob(),
311+
"473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813",
312+
"473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813:sha256",
313+
),
314+
(
315+
HashAlgorithm::SHA256.empty_tree(),
316+
"6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321",
317+
"6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321:sha256",
318+
),
319+
];
320+
for (oid, display, debug) in entries {
321+
assert_eq!(format!("{}", oid), *display);
322+
assert_eq!(format!("{:?}", oid), *debug);
323+
}
324+
}
325+
}

0 commit comments

Comments
 (0)