Skip to main content

git_internal/
hash.rs

1//! Hash utilities for Git objects with selectable algorithms (SHA-1 and SHA-256).
2//! Hash kind is stored thread-locally; set once at startup to match your repository format.
3//! Defaults to SHA-1.
4
5use std::{cell::RefCell, fmt::Display, hash::Hash, io, str::FromStr};
6
7use colored::Colorize;
8use sha1::Digest;
9
10use crate::internal::object::types::ObjectType;
11
12/// Supported hash algorithms for object IDs (selector only, no data attached).
13/// Used to configure which hash algorithm to use globally (thread-local).
14/// Defaults to SHA-1.
15#[derive(
16    Clone,
17    Copy,
18    Debug,
19    PartialEq,
20    Eq,
21    Hash,
22    PartialOrd,
23    Ord,
24    Default,
25    serde::Deserialize,
26    serde::Serialize,
27    rkyv::Archive,
28    rkyv::Serialize,
29    rkyv::Deserialize,
30)]
31pub enum HashKind {
32    #[default]
33    Sha1,
34    Sha256,
35}
36impl HashKind {
37    /// Byte length of the hash output.
38    pub const fn size(&self) -> usize {
39        match self {
40            HashKind::Sha1 => 20,
41            HashKind::Sha256 => 32,
42            // Add more hash kinds here as needed
43        }
44    }
45    /// Hex string length of the hash output.
46    pub const fn hex_len(&self) -> usize {
47        match self {
48            HashKind::Sha1 => 40,
49            HashKind::Sha256 => 64,
50        }
51    }
52    /// Lowercase name of the hash algorithm.
53    pub const fn as_str(&self) -> &'static str {
54        match self {
55            HashKind::Sha1 => "sha1",
56            HashKind::Sha256 => "sha256",
57        }
58    }
59}
60impl std::fmt::Display for HashKind {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.write_str(self.as_str())
63    }
64}
65impl std::str::FromStr for HashKind {
66    type Err = String;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        match s.to_ascii_lowercase().as_str() {
70            "sha1" => Ok(HashKind::Sha1),
71            "sha256" => Ok(HashKind::Sha256),
72            _ => Err("Invalid hash kind".to_string()),
73        }
74    }
75}
76
77#[derive(
78    Clone,
79    Copy,
80    Debug,
81    PartialEq,
82    Eq,
83    Hash,
84    PartialOrd,
85    Ord,
86    serde::Deserialize,
87    serde::Serialize,
88    rkyv::Archive,
89    rkyv::Serialize,
90    rkyv::Deserialize,
91)]
92/// Concrete object ID value carrying the bytes for the selected algorithm (SHA-1 or SHA-256).
93/// Used for Git object hashes.
94/// Supports conversion to/from hex strings, byte slices, and stream reading.
95pub enum ObjectHash {
96    Sha1([u8; 20]),
97    Sha256([u8; 32]),
98}
99impl Default for ObjectHash {
100    fn default() -> Self {
101        ObjectHash::Sha1([0u8; 20])
102    }
103}
104impl Display for ObjectHash {
105    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
106        write!(f, "{}", hex::encode(self.as_ref()))
107    }
108}
109impl AsRef<[u8]> for ObjectHash {
110    fn as_ref(&self) -> &[u8] {
111        match self {
112            ObjectHash::Sha1(bytes) => bytes.as_slice(),
113            ObjectHash::Sha256(bytes) => bytes.as_slice(),
114        }
115    }
116}
117/// Parse hex (40 for SHA1, 64 for SHA-256) into `ObjectHash`.
118impl FromStr for ObjectHash {
119    type Err = String;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        match s.len() {
123            40 => {
124                let mut h = [0u8; 20];
125                let bytes = hex::decode(s).map_err(|e| e.to_string())?;
126                h.copy_from_slice(bytes.as_slice());
127                Ok(ObjectHash::Sha1(h))
128            }
129            64 => {
130                let mut h = [0u8; 32];
131                let bytes = hex::decode(s).map_err(|e| e.to_string())?;
132                h.copy_from_slice(bytes.as_slice());
133                Ok(ObjectHash::Sha256(h))
134            }
135            _ => Err("Invalid hash length".to_string()),
136        }
137    }
138}
139
140impl ObjectHash {
141    /// Zero-filled hex string for a given hash kind.
142    pub fn zero_str(kind: HashKind) -> String {
143        match kind {
144            HashKind::Sha1 => "0000000000000000000000000000000000000000".to_string(),
145            HashKind::Sha256 => {
146                "0000000000000000000000000000000000000000000000000000000000000000".to_string()
147            }
148        }
149    }
150
151    /// Return the hash kind for this value.
152    pub fn kind(&self) -> HashKind {
153        match self {
154            ObjectHash::Sha1(_) => HashKind::Sha1,
155            ObjectHash::Sha256(_) => HashKind::Sha256,
156        }
157    }
158    /// Return the hash size in bytes.
159    pub fn size(&self) -> usize {
160        self.kind().size()
161    }
162
163    /// Compute hash of data using current thread-local `HashKind`.
164    pub fn new(data: &[u8]) -> ObjectHash {
165        match get_hash_kind() {
166            HashKind::Sha1 => {
167                let h = sha1::Sha1::digest(data);
168                let mut bytes = [0u8; 20];
169                bytes.copy_from_slice(h.as_ref());
170                ObjectHash::Sha1(bytes)
171            }
172            HashKind::Sha256 => {
173                let h = sha2::Sha256::digest(data);
174                let mut bytes = [0u8; 32];
175                bytes.copy_from_slice(h.as_ref());
176                ObjectHash::Sha256(bytes)
177            }
178        }
179    }
180    /// Create ObjectHash from object type and data
181    pub fn from_type_and_data(object_type: ObjectType, data: &[u8]) -> ObjectHash {
182        let mut d: Vec<u8> = Vec::new();
183        d.extend(object_type.to_data().unwrap());
184        d.push(b' ');
185        d.extend(data.len().to_string().as_bytes());
186        d.push(b'\x00');
187        d.extend(data);
188        ObjectHash::new(&d)
189    }
190    /// Create `ObjectHash` from raw bytes matching the current hash size.
191    pub fn from_bytes(bytes: &[u8]) -> Result<ObjectHash, String> {
192        let expected_len = get_hash_kind().size();
193        if bytes.len() != expected_len {
194            return Err(format!(
195                "Invalid byte length: got {}, expected {}",
196                bytes.len(),
197                expected_len
198            ));
199        }
200
201        match get_hash_kind() {
202            HashKind::Sha1 => {
203                let mut h = [0u8; 20];
204                h.copy_from_slice(bytes);
205                Ok(ObjectHash::Sha1(h))
206            }
207            HashKind::Sha256 => {
208                let mut h = [0u8; 32];
209                h.copy_from_slice(bytes);
210                Ok(ObjectHash::Sha256(h))
211            }
212        }
213    }
214    /// Create `ObjectHash` from raw bytes, inferring the hash kind from the
215    /// byte length (20 → SHA-1, 32 → SHA-256).
216    ///
217    /// Unlike [`ObjectHash::from_bytes`], this does not consult the
218    /// thread-local [`HashKind`], so it is safe on threads where
219    /// [`set_hash_kind`] was never called. The pack encoder finalizes its
220    /// running checksum on whichever async worker thread happens to run the
221    /// task; the checksum bytes already carry the correct length, while the
222    /// worker's thread-local may still hold the default SHA-1 kind — the
223    /// mismatch used to panic with "Invalid byte length: got 32, expected 20".
224    pub fn from_bytes_infer_kind(bytes: &[u8]) -> Result<ObjectHash, String> {
225        match bytes.len() {
226            20 => {
227                let mut h = [0u8; 20];
228                h.copy_from_slice(bytes);
229                Ok(ObjectHash::Sha1(h))
230            }
231            32 => {
232                let mut h = [0u8; 32];
233                h.copy_from_slice(bytes);
234                Ok(ObjectHash::Sha256(h))
235            }
236            other => Err(format!(
237                "Invalid byte length: got {other}, expected 20 (SHA-1) or 32 (SHA-256)"
238            )),
239        }
240    }
241    /// Read hash bytes from a stream according to current hash size.
242    pub fn from_stream(data: &mut impl io::Read) -> io::Result<ObjectHash> {
243        match get_hash_kind() {
244            HashKind::Sha1 => {
245                let mut h = [0u8; 20];
246                data.read_exact(&mut h)?;
247                Ok(ObjectHash::Sha1(h))
248            }
249            HashKind::Sha256 => {
250                let mut h = [0u8; 32];
251                data.read_exact(&mut h)?;
252                Ok(ObjectHash::Sha256(h))
253            }
254        }
255    }
256
257    /// Format hash as colored string (for terminal display).
258    pub fn to_color_str(self) -> String {
259        self.to_string().red().bold().to_string()
260    }
261
262    /// Return raw bytes of the hash.
263    pub fn to_data(self) -> Vec<u8> {
264        self.as_ref().to_vec()
265    }
266
267    /// Faster string conversion than `Display`.
268    pub fn _to_string(&self) -> String {
269        hex::encode(self.as_ref())
270    }
271
272    /// Get mutable access to inner byte slice.
273    pub fn as_mut_bytes(&mut self) -> &mut [u8] {
274        match self {
275            ObjectHash::Sha1(bytes) => bytes.as_mut_slice(),
276            ObjectHash::Sha256(bytes) => bytes.as_mut_slice(),
277        }
278    }
279}
280
281thread_local! {
282    /// Thread-local variable to store the current hash kind.
283    /// This allows different threads to work with different hash algorithms concurrently
284    /// without interfering with each other.
285    static CURRENT_HASH_KIND: RefCell<HashKind> = RefCell::new(HashKind::default());
286}
287/// Set the thread-local hash kind (configure once at startup to match repo format).
288pub fn set_hash_kind(kind: HashKind) {
289    CURRENT_HASH_KIND.with(|h| {
290        *h.borrow_mut() = kind;
291    });
292}
293
294/// Retrieves the hash kind for the current thread.
295pub fn get_hash_kind() -> HashKind {
296    CURRENT_HASH_KIND.with(|h| *h.borrow())
297}
298/// A guard to reset the hash kind after the test
299pub struct HashKindGuard {
300    prev: HashKind,
301}
302/// Implementation of the `Drop` trait for the `HashKindGuard` struct.
303impl Drop for HashKindGuard {
304    fn drop(&mut self) {
305        set_hash_kind(self.prev);
306    }
307}
308/// Sets the hash kind for the current thread and returns a guard to reset it later.
309pub fn set_hash_kind_for_test(kind: HashKind) -> HashKindGuard {
310    let prev = get_hash_kind();
311    set_hash_kind(kind);
312    HashKindGuard { prev }
313}
314#[cfg(test)]
315mod tests {
316
317    use std::{
318        io::{BufReader, Read, Seek, SeekFrom},
319        str::FromStr,
320    };
321
322    use crate::{
323        hash::{HashKind, ObjectHash, set_hash_kind_for_test},
324        internal::pack::test_pack_download::download_pack_file,
325    };
326
327    /// Hashing "Hello, world!" with SHA1 should match known value.
328    #[test]
329    fn test_sha1_new() {
330        // Set hash kind to SHA1 for this test
331        let _guard = set_hash_kind_for_test(HashKind::Sha1);
332        // Example input
333        let data = "Hello, world!".as_bytes();
334
335        // Generate SHA1 hash from the input data
336        let sha1 = ObjectHash::new(data);
337
338        // Known SHA1 hash for "Hello, world!"
339        let expected_sha1_hash = "943a702d06f34599aee1f8da8ef9f7296031d699";
340
341        assert_eq!(sha1.to_string(), expected_sha1_hash);
342    }
343
344    /// Hashing "Hello, world!" with SHA256 should match known value.
345    #[test]
346    fn test_sha256_new() {
347        let _guard = set_hash_kind_for_test(HashKind::Sha256);
348        let data = "Hello, world!".as_bytes();
349        let sha256 = ObjectHash::new(data);
350        let expected_sha256_hash =
351            "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3";
352        assert_eq!(sha256.to_string(), expected_sha256_hash);
353    }
354
355    /// Read pack trailer for SHA1 pack should yield SHA1 hash.
356    #[test]
357    fn test_signature_without_delta() {
358        let _guard = set_hash_kind_for_test(HashKind::Sha1);
359        let (source, _dl_guard) = download_pack_file("small-sha1.pack");
360
361        let f = std::fs::File::open(source).unwrap();
362        let mut buffered = BufReader::new(f);
363
364        buffered.seek(SeekFrom::End(-20)).unwrap();
365        let mut buffer = vec![0; 20];
366        buffered.read_exact(&mut buffer).unwrap();
367        let signature = ObjectHash::from_bytes(buffer.as_ref()).unwrap();
368        assert_eq!(signature.kind(), HashKind::Sha1);
369    }
370
371    /// Read pack trailer for SHA256 pack should yield SHA256 hash.
372    #[test]
373    fn test_signature_without_delta_sha256() {
374        let _guard = set_hash_kind_for_test(HashKind::Sha256);
375        let (source, _dl_guard) = download_pack_file("small-sha256.pack");
376
377        let f = std::fs::File::open(source).unwrap();
378        let mut buffered = BufReader::new(f);
379
380        buffered.seek(SeekFrom::End(-32)).unwrap();
381        let mut buffer = vec![0; 32];
382        buffered.read_exact(&mut buffer).unwrap();
383        let signature = ObjectHash::from_bytes(buffer.as_ref()).unwrap();
384        assert_eq!(signature.kind(), HashKind::Sha256);
385    }
386
387    /// Construct SHA1 from raw bytes.
388    #[test]
389    fn test_sha1_from_bytes() {
390        let _guard = set_hash_kind_for_test(HashKind::Sha1);
391        let sha1 = ObjectHash::from_bytes(&[
392            0x8a, 0xb6, 0x86, 0xea, 0xfe, 0xb1, 0xf4, 0x47, 0x02, 0x73, 0x8c, 0x8b, 0x0f, 0x24,
393            0xf2, 0x56, 0x7c, 0x36, 0xda, 0x6d,
394        ])
395        .unwrap();
396
397        assert_eq!(sha1.to_string(), "8ab686eafeb1f44702738c8b0f24f2567c36da6d");
398    }
399
400    /// Construct SHA256 from raw bytes.
401    #[test]
402    fn test_sha256_from_bytes() {
403        let _guard = set_hash_kind_for_test(HashKind::Sha256);
404        // Pre-calculated SHA256 hash for "abc"
405        let sha256 = ObjectHash::from_bytes(&[
406            0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
407            0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
408            0xf2, 0x00, 0x15, 0xad,
409        ])
410        .unwrap();
411
412        assert_eq!(
413            sha256.to_string(),
414            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
415        );
416    }
417
418    /// Read hash from stream for SHA1.
419    #[test]
420    fn test_from_stream() {
421        let _guard = set_hash_kind_for_test(HashKind::Sha1);
422        let source = [
423            0x8a, 0xb6, 0x86, 0xea, 0xfe, 0xb1, 0xf4, 0x47, 0x02, 0x73, 0x8c, 0x8b, 0x0f, 0x24,
424            0xf2, 0x56, 0x7c, 0x36, 0xda, 0x6d,
425        ];
426        let mut reader = std::io::Cursor::new(source);
427        let sha1 = ObjectHash::from_stream(&mut reader).unwrap();
428        assert_eq!(sha1.to_string(), "8ab686eafeb1f44702738c8b0f24f2567c36da6d");
429    }
430
431    /// Read hash from stream for SHA256.
432    #[test]
433    fn test_sha256_from_stream() {
434        let _guard = set_hash_kind_for_test(HashKind::Sha256);
435        let source = [
436            0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
437            0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
438            0xf2, 0x00, 0x15, 0xad,
439        ];
440        let mut reader = std::io::Cursor::new(source);
441        let sha256 = ObjectHash::from_stream(&mut reader).unwrap();
442        assert_eq!(
443            sha256.to_string(),
444            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
445        );
446    }
447
448    /// Parse SHA1 from hex string.
449    #[test]
450    fn test_sha1_from_str() {
451        let _guard = set_hash_kind_for_test(HashKind::Sha1);
452        let hash_str = "8ab686eafeb1f44702738c8b0f24f2567c36da6d";
453
454        match ObjectHash::from_str(hash_str) {
455            Ok(hash) => {
456                assert_eq!(hash.to_string(), "8ab686eafeb1f44702738c8b0f24f2567c36da6d");
457            }
458            Err(e) => println!("Error: {e}"),
459        }
460    }
461
462    /// Parse SHA256 from hex string.
463    #[test]
464    fn test_sha256_from_str() {
465        let _guard = set_hash_kind_for_test(HashKind::Sha256);
466        let hash_str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
467
468        match ObjectHash::from_str(hash_str) {
469            Ok(hash) => {
470                assert_eq!(
471                    hash.to_string(),
472                    "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
473                );
474            }
475            Err(e) => println!("Error: {e}"),
476        }
477    }
478
479    /// SHA1 to_string should round-trip.
480    #[test]
481    fn test_sha1_to_string() {
482        let _guard = set_hash_kind_for_test(HashKind::Sha1);
483        let hash_str = "8ab686eafeb1f44702738c8b0f24f2567c36da6d";
484
485        match ObjectHash::from_str(hash_str) {
486            Ok(hash) => {
487                assert_eq!(hash.to_string(), "8ab686eafeb1f44702738c8b0f24f2567c36da6d");
488            }
489            Err(e) => println!("Error: {e}"),
490        }
491    }
492
493    /// SHA256 to_string should round-trip.
494    #[test]
495    fn test_sha256_to_string() {
496        let _guard = set_hash_kind_for_test(HashKind::Sha256);
497        let hash_str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
498        match ObjectHash::from_str(hash_str) {
499            Ok(hash) => {
500                assert_eq!(
501                    hash.to_string(),
502                    "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
503                );
504            }
505            Err(e) => println!("Error: {e}"),
506        }
507    }
508
509    /// SHA1 to_data should produce expected bytes.
510    #[test]
511    fn test_sha1_to_data() {
512        let _guard = set_hash_kind_for_test(HashKind::Sha1);
513        let hash_str = "8ab686eafeb1f44702738c8b0f24f2567c36da6d";
514
515        match ObjectHash::from_str(hash_str) {
516            Ok(hash) => {
517                assert_eq!(
518                    hash.to_data(),
519                    vec![
520                        0x8a, 0xb6, 0x86, 0xea, 0xfe, 0xb1, 0xf4, 0x47, 0x02, 0x73, 0x8c, 0x8b,
521                        0x0f, 0x24, 0xf2, 0x56, 0x7c, 0x36, 0xda, 0x6d
522                    ]
523                );
524            }
525            Err(e) => println!("Error: {e}"),
526        }
527    }
528
529    /// SHA256 to_data should produce expected bytes.
530    #[test]
531    fn test_sha256_to_data() {
532        let _guard = set_hash_kind_for_test(HashKind::Sha256);
533        let hash_str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
534        match ObjectHash::from_str(hash_str) {
535            Ok(hash) => {
536                assert_eq!(
537                    hash.to_data(),
538                    vec![
539                        0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde,
540                        0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
541                        0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
542                    ]
543                );
544            }
545            Err(e) => println!("Error: {e}"),
546        }
547    }
548}