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)
}
}
}
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);
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)),
}
};
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,
})
}
}