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