1 use std::io; 2 use std::marker; 3 use std::mem; 4 use std::slice; 5 6 use crate::util::Binding; 7 use crate::{raw, Error, Object, Oid}; 8 9 /// A structure to represent a git [blob][1] 10 /// 11 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects 12 pub struct Blob<'repo> { 13 raw: *mut raw::git_blob, 14 _marker: marker::PhantomData<Object<'repo>>, 15 } 16 17 impl<'repo> Blob<'repo> { 18 /// Get the id (SHA1) of a repository blob id(&self) -> Oid19 pub fn id(&self) -> Oid { 20 unsafe { Binding::from_raw(raw::git_blob_id(&*self.raw)) } 21 } 22 23 /// Determine if the blob content is most certainly binary or not. is_binary(&self) -> bool24 pub fn is_binary(&self) -> bool { 25 unsafe { raw::git_blob_is_binary(&*self.raw) == 1 } 26 } 27 28 /// Get the content of this blob. content(&self) -> &[u8]29 pub fn content(&self) -> &[u8] { 30 unsafe { 31 let data = raw::git_blob_rawcontent(&*self.raw) as *const u8; 32 let len = raw::git_blob_rawsize(&*self.raw) as usize; 33 slice::from_raw_parts(data, len) 34 } 35 } 36 37 /// Get the size in bytes of the contents of this blob. size(&self) -> usize38 pub fn size(&self) -> usize { 39 unsafe { raw::git_blob_rawsize(&*self.raw) as usize } 40 } 41 42 /// Casts this Blob to be usable as an `Object` as_object(&self) -> &Object<'repo>43 pub fn as_object(&self) -> &Object<'repo> { 44 unsafe { &*(self as *const _ as *const Object<'repo>) } 45 } 46 47 /// Consumes Blob to be returned as an `Object` into_object(self) -> Object<'repo>48 pub fn into_object(self) -> Object<'repo> { 49 assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>()); 50 unsafe { mem::transmute(self) } 51 } 52 } 53 54 impl<'repo> Binding for Blob<'repo> { 55 type Raw = *mut raw::git_blob; 56 from_raw(raw: *mut raw::git_blob) -> Blob<'repo>57 unsafe fn from_raw(raw: *mut raw::git_blob) -> Blob<'repo> { 58 Blob { 59 raw, 60 _marker: marker::PhantomData, 61 } 62 } raw(&self) -> *mut raw::git_blob63 fn raw(&self) -> *mut raw::git_blob { 64 self.raw 65 } 66 } 67 68 impl<'repo> std::fmt::Debug for Blob<'repo> { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 70 f.debug_struct("Blob").field("id", &self.id()).finish() 71 } 72 } 73 74 impl<'repo> Clone for Blob<'repo> { clone(&self) -> Self75 fn clone(&self) -> Self { 76 self.as_object().clone().into_blob().ok().unwrap() 77 } 78 } 79 80 impl<'repo> Drop for Blob<'repo> { drop(&mut self)81 fn drop(&mut self) { 82 unsafe { raw::git_blob_free(self.raw) } 83 } 84 } 85 86 /// A structure to represent a git writestream for blobs 87 pub struct BlobWriter<'repo> { 88 raw: *mut raw::git_writestream, 89 need_cleanup: bool, 90 _marker: marker::PhantomData<Object<'repo>>, 91 } 92 93 impl<'repo> BlobWriter<'repo> { 94 /// Finalize blob writing stream and write the blob to the object db commit(mut self) -> Result<Oid, Error>95 pub fn commit(mut self) -> Result<Oid, Error> { 96 // After commit we already doesn't need cleanup on drop 97 self.need_cleanup = false; 98 let mut raw = raw::git_oid { 99 id: [0; raw::GIT_OID_RAWSZ], 100 }; 101 unsafe { 102 try_call!(raw::git_blob_create_fromstream_commit(&mut raw, self.raw)); 103 Ok(Binding::from_raw(&raw as *const _)) 104 } 105 } 106 } 107 108 impl<'repo> Binding for BlobWriter<'repo> { 109 type Raw = *mut raw::git_writestream; 110 from_raw(raw: *mut raw::git_writestream) -> BlobWriter<'repo>111 unsafe fn from_raw(raw: *mut raw::git_writestream) -> BlobWriter<'repo> { 112 BlobWriter { 113 raw, 114 need_cleanup: true, 115 _marker: marker::PhantomData, 116 } 117 } raw(&self) -> *mut raw::git_writestream118 fn raw(&self) -> *mut raw::git_writestream { 119 self.raw 120 } 121 } 122 123 impl<'repo> Drop for BlobWriter<'repo> { drop(&mut self)124 fn drop(&mut self) { 125 // We need cleanup in case the stream has not been committed 126 if self.need_cleanup { 127 unsafe { 128 if let Some(f) = (*self.raw).free { 129 f(self.raw) 130 } 131 } 132 } 133 } 134 } 135 136 impl<'repo> io::Write for BlobWriter<'repo> { write(&mut self, buf: &[u8]) -> io::Result<usize>137 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { 138 unsafe { 139 if let Some(f) = (*self.raw).write { 140 let res = f(self.raw, buf.as_ptr() as *const _, buf.len()); 141 if res < 0 { 142 Err(io::Error::new(io::ErrorKind::Other, "Write error")) 143 } else { 144 Ok(buf.len()) 145 } 146 } else { 147 Err(io::Error::new(io::ErrorKind::Other, "no write callback")) 148 } 149 } 150 } flush(&mut self) -> io::Result<()>151 fn flush(&mut self) -> io::Result<()> { 152 Ok(()) 153 } 154 } 155 156 #[cfg(test)] 157 mod tests { 158 use crate::Repository; 159 use std::fs::File; 160 use std::io::prelude::*; 161 use std::path::Path; 162 use tempfile::TempDir; 163 164 #[test] buffer()165 fn buffer() { 166 let td = TempDir::new().unwrap(); 167 let repo = Repository::init(td.path()).unwrap(); 168 let id = repo.blob(&[5, 4, 6]).unwrap(); 169 let blob = repo.find_blob(id).unwrap(); 170 171 assert_eq!(blob.id(), id); 172 assert_eq!(blob.size(), 3); 173 assert_eq!(blob.content(), [5, 4, 6]); 174 assert!(blob.is_binary()); 175 176 repo.find_object(id, None).unwrap().as_blob().unwrap(); 177 repo.find_object(id, None) 178 .unwrap() 179 .into_blob() 180 .ok() 181 .unwrap(); 182 } 183 184 #[test] path()185 fn path() { 186 let td = TempDir::new().unwrap(); 187 let path = td.path().join("foo"); 188 File::create(&path).unwrap().write_all(&[7, 8, 9]).unwrap(); 189 let repo = Repository::init(td.path()).unwrap(); 190 let id = repo.blob_path(&path).unwrap(); 191 let blob = repo.find_blob(id).unwrap(); 192 assert_eq!(blob.content(), [7, 8, 9]); 193 blob.into_object(); 194 } 195 196 #[test] stream()197 fn stream() { 198 let td = TempDir::new().unwrap(); 199 let repo = Repository::init(td.path()).unwrap(); 200 let mut ws = repo.blob_writer(Some(Path::new("foo"))).unwrap(); 201 let wl = ws.write(&[10, 11, 12]).unwrap(); 202 assert_eq!(wl, 3); 203 let id = ws.commit().unwrap(); 204 let blob = repo.find_blob(id).unwrap(); 205 assert_eq!(blob.content(), [10, 11, 12]); 206 blob.into_object(); 207 } 208 } 209