git2/
odb.rs

1use std::io;
2use std::marker;
3use std::ptr;
4use std::slice;
5
6use std::ffi::CString;
7
8use libc::{c_char, c_int, c_uint, c_void, size_t};
9
10use crate::panic;
11use crate::util::Binding;
12use crate::{
13    raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress,
14};
15
16/// A structure to represent a git object database
17pub struct Odb<'repo> {
18    raw: *mut raw::git_odb,
19    _marker: marker::PhantomData<Object<'repo>>,
20}
21
22// `git_odb` uses locking and atomics internally.
23unsafe impl<'repo> Send for Odb<'repo> {}
24unsafe impl<'repo> Sync for Odb<'repo> {}
25
26impl<'repo> Binding for Odb<'repo> {
27    type Raw = *mut raw::git_odb;
28
29    unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> {
30        Odb {
31            raw,
32            _marker: marker::PhantomData,
33        }
34    }
35    fn raw(&self) -> *mut raw::git_odb {
36        self.raw
37    }
38}
39
40impl<'repo> Drop for Odb<'repo> {
41    fn drop(&mut self) {
42        unsafe { raw::git_odb_free(self.raw) }
43    }
44}
45
46impl<'repo> Odb<'repo> {
47    /// Creates an object database without any backends.
48    pub fn new<'a>() -> Result<Odb<'a>, Error> {
49        crate::init();
50        unsafe {
51            let mut out = ptr::null_mut();
52            try_call!(raw::git_odb_new(&mut out));
53            Ok(Odb::from_raw(out))
54        }
55    }
56
57    /// Create object database reading stream.
58    ///
59    /// Note that most backends do not support streaming reads because they store their objects as compressed/delta'ed blobs.
60    /// If the backend does not support streaming reads, use the `read` method instead.
61    pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> {
62        let mut out = ptr::null_mut();
63        let mut size = 0usize;
64        let mut otype: raw::git_object_t = ObjectType::Any.raw();
65        unsafe {
66            try_call!(raw::git_odb_open_rstream(
67                &mut out,
68                &mut size,
69                &mut otype,
70                self.raw,
71                oid.raw()
72            ));
73            Ok((
74                OdbReader::from_raw(out),
75                size,
76                ObjectType::from_raw(otype).unwrap(),
77            ))
78        }
79    }
80
81    /// Create object database writing stream.
82    ///
83    /// The type and final length of the object must be specified when opening the stream.
84    /// If the backend does not support streaming writes, use the `write` method instead.
85    pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> {
86        let mut out = ptr::null_mut();
87        unsafe {
88            try_call!(raw::git_odb_open_wstream(
89                &mut out,
90                self.raw,
91                size as raw::git_object_size_t,
92                obj_type.raw()
93            ));
94            Ok(OdbWriter::from_raw(out))
95        }
96    }
97
98    /// Iterate over all objects in the object database.s
99    pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error>
100    where
101        C: FnMut(&Oid) -> bool,
102    {
103        unsafe {
104            let mut data = ForeachCbData {
105                callback: &mut callback,
106            };
107            let cb: raw::git_odb_foreach_cb = Some(foreach_cb);
108            try_call!(raw::git_odb_foreach(
109                self.raw(),
110                cb,
111                &mut data as *mut _ as *mut _
112            ));
113            Ok(())
114        }
115    }
116
117    /// Read an object from the database.
118    pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> {
119        let mut out = ptr::null_mut();
120        unsafe {
121            try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw()));
122            Ok(OdbObject::from_raw(out))
123        }
124    }
125
126    /// Reads the header of an object from the database
127    /// without reading the full content.
128    pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> {
129        let mut size: usize = 0;
130        let mut kind_id: i32 = ObjectType::Any.raw();
131
132        unsafe {
133            try_call!(raw::git_odb_read_header(
134                &mut size as *mut size_t,
135                &mut kind_id as *mut raw::git_object_t,
136                self.raw,
137                oid.raw()
138            ));
139
140            Ok((size, ObjectType::from_raw(kind_id).unwrap()))
141        }
142    }
143
144    /// Write an object to the database.
145    pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> {
146        unsafe {
147            let mut out = raw::git_oid {
148                id: [0; raw::GIT_OID_RAWSZ],
149            };
150            try_call!(raw::git_odb_write(
151                &mut out,
152                self.raw,
153                data.as_ptr() as *const c_void,
154                data.len(),
155                kind.raw()
156            ));
157            Ok(Oid::from_raw(&mut out))
158        }
159    }
160
161    /// Create stream for writing a pack file to the ODB
162    pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> {
163        let mut out = ptr::null_mut();
164        let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
165        let progress_payload = Box::new(OdbPackwriterCb { cb: None });
166        let progress_payload_ptr = Box::into_raw(progress_payload);
167
168        unsafe {
169            try_call!(raw::git_odb_write_pack(
170                &mut out,
171                self.raw,
172                progress_cb,
173                progress_payload_ptr as *mut c_void
174            ));
175        }
176
177        Ok(OdbPackwriter {
178            raw: out,
179            progress: Default::default(),
180            progress_payload_ptr,
181        })
182    }
183
184    /// Checks if the object database has an object.
185    pub fn exists(&self, oid: Oid) -> bool {
186        unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
187    }
188
189    /// Checks if the object database has an object, with extended flags.
190    pub fn exists_ext(&self, oid: Oid, flags: OdbLookupFlags) -> bool {
191        unsafe { raw::git_odb_exists_ext(self.raw, oid.raw(), flags.bits() as c_uint) != 0 }
192    }
193
194    /// Potentially finds an object that starts with the given prefix.
195    pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> {
196        unsafe {
197            let mut out = raw::git_oid {
198                id: [0; raw::GIT_OID_RAWSZ],
199            };
200            try_call!(raw::git_odb_exists_prefix(
201                &mut out,
202                self.raw,
203                short_oid.raw(),
204                len
205            ));
206            Ok(Oid::from_raw(&out))
207        }
208    }
209
210    /// Refresh the object database.
211    /// This should never be needed, and is
212    /// provided purely for convenience.
213    /// The object database will automatically
214    /// refresh when an object is not found when
215    /// requested.
216    pub fn refresh(&self) -> Result<(), Error> {
217        unsafe {
218            try_call!(raw::git_odb_refresh(self.raw));
219            Ok(())
220        }
221    }
222
223    /// Adds an alternate disk backend to the object database.
224    pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> {
225        unsafe {
226            let path = CString::new(path)?;
227            try_call!(raw::git_odb_add_disk_alternate(self.raw, path));
228            Ok(())
229        }
230    }
231
232    /// Create a new mempack backend, and add it to this odb with the given
233    /// priority. Higher values give the backend higher precedence. The default
234    /// loose and pack backends have priorities 1 and 2 respectively (hard-coded
235    /// in libgit2). A reference to the new mempack backend is returned on
236    /// success. The lifetime of the backend must be contained within the
237    /// lifetime of this odb, since deletion of the odb will also result in
238    /// deletion of the mempack backend.
239    ///
240    /// Here is an example that fails to compile because it tries to hold the
241    /// mempack reference beyond the Odb's lifetime:
242    ///
243    /// ```compile_fail
244    /// use git2::Odb;
245    /// let mempack = {
246    ///     let odb = Odb::new().unwrap();
247    ///     odb.add_new_mempack_backend(1000).unwrap()
248    /// };
249    /// ```
250    pub fn add_new_mempack_backend<'odb>(
251        &'odb self,
252        priority: i32,
253    ) -> Result<Mempack<'odb>, Error> {
254        unsafe {
255            let mut mempack = ptr::null_mut();
256            // The mempack backend object in libgit2 is only ever freed by an
257            // odb that has the backend in its list. So to avoid potentially
258            // leaking the mempack backend, this API ensures that the backend
259            // is added to the odb before returning it. The lifetime of the
260            // mempack is also bound to the lifetime of the odb, so that users
261            // can't end up with a dangling reference to a mempack object that
262            // was actually freed when the odb was destroyed.
263            try_call!(raw::git_mempack_new(&mut mempack));
264            try_call!(raw::git_odb_add_backend(
265                self.raw,
266                mempack,
267                priority as c_int
268            ));
269            Ok(Mempack::from_raw(mempack))
270        }
271    }
272}
273
274/// An object from the Object Database.
275pub struct OdbObject<'a> {
276    raw: *mut raw::git_odb_object,
277    _marker: marker::PhantomData<Object<'a>>,
278}
279
280impl<'a> Binding for OdbObject<'a> {
281    type Raw = *mut raw::git_odb_object;
282
283    unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> {
284        OdbObject {
285            raw,
286            _marker: marker::PhantomData,
287        }
288    }
289
290    fn raw(&self) -> *mut raw::git_odb_object {
291        self.raw
292    }
293}
294
295impl<'a> Drop for OdbObject<'a> {
296    fn drop(&mut self) {
297        unsafe { raw::git_odb_object_free(self.raw) }
298    }
299}
300
301impl<'a> OdbObject<'a> {
302    /// Get the object type.
303    pub fn kind(&self) -> ObjectType {
304        unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() }
305    }
306
307    /// Get the object size.
308    pub fn len(&self) -> usize {
309        unsafe { raw::git_odb_object_size(self.raw) }
310    }
311
312    /// Get the object data.
313    pub fn data(&self) -> &[u8] {
314        unsafe {
315            let size = self.len();
316            let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8;
317            let buffer = slice::from_raw_parts(ptr, size);
318            return buffer;
319        }
320    }
321
322    /// Get the object id.
323    pub fn id(&self) -> Oid {
324        unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) }
325    }
326}
327
328/// A structure to represent a git ODB rstream
329pub struct OdbReader<'repo> {
330    raw: *mut raw::git_odb_stream,
331    _marker: marker::PhantomData<Object<'repo>>,
332}
333
334// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another
335// thread and continuing to read will work.
336unsafe impl<'repo> Send for OdbReader<'repo> {}
337
338impl<'repo> Binding for OdbReader<'repo> {
339    type Raw = *mut raw::git_odb_stream;
340
341    unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> {
342        OdbReader {
343            raw,
344            _marker: marker::PhantomData,
345        }
346    }
347    fn raw(&self) -> *mut raw::git_odb_stream {
348        self.raw
349    }
350}
351
352impl<'repo> Drop for OdbReader<'repo> {
353    fn drop(&mut self) {
354        unsafe { raw::git_odb_stream_free(self.raw) }
355    }
356}
357
358impl<'repo> io::Read for OdbReader<'repo> {
359    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
360        unsafe {
361            let ptr = buf.as_ptr() as *mut c_char;
362            let len = buf.len();
363            let res = raw::git_odb_stream_read(self.raw, ptr, len);
364            if res < 0 {
365                Err(io::Error::new(io::ErrorKind::Other, "Read error"))
366            } else {
367                Ok(res as _)
368            }
369        }
370    }
371}
372
373/// A structure to represent a git ODB wstream
374pub struct OdbWriter<'repo> {
375    raw: *mut raw::git_odb_stream,
376    _marker: marker::PhantomData<Object<'repo>>,
377}
378
379// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another
380// thread and continuing to write will work.
381unsafe impl<'repo> Send for OdbWriter<'repo> {}
382
383impl<'repo> OdbWriter<'repo> {
384    /// Finish writing to an ODB stream
385    ///
386    /// This method can be used to finalize writing object to the database and get an identifier.
387    /// The object will take its final name and will be available to the odb.
388    /// This method will fail if the total number of received bytes differs from the size declared with odb_writer()
389    /// Attempting write after finishing will be ignored.
390    pub fn finalize(&mut self) -> Result<Oid, Error> {
391        let mut raw = raw::git_oid {
392            id: [0; raw::GIT_OID_RAWSZ],
393        };
394        unsafe {
395            try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw));
396            Ok(Binding::from_raw(&raw as *const _))
397        }
398    }
399}
400
401impl<'repo> Binding for OdbWriter<'repo> {
402    type Raw = *mut raw::git_odb_stream;
403
404    unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> {
405        OdbWriter {
406            raw,
407            _marker: marker::PhantomData,
408        }
409    }
410    fn raw(&self) -> *mut raw::git_odb_stream {
411        self.raw
412    }
413}
414
415impl<'repo> Drop for OdbWriter<'repo> {
416    fn drop(&mut self) {
417        unsafe { raw::git_odb_stream_free(self.raw) }
418    }
419}
420
421impl<'repo> io::Write for OdbWriter<'repo> {
422    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
423        unsafe {
424            let ptr = buf.as_ptr() as *const c_char;
425            let len = buf.len();
426            let res = raw::git_odb_stream_write(self.raw, ptr, len);
427            if res < 0 {
428                Err(io::Error::new(io::ErrorKind::Other, "Write error"))
429            } else {
430                Ok(buf.len())
431            }
432        }
433    }
434    fn flush(&mut self) -> io::Result<()> {
435        Ok(())
436    }
437}
438
439pub(crate) struct OdbPackwriterCb<'repo> {
440    pub(crate) cb: Option<Box<IndexerProgress<'repo>>>,
441}
442
443/// A stream to write a packfile to the ODB
444pub struct OdbPackwriter<'repo> {
445    raw: *mut raw::git_odb_writepack,
446    progress: raw::git_indexer_progress,
447    progress_payload_ptr: *mut OdbPackwriterCb<'repo>,
448}
449
450impl<'repo> OdbPackwriter<'repo> {
451    /// Finish writing the packfile
452    pub fn commit(&mut self) -> Result<i32, Error> {
453        unsafe {
454            let writepack = &*self.raw;
455            let res = match writepack.commit {
456                Some(commit) => commit(self.raw, &mut self.progress),
457                None => -1,
458            };
459
460            if res < 0 {
461                Err(Error::last_error(res))
462            } else {
463                Ok(res)
464            }
465        }
466    }
467
468    /// The callback through which progress is monitored. Be aware that this is
469    /// called inline, so performance may be affected.
470    pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo>
471    where
472        F: FnMut(Progress<'_>) -> bool + 'repo,
473    {
474        let progress_payload =
475            unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
476
477        progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>);
478        self
479    }
480}
481
482impl<'repo> io::Write for OdbPackwriter<'repo> {
483    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
484        unsafe {
485            let ptr = buf.as_ptr() as *mut c_void;
486            let len = buf.len();
487
488            let writepack = &*self.raw;
489            let res = match writepack.append {
490                Some(append) => append(self.raw, ptr, len, &mut self.progress),
491                None => -1,
492            };
493
494            if res < 0 {
495                Err(io::Error::new(io::ErrorKind::Other, "Write error"))
496            } else {
497                Ok(buf.len())
498            }
499        }
500    }
501    fn flush(&mut self) -> io::Result<()> {
502        Ok(())
503    }
504}
505
506impl<'repo> Drop for OdbPackwriter<'repo> {
507    fn drop(&mut self) {
508        unsafe {
509            let writepack = &*self.raw;
510            match writepack.free {
511                Some(free) => free(self.raw),
512                None => (),
513            };
514
515            drop(Box::from_raw(self.progress_payload_ptr));
516        }
517    }
518}
519
520pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
521
522struct ForeachCbData<'a> {
523    pub callback: &'a mut ForeachCb<'a>,
524}
525
526extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int {
527    panic::wrap(|| unsafe {
528        let data = &mut *(payload as *mut ForeachCbData<'_>);
529        let res = {
530            let callback = &mut data.callback;
531            callback(&Binding::from_raw(id))
532        };
533
534        if res {
535            0
536        } else {
537            1
538        }
539    })
540    .unwrap_or(1)
541}
542
543pub(crate) extern "C" fn write_pack_progress_cb(
544    stats: *const raw::git_indexer_progress,
545    payload: *mut c_void,
546) -> c_int {
547    let ok = panic::wrap(|| unsafe {
548        let payload = &mut *(payload as *mut OdbPackwriterCb<'_>);
549
550        let callback = match payload.cb {
551            Some(ref mut cb) => cb,
552            None => return true,
553        };
554
555        let progress: Progress<'_> = Binding::from_raw(stats);
556        callback(progress)
557    });
558    if ok == Some(true) {
559        0
560    } else {
561        -1
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use crate::{Buf, ObjectType, Oid, Repository};
568    use std::io::prelude::*;
569    use tempfile::TempDir;
570
571    #[test]
572    fn read() {
573        let td = TempDir::new().unwrap();
574        let repo = Repository::init(td.path()).unwrap();
575        let dat = [4, 3, 5, 6, 9];
576        let id = repo.blob(&dat).unwrap();
577        let db = repo.odb().unwrap();
578        let obj = db.read(id).unwrap();
579        let data = obj.data();
580        let size = obj.len();
581        assert_eq!(size, 5);
582        assert_eq!(dat, data);
583        assert_eq!(id, obj.id());
584    }
585
586    #[test]
587    fn read_header() {
588        let td = TempDir::new().unwrap();
589        let repo = Repository::init(td.path()).unwrap();
590        let dat = [4, 3, 5, 6, 9];
591        let id = repo.blob(&dat).unwrap();
592        let db = repo.odb().unwrap();
593        let (size, kind) = db.read_header(id).unwrap();
594
595        assert_eq!(size, 5);
596        assert_eq!(kind, ObjectType::Blob);
597    }
598
599    #[test]
600    fn write() {
601        let td = TempDir::new().unwrap();
602        let repo = Repository::init(td.path()).unwrap();
603        let dat = [4, 3, 5, 6, 9];
604        let db = repo.odb().unwrap();
605        let id = db.write(ObjectType::Blob, &dat).unwrap();
606        let blob = repo.find_blob(id).unwrap();
607        assert_eq!(blob.content(), dat);
608    }
609
610    #[test]
611    fn writer() {
612        let td = TempDir::new().unwrap();
613        let repo = Repository::init(td.path()).unwrap();
614        let dat = [4, 3, 5, 6, 9];
615        let db = repo.odb().unwrap();
616        let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap();
617        let wl = ws.write(&dat[0..3]).unwrap();
618        assert_eq!(wl, 3);
619        let wl = ws.write(&dat[3..5]).unwrap();
620        assert_eq!(wl, 2);
621        let id = ws.finalize().unwrap();
622        let blob = repo.find_blob(id).unwrap();
623        assert_eq!(blob.content(), dat);
624    }
625
626    #[test]
627    fn exists() {
628        let td = TempDir::new().unwrap();
629        let repo = Repository::init(td.path()).unwrap();
630        let dat = [4, 3, 5, 6, 9];
631        let db = repo.odb().unwrap();
632        let id = db.write(ObjectType::Blob, &dat).unwrap();
633        assert!(db.exists(id));
634    }
635
636    #[test]
637    fn exists_prefix() {
638        let td = TempDir::new().unwrap();
639        let repo = Repository::init(td.path()).unwrap();
640        let dat = [4, 3, 5, 6, 9];
641        let db = repo.odb().unwrap();
642        let id = db.write(ObjectType::Blob, &dat).unwrap();
643        let id_prefix_str = &id.to_string()[0..10];
644        let id_prefix = Oid::from_str(id_prefix_str).unwrap();
645        let found_oid = db.exists_prefix(id_prefix, 10).unwrap();
646        assert_eq!(found_oid, id);
647    }
648
649    #[test]
650    fn packwriter() {
651        let (_td, repo_source) = crate::test::repo_init();
652        let (_td, repo_target) = crate::test::repo_init();
653        let mut builder = t!(repo_source.packbuilder());
654        let mut buf = Buf::new();
655        let (commit_source_id, _tree) = crate::test::commit(&repo_source);
656        t!(builder.insert_object(commit_source_id, None));
657        t!(builder.write_buf(&mut buf));
658        let db = repo_target.odb().unwrap();
659        let mut packwriter = db.packwriter().unwrap();
660        packwriter.write(&buf).unwrap();
661        packwriter.commit().unwrap();
662        let commit_target = repo_target.find_commit(commit_source_id).unwrap();
663        assert_eq!(commit_target.id(), commit_source_id);
664    }
665
666    #[test]
667    fn packwriter_progress() {
668        let mut progress_called = false;
669        {
670            let (_td, repo_source) = crate::test::repo_init();
671            let (_td, repo_target) = crate::test::repo_init();
672            let mut builder = t!(repo_source.packbuilder());
673            let mut buf = Buf::new();
674            let (commit_source_id, _tree) = crate::test::commit(&repo_source);
675            t!(builder.insert_object(commit_source_id, None));
676            t!(builder.write_buf(&mut buf));
677            let db = repo_target.odb().unwrap();
678            let mut packwriter = db.packwriter().unwrap();
679            packwriter.progress(|_| {
680                progress_called = true;
681                true
682            });
683            packwriter.write(&buf).unwrap();
684            packwriter.commit().unwrap();
685        }
686        assert_eq!(progress_called, true);
687    }
688
689    #[test]
690    fn write_with_mempack() {
691        use crate::{Buf, ResetType};
692        use std::io::Write;
693        use std::path::Path;
694
695        // Create a repo, add a mempack backend
696        let (_td, repo) = crate::test::repo_init();
697        let odb = repo.odb().unwrap();
698        let mempack = odb.add_new_mempack_backend(1000).unwrap();
699
700        // Sanity check that foo doesn't exist initially
701        let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
702        assert!(!foo_file.exists());
703
704        // Make a commit that adds foo. This writes new stuff into the mempack
705        // backend.
706        let (oid1, _id) = crate::test::commit(&repo);
707        let commit1 = repo.find_commit(oid1).unwrap();
708        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
709        assert!(foo_file.exists());
710
711        // Dump the mempack modifications into a buf, and reset it. This "erases"
712        // commit-related objects from the repository. Ensure the commit appears
713        // to have become invalid, by checking for failure in `reset --hard`.
714        let mut buf = Buf::new();
715        mempack.dump(&repo, &mut buf).unwrap();
716        mempack.reset().unwrap();
717        assert!(repo
718            .reset(commit1.as_object(), ResetType::Hard, None)
719            .is_err());
720
721        // Write the buf into a packfile in the repo. This brings back the
722        // missing objects, and we verify everything is good again.
723        let mut packwriter = odb.packwriter().unwrap();
724        packwriter.write(&buf).unwrap();
725        packwriter.commit().unwrap();
726        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
727        assert!(foo_file.exists());
728    }
729
730    #[test]
731    fn stream_read() {
732        // Test for read impl of OdbReader.
733        const FOO_TEXT: &[u8] = b"this is a test";
734        let (_td, repo) = crate::test::repo_init();
735        let p = repo.path().parent().unwrap().join("foo");
736        std::fs::write(&p, FOO_TEXT).unwrap();
737        let mut index = repo.index().unwrap();
738        index.add_path(std::path::Path::new("foo")).unwrap();
739        let tree_id = index.write_tree().unwrap();
740        let tree = repo.find_tree(tree_id).unwrap();
741        let sig = repo.signature().unwrap();
742        let head_id = repo.refname_to_id("HEAD").unwrap();
743        let parent = repo.find_commit(head_id).unwrap();
744        let _commit = repo
745            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
746            .unwrap();
747
748        // Try reading from a commit object.
749        let odb = repo.odb().unwrap();
750        let oid = repo.refname_to_id("HEAD").unwrap();
751        let (mut reader, size, ty) = odb.reader(oid).unwrap();
752        assert!(ty == ObjectType::Commit);
753        let mut x = [0; 10000];
754        let r = reader.read(&mut x).unwrap();
755        assert!(r == size);
756
757        // Try reading from a blob. This assumes it is a loose object (packed
758        // objects can't read).
759        let commit = repo.find_commit(oid).unwrap();
760        let tree = commit.tree().unwrap();
761        let entry = tree.get_name("foo").unwrap();
762        let (mut reader, size, ty) = odb.reader(entry.id()).unwrap();
763        assert_eq!(size, FOO_TEXT.len());
764        assert!(ty == ObjectType::Blob);
765        let mut x = [0; 10000];
766        let r = reader.read(&mut x).unwrap();
767        assert_eq!(r, 14);
768        assert_eq!(&x[..FOO_TEXT.len()], FOO_TEXT);
769    }
770}