1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use crate::{
    loose::{db::sha1_path, object::header, Db, Object, HEADER_READ_COMPRESSED_BYTES, HEADER_READ_UNCOMPRESSED_BYTES},
    zlib,
};
use git_object as object;
use object::borrowed;
use quick_error::quick_error;
use smallvec::SmallVec;
use std::{convert::TryInto, fs, io::Read, path::PathBuf};

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        DecompressFile(err: zlib::Error, path: PathBuf) {
            display("decompression of loose object at '{}' failed", path.display())
            source(err)
        }
        Decode(err: header::Error) {
            display("Could not decode header")
            from()
            source(err)
        }
        Io(err: std::io::Error, action: &'static str, path: PathBuf) {
            display("Could not {} data at '{}'", action, path.display())
            source(err)
        }
    }
}

/// Object lookup
impl Db {
    const OPEN_ACTION: &'static str = "open";

    pub fn locate(&self, id: borrowed::Id) -> Option<Result<Object, Error>> {
        match self.locate_inner(id) {
            Ok(obj) => Some(Ok(obj)),
            Err(err) => match err {
                Error::Io(err, action, path) => {
                    if action == Self::OPEN_ACTION {
                        None
                    } else {
                        Some(Err(Error::Io(err, action, path)))
                    }
                }
                err => Some(Err(err)),
            },
        }
    }

    fn locate_inner(&self, id: borrowed::Id) -> Result<Object, Error> {
        let path = sha1_path(id, self.path.clone());

        let mut inflate = zlib::Inflate::default();
        let mut decompressed = [0; HEADER_READ_UNCOMPRESSED_BYTES];
        let mut compressed = [0; HEADER_READ_COMPRESSED_BYTES];
        let ((_status, _consumed_in, consumed_out), bytes_read, mut input_stream) = {
            let mut istream = fs::File::open(&path).map_err(|e| Error::Io(e, Self::OPEN_ACTION, path.to_owned()))?;
            let bytes_read = istream
                .read(&mut compressed[..])
                .map_err(|e| Error::Io(e, "read", path.to_owned()))?;
            (
                inflate
                    .once(&compressed[..bytes_read], &mut decompressed[..], true)
                    .map_err(|e| Error::DecompressFile(e, path.to_owned()))?,
                bytes_read,
                istream,
            )
        };

        let (kind, size, header_size) = header::decode(&decompressed[..consumed_out])?;
        let mut decompressed = SmallVec::from_buf(decompressed);
        decompressed.resize(consumed_out, 0);

        let (compressed, path) = if inflate.is_done {
            (SmallVec::default(), None)
        } else {
            match kind {
                object::Kind::Tree | object::Kind::Commit | object::Kind::Tag => {
                    let mut compressed = SmallVec::from_buf(compressed);
                    // Read small objects right away and store them in memory while we
                    // have a data handle available and 'hot'. Note that we don't decompress yet!
                    let file_size = input_stream
                        .metadata()
                        .map_err(|e| Error::Io(e, "read metadata", path.to_owned()))?
                        .len();
                    assert!(file_size <= ::std::usize::MAX as u64);
                    let file_size = file_size as usize;
                    if bytes_read == file_size {
                        (compressed, None)
                    } else {
                        let cap = compressed.capacity();
                        if cap < file_size {
                            compressed.reserve_exact(file_size - cap);
                            debug_assert!(file_size == compressed.capacity());
                        }

                        compressed.resize(file_size, 0);
                        input_stream
                            .read_exact(&mut compressed[bytes_read..])
                            .map_err(|e| Error::Io(e, "read", path.to_owned()))?;
                        (compressed, None)
                    }
                }
                object::Kind::Blob => (SmallVec::default(), Some(path)), // we will open the data again when needed. Maybe we can load small sized objects anyway
            }
        };

        Ok(Object {
            kind,
            size: size.try_into().expect("actual size to potentially fit into memory"),
            decompressed_data: decompressed,
            compressed_data: compressed,
            header_size,
            path,
            decompression_complete: inflate.is_done,
        })
    }
}