git2/
oid.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::path::Path;
5use std::str;
6
7use crate::{raw, Error, IntoCString, ObjectType};
8
9use crate::util::{c_cmp_to_ordering, Binding};
10
11/// Unique identity of any object (commit, tree, blob, tag).
12#[derive(Copy, Clone)]
13#[repr(C)]
14pub struct Oid {
15    raw: raw::git_oid,
16}
17
18impl Oid {
19    /// Parse a hex-formatted object id into an Oid structure.
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if the string is empty, is longer than 40 hex
24    /// characters, or contains any non-hex characters.
25    pub fn from_str(s: &str) -> Result<Oid, Error> {
26        crate::init();
27        let mut raw = raw::git_oid {
28            id: [0; raw::GIT_OID_RAWSZ],
29        };
30        unsafe {
31            try_call!(raw::git_oid_fromstrn(
32                &mut raw,
33                s.as_bytes().as_ptr() as *const libc::c_char,
34                s.len() as libc::size_t
35            ));
36        }
37        Ok(Oid { raw })
38    }
39
40    /// Parse a raw object id into an Oid structure.
41    ///
42    /// If the array given is not 20 bytes in length, an error is returned.
43    pub fn from_bytes(bytes: &[u8]) -> Result<Oid, Error> {
44        crate::init();
45        let mut raw = raw::git_oid {
46            id: [0; raw::GIT_OID_RAWSZ],
47        };
48        if bytes.len() != raw::GIT_OID_RAWSZ {
49            Err(Error::from_str("raw byte array must be 20 bytes"))
50        } else {
51            unsafe {
52                try_call!(raw::git_oid_fromraw(&mut raw, bytes.as_ptr()));
53            }
54            Ok(Oid { raw })
55        }
56    }
57
58    /// Creates an all zero Oid structure.
59    pub fn zero() -> Oid {
60        let out = raw::git_oid {
61            id: [0; raw::GIT_OID_RAWSZ],
62        };
63        Oid { raw: out }
64    }
65
66    /// Hashes the provided data as an object of the provided type, and returns
67    /// an Oid corresponding to the result. This does not store the object
68    /// inside any object database or repository.
69    pub fn hash_object(kind: ObjectType, bytes: &[u8]) -> Result<Oid, Error> {
70        crate::init();
71
72        let mut out = raw::git_oid {
73            id: [0; raw::GIT_OID_RAWSZ],
74        };
75        unsafe {
76            try_call!(raw::git_odb_hash(
77                &mut out,
78                bytes.as_ptr() as *const libc::c_void,
79                bytes.len(),
80                kind.raw()
81            ));
82        }
83
84        Ok(Oid { raw: out })
85    }
86
87    /// Hashes the content of the provided file as an object of the provided type,
88    /// and returns an Oid corresponding to the result. This does not store the object
89    /// inside any object database or repository.
90    pub fn hash_file<P: AsRef<Path>>(kind: ObjectType, path: P) -> Result<Oid, Error> {
91        crate::init();
92
93        // Normal file path OK (does not need Windows conversion).
94        let rpath = path.as_ref().into_c_string()?;
95
96        let mut out = raw::git_oid {
97            id: [0; raw::GIT_OID_RAWSZ],
98        };
99        unsafe {
100            try_call!(raw::git_odb_hashfile(&mut out, rpath, kind.raw()));
101        }
102
103        Ok(Oid { raw: out })
104    }
105
106    /// View this OID as a byte-slice 20 bytes in length.
107    pub fn as_bytes(&self) -> &[u8] {
108        &self.raw.id
109    }
110
111    /// Test if this OID is all zeros.
112    pub fn is_zero(&self) -> bool {
113        unsafe { raw::git_oid_iszero(&self.raw) == 1 }
114    }
115}
116
117impl Binding for Oid {
118    type Raw = *const raw::git_oid;
119
120    unsafe fn from_raw(oid: *const raw::git_oid) -> Oid {
121        Oid { raw: *oid }
122    }
123    fn raw(&self) -> *const raw::git_oid {
124        &self.raw as *const _
125    }
126}
127
128impl fmt::Debug for Oid {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        fmt::Display::fmt(self, f)
131    }
132}
133
134impl fmt::Display for Oid {
135    /// Hex-encode this Oid into a formatter.
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        let mut dst = [0u8; raw::GIT_OID_HEXSZ + 1];
138        unsafe {
139            raw::git_oid_tostr(
140                dst.as_mut_ptr() as *mut libc::c_char,
141                dst.len() as libc::size_t,
142                &self.raw,
143            );
144        }
145        let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
146        str::from_utf8(s).unwrap().fmt(f)
147    }
148}
149
150impl str::FromStr for Oid {
151    type Err = Error;
152
153    /// Parse a hex-formatted object id into an Oid structure.
154    ///
155    /// # Errors
156    ///
157    /// Returns an error if the string is empty, is longer than 40 hex
158    /// characters, or contains any non-hex characters.
159    fn from_str(s: &str) -> Result<Oid, Error> {
160        Oid::from_str(s)
161    }
162}
163
164impl PartialEq for Oid {
165    fn eq(&self, other: &Oid) -> bool {
166        unsafe { raw::git_oid_equal(&self.raw, &other.raw) != 0 }
167    }
168}
169impl Eq for Oid {}
170
171impl PartialOrd for Oid {
172    fn partial_cmp(&self, other: &Oid) -> Option<Ordering> {
173        Some(self.cmp(other))
174    }
175}
176
177impl Ord for Oid {
178    fn cmp(&self, other: &Oid) -> Ordering {
179        c_cmp_to_ordering(unsafe { raw::git_oid_cmp(&self.raw, &other.raw) })
180    }
181}
182
183impl Hash for Oid {
184    fn hash<H: Hasher>(&self, into: &mut H) {
185        self.raw.id.hash(into)
186    }
187}
188
189impl AsRef<[u8]> for Oid {
190    fn as_ref(&self) -> &[u8] {
191        self.as_bytes()
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use std::fs::File;
198    use std::io::prelude::*;
199
200    use super::Error;
201    use super::Oid;
202    use crate::ObjectType;
203    use tempfile::TempDir;
204
205    #[test]
206    fn conversions() {
207        assert!(Oid::from_str("foo").is_err());
208        assert!(Oid::from_str("decbf2be529ab6557d5429922251e5ee36519817").is_ok());
209        assert!(Oid::from_bytes(b"foo").is_err());
210        assert!(Oid::from_bytes(b"00000000000000000000").is_ok());
211    }
212
213    #[test]
214    fn comparisons() -> Result<(), Error> {
215        assert_eq!(Oid::from_str("decbf2b")?, Oid::from_str("decbf2b")?);
216        assert!(Oid::from_str("decbf2b")? <= Oid::from_str("decbf2b")?);
217        assert!(Oid::from_str("decbf2b")? >= Oid::from_str("decbf2b")?);
218        {
219            let o = Oid::from_str("decbf2b")?;
220            assert_eq!(o, o);
221            assert!(o <= o);
222            assert!(o >= o);
223        }
224        assert_eq!(
225            Oid::from_str("decbf2b")?,
226            Oid::from_str("decbf2b000000000000000000000000000000000")?
227        );
228        assert!(
229            Oid::from_bytes(b"00000000000000000000")? < Oid::from_bytes(b"00000000000000000001")?
230        );
231        assert!(Oid::from_bytes(b"00000000000000000000")? < Oid::from_str("decbf2b")?);
232        assert_eq!(
233            Oid::from_bytes(b"00000000000000000000")?,
234            Oid::from_str("3030303030303030303030303030303030303030")?
235        );
236        Ok(())
237    }
238
239    #[test]
240    fn zero_is_zero() {
241        assert!(Oid::zero().is_zero());
242    }
243
244    #[test]
245    fn hash_object() {
246        let bytes = "Hello".as_bytes();
247        assert!(Oid::hash_object(ObjectType::Blob, bytes).is_ok());
248    }
249
250    #[test]
251    fn hash_file() {
252        let td = TempDir::new().unwrap();
253        let path = td.path().join("hello.txt");
254        let mut file = File::create(&path).unwrap();
255        file.write_all("Hello".as_bytes()).unwrap();
256        assert!(Oid::hash_file(ObjectType::Blob, &path).is_ok());
257    }
258}