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