git2/
blob.rs

1use std::io;
2use std::marker;
3use std::mem;
4use std::slice;
5
6use crate::util::Binding;
7use 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
12pub struct Blob<'repo> {
13    raw: *mut raw::git_blob,
14    _marker: marker::PhantomData<Object<'repo>>,
15}
16
17impl<'repo> Blob<'repo> {
18    /// Get the id (SHA1) of a repository blob
19    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.
24    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.
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.
38    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`
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`
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
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
86/// A structure to represent a git writestream for blobs
87pub 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    /// Finalize blob writing stream and write the blob to the object db
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
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        // 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
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}