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#[derive(Copy, Clone)]
13#[repr(C)]
14pub struct Oid {
15 raw: raw::git_oid,
16}
17
18impl Oid {
19 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 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 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 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 pub fn hash_file<P: AsRef<Path>>(kind: ObjectType, path: P) -> Result<Oid, Error> {
91 crate::init();
92
93 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 pub fn as_bytes(&self) -> &[u8] {
108 &self.raw.id
109 }
110
111 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 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 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}