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 }) 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 }) 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