1use std::io;
2use std::marker;
3use std::mem;
4use std::slice;
5
6use crate::util::Binding;
7use crate::{raw, Error, Object, Oid};
8
9pub struct Blob<'repo> {
13 raw: *mut raw::git_blob,
14 _marker: marker::PhantomData<Object<'repo>>,
15}
16
17impl<'repo> Blob<'repo> {
18 pub fn id(&self) -> Oid {
20 unsafe { Binding::from_raw(raw::git_blob_id(&*self.raw)) }
21 }
22
23 pub fn is_binary(&self) -> bool {
25 unsafe { raw::git_blob_is_binary(&*self.raw) == 1 }
26 }
27
28 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 pub fn size(&self) -> usize {
39 unsafe { raw::git_blob_rawsize(&*self.raw) as usize }
40 }
41
42 pub fn as_object(&self) -> &Object<'repo> {
44 unsafe { &*(self as *const _ as *const Object<'repo>) }
45 }
46
47 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
54impl<'repo> Binding for Blob<'repo> {
55 type Raw = *mut raw::git_blob;
56
57 unsafe fn from_raw(raw: *mut raw::git_blob) -> Blob<'repo> {
58 Blob {
59 raw,
60 _marker: marker::PhantomData,
61 }
62 }
63 fn raw(&self) -> *mut raw::git_blob {
64 self.raw
65 }
66}
67
68impl<'repo> std::fmt::Debug for Blob<'repo> {
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
74impl<'repo> Clone for Blob<'repo> {
75 fn clone(&self) -> Self {
76 self.as_object().clone().into_blob().ok().unwrap()
77 }
78}
79
80impl<'repo> Drop for Blob<'repo> {
81 fn drop(&mut self) {
82 unsafe { raw::git_blob_free(self.raw) }
83 }
84}
85
86pub struct BlobWriter<'repo> {
88 raw: *mut raw::git_writestream,
89 need_cleanup: bool,
90 _marker: marker::PhantomData<Object<'repo>>,
91}
92
93impl<'repo> BlobWriter<'repo> {
94 pub fn commit(mut self) -> Result<Oid, Error> {
96 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
108impl<'repo> Binding for BlobWriter<'repo> {
109 type Raw = *mut raw::git_writestream;
110
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 }
118 fn raw(&self) -> *mut raw::git_writestream {
119 self.raw
120 }
121}
122
123impl<'repo> Drop for BlobWriter<'repo> {
124 fn drop(&mut self) {
125 if self.need_cleanup {
127 unsafe {
128 if let Some(f) = (*self.raw).free {
129 f(self.raw)
130 }
131 }
132 }
133 }
134}
135
136impl<'repo> io::Write for BlobWriter<'repo> {
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 }
151 fn flush(&mut self) -> io::Result<()> {
152 Ok(())
153 }
154}
155
156#[cfg(test)]
157mod 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]
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]
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]
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}