##// END OF EJS Templates
rhg: do not fail when the repo is empty...
Arseniy Alekseyev -
r49013:9d0e5629 default
parent child Browse files
Show More
@@ -1,423 +1,429 b''
1 use std::borrow::Cow;
1 use std::borrow::Cow;
2 use std::io::Read;
2 use std::io::Read;
3 use std::ops::Deref;
3 use std::ops::Deref;
4 use std::path::Path;
4 use std::path::Path;
5
5
6 use byteorder::{BigEndian, ByteOrder};
6 use byteorder::{BigEndian, ByteOrder};
7 use flate2::read::ZlibDecoder;
7 use flate2::read::ZlibDecoder;
8 use micro_timer::timed;
8 use micro_timer::timed;
9 use sha1::{Digest, Sha1};
9 use sha1::{Digest, Sha1};
10 use zstd;
10 use zstd;
11
11
12 use super::index::Index;
12 use super::index::Index;
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
14 use super::nodemap;
14 use super::nodemap;
15 use super::nodemap::{NodeMap, NodeMapError};
15 use super::nodemap::{NodeMap, NodeMapError};
16 use super::nodemap_docket::NodeMapDocket;
16 use super::nodemap_docket::NodeMapDocket;
17 use super::patch;
17 use super::patch;
18 use crate::errors::HgError;
18 use crate::errors::HgError;
19 use crate::repo::Repo;
19 use crate::repo::Repo;
20 use crate::revlog::Revision;
20 use crate::revlog::Revision;
21 use crate::{Node, NULL_REVISION};
21 use crate::{Node, NULL_REVISION};
22
22
23 #[derive(derive_more::From)]
23 #[derive(derive_more::From)]
24 pub enum RevlogError {
24 pub enum RevlogError {
25 InvalidRevision,
25 InvalidRevision,
26 /// Working directory is not supported
26 /// Working directory is not supported
27 WDirUnsupported,
27 WDirUnsupported,
28 /// Found more than one entry whose ID match the requested prefix
28 /// Found more than one entry whose ID match the requested prefix
29 AmbiguousPrefix,
29 AmbiguousPrefix,
30 #[from]
30 #[from]
31 Other(HgError),
31 Other(HgError),
32 }
32 }
33
33
34 impl From<NodeMapError> for RevlogError {
34 impl From<NodeMapError> for RevlogError {
35 fn from(error: NodeMapError) -> Self {
35 fn from(error: NodeMapError) -> Self {
36 match error {
36 match error {
37 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
37 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
38 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
38 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
39 }
39 }
40 }
40 }
41 }
41 }
42
42
43 impl RevlogError {
43 impl RevlogError {
44 fn corrupted() -> Self {
44 fn corrupted() -> Self {
45 RevlogError::Other(HgError::corrupted("corrupted revlog"))
45 RevlogError::Other(HgError::corrupted("corrupted revlog"))
46 }
46 }
47 }
47 }
48
48
49 /// Read only implementation of revlog.
49 /// Read only implementation of revlog.
50 pub struct Revlog {
50 pub struct Revlog {
51 /// When index and data are not interleaved: bytes of the revlog index.
51 /// When index and data are not interleaved: bytes of the revlog index.
52 /// When index and data are interleaved: bytes of the revlog index and
52 /// When index and data are interleaved: bytes of the revlog index and
53 /// data.
53 /// data.
54 index: Index,
54 index: Index,
55 /// When index and data are not interleaved: bytes of the revlog data
55 /// When index and data are not interleaved: bytes of the revlog data
56 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
56 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
57 /// When present on disk: the persistent nodemap for this revlog
57 /// When present on disk: the persistent nodemap for this revlog
58 nodemap: Option<nodemap::NodeTree>,
58 nodemap: Option<nodemap::NodeTree>,
59 }
59 }
60
60
61 impl Revlog {
61 impl Revlog {
62 /// Open a revlog index file.
62 /// Open a revlog index file.
63 ///
63 ///
64 /// It will also open the associated data file if index and data are not
64 /// It will also open the associated data file if index and data are not
65 /// interleaved.
65 /// interleaved.
66 #[timed]
66 #[timed]
67 pub fn open(
67 pub fn open(
68 repo: &Repo,
68 repo: &Repo,
69 index_path: impl AsRef<Path>,
69 index_path: impl AsRef<Path>,
70 data_path: Option<&Path>,
70 data_path: Option<&Path>,
71 ) -> Result<Self, HgError> {
71 ) -> Result<Self, HgError> {
72 let index_path = index_path.as_ref();
72 let index_path = index_path.as_ref();
73 let index_mmap = repo.store_vfs().mmap_open(&index_path)?;
73 let index = {
74 match repo.store_vfs().mmap_open_opt(&index_path)? {
75 None => Index::new(Box::new(vec![])),
76 Some(index_mmap) => {
77 let version = get_version(&index_mmap)?;
78 if version != 1 {
79 // A proper new version should have had a repo/store requirement.
80 return Err(HgError::corrupted("corrupted revlog"));
81 }
74
82
75 let version = get_version(&index_mmap)?;
83 let index = Index::new(Box::new(index_mmap))?;
76 if version != 1 {
84 Ok(index)
77 // A proper new version should have had a repo/store requirement.
85 }
78 return Err(HgError::corrupted("corrupted revlog"));
86 }
79 }
87 }?;
80
81 let index = Index::new(Box::new(index_mmap))?;
82
88
83 let default_data_path = index_path.with_extension("d");
89 let default_data_path = index_path.with_extension("d");
84
90
85 // type annotation required
91 // type annotation required
86 // won't recognize Mmap as Deref<Target = [u8]>
92 // won't recognize Mmap as Deref<Target = [u8]>
87 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
93 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
88 if index.is_inline() {
94 if index.is_inline() {
89 None
95 None
90 } else {
96 } else {
91 let data_path = data_path.unwrap_or(&default_data_path);
97 let data_path = data_path.unwrap_or(&default_data_path);
92 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
98 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
93 Some(Box::new(data_mmap))
99 Some(Box::new(data_mmap))
94 };
100 };
95
101
96 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
102 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
97 |(docket, data)| {
103 |(docket, data)| {
98 nodemap::NodeTree::load_bytes(
104 nodemap::NodeTree::load_bytes(
99 Box::new(data),
105 Box::new(data),
100 docket.data_length,
106 docket.data_length,
101 )
107 )
102 },
108 },
103 );
109 );
104
110
105 Ok(Revlog {
111 Ok(Revlog {
106 index,
112 index,
107 data_bytes,
113 data_bytes,
108 nodemap,
114 nodemap,
109 })
115 })
110 }
116 }
111
117
112 /// Return number of entries of the `Revlog`.
118 /// Return number of entries of the `Revlog`.
113 pub fn len(&self) -> usize {
119 pub fn len(&self) -> usize {
114 self.index.len()
120 self.index.len()
115 }
121 }
116
122
117 /// Returns `true` if the `Revlog` has zero `entries`.
123 /// Returns `true` if the `Revlog` has zero `entries`.
118 pub fn is_empty(&self) -> bool {
124 pub fn is_empty(&self) -> bool {
119 self.index.is_empty()
125 self.index.is_empty()
120 }
126 }
121
127
122 /// Returns the node ID for the given revision number, if it exists in this
128 /// Returns the node ID for the given revision number, if it exists in this
123 /// revlog
129 /// revlog
124 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
130 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
125 Some(self.index.get_entry(rev)?.hash())
131 Some(self.index.get_entry(rev)?.hash())
126 }
132 }
127
133
128 /// Return the revision number for the given node ID, if it exists in this
134 /// Return the revision number for the given node ID, if it exists in this
129 /// revlog
135 /// revlog
130 #[timed]
136 #[timed]
131 pub fn rev_from_node(
137 pub fn rev_from_node(
132 &self,
138 &self,
133 node: NodePrefix,
139 node: NodePrefix,
134 ) -> Result<Revision, RevlogError> {
140 ) -> Result<Revision, RevlogError> {
135 if node.is_prefix_of(&NULL_NODE) {
141 if node.is_prefix_of(&NULL_NODE) {
136 return Ok(NULL_REVISION);
142 return Ok(NULL_REVISION);
137 }
143 }
138
144
139 if let Some(nodemap) = &self.nodemap {
145 if let Some(nodemap) = &self.nodemap {
140 return nodemap
146 return nodemap
141 .find_bin(&self.index, node)?
147 .find_bin(&self.index, node)?
142 .ok_or(RevlogError::InvalidRevision);
148 .ok_or(RevlogError::InvalidRevision);
143 }
149 }
144
150
145 // Fallback to linear scan when a persistent nodemap is not present.
151 // Fallback to linear scan when a persistent nodemap is not present.
146 // This happens when the persistent-nodemap experimental feature is not
152 // This happens when the persistent-nodemap experimental feature is not
147 // enabled, or for small revlogs.
153 // enabled, or for small revlogs.
148 //
154 //
149 // TODO: consider building a non-persistent nodemap in memory to
155 // TODO: consider building a non-persistent nodemap in memory to
150 // optimize these cases.
156 // optimize these cases.
151 let mut found_by_prefix = None;
157 let mut found_by_prefix = None;
152 for rev in (0..self.len() as Revision).rev() {
158 for rev in (0..self.len() as Revision).rev() {
153 let index_entry =
159 let index_entry =
154 self.index.get_entry(rev).ok_or(HgError::corrupted(
160 self.index.get_entry(rev).ok_or(HgError::corrupted(
155 "revlog references a revision not in the index",
161 "revlog references a revision not in the index",
156 ))?;
162 ))?;
157 if node == *index_entry.hash() {
163 if node == *index_entry.hash() {
158 return Ok(rev);
164 return Ok(rev);
159 }
165 }
160 if node.is_prefix_of(index_entry.hash()) {
166 if node.is_prefix_of(index_entry.hash()) {
161 if found_by_prefix.is_some() {
167 if found_by_prefix.is_some() {
162 return Err(RevlogError::AmbiguousPrefix);
168 return Err(RevlogError::AmbiguousPrefix);
163 }
169 }
164 found_by_prefix = Some(rev)
170 found_by_prefix = Some(rev)
165 }
171 }
166 }
172 }
167 found_by_prefix.ok_or(RevlogError::InvalidRevision)
173 found_by_prefix.ok_or(RevlogError::InvalidRevision)
168 }
174 }
169
175
170 /// Returns whether the given revision exists in this revlog.
176 /// Returns whether the given revision exists in this revlog.
171 pub fn has_rev(&self, rev: Revision) -> bool {
177 pub fn has_rev(&self, rev: Revision) -> bool {
172 self.index.get_entry(rev).is_some()
178 self.index.get_entry(rev).is_some()
173 }
179 }
174
180
175 /// Return the full data associated to a revision.
181 /// Return the full data associated to a revision.
176 ///
182 ///
177 /// All entries required to build the final data out of deltas will be
183 /// All entries required to build the final data out of deltas will be
178 /// retrieved as needed, and the deltas will be applied to the inital
184 /// retrieved as needed, and the deltas will be applied to the inital
179 /// snapshot to rebuild the final data.
185 /// snapshot to rebuild the final data.
180 #[timed]
186 #[timed]
181 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
187 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
182 if rev == NULL_REVISION {
188 if rev == NULL_REVISION {
183 return Ok(vec![]);
189 return Ok(vec![]);
184 };
190 };
185 // Todo return -> Cow
191 // Todo return -> Cow
186 let mut entry = self.get_entry(rev)?;
192 let mut entry = self.get_entry(rev)?;
187 let mut delta_chain = vec![];
193 let mut delta_chain = vec![];
188 while let Some(base_rev) = entry.base_rev {
194 while let Some(base_rev) = entry.base_rev {
189 delta_chain.push(entry);
195 delta_chain.push(entry);
190 entry = self
196 entry = self
191 .get_entry(base_rev)
197 .get_entry(base_rev)
192 .map_err(|_| RevlogError::corrupted())?;
198 .map_err(|_| RevlogError::corrupted())?;
193 }
199 }
194
200
195 // TODO do not look twice in the index
201 // TODO do not look twice in the index
196 let index_entry = self
202 let index_entry = self
197 .index
203 .index
198 .get_entry(rev)
204 .get_entry(rev)
199 .ok_or(RevlogError::InvalidRevision)?;
205 .ok_or(RevlogError::InvalidRevision)?;
200
206
201 let data: Vec<u8> = if delta_chain.is_empty() {
207 let data: Vec<u8> = if delta_chain.is_empty() {
202 entry.data()?.into()
208 entry.data()?.into()
203 } else {
209 } else {
204 Revlog::build_data_from_deltas(entry, &delta_chain)?
210 Revlog::build_data_from_deltas(entry, &delta_chain)?
205 };
211 };
206
212
207 if self.check_hash(
213 if self.check_hash(
208 index_entry.p1(),
214 index_entry.p1(),
209 index_entry.p2(),
215 index_entry.p2(),
210 index_entry.hash().as_bytes(),
216 index_entry.hash().as_bytes(),
211 &data,
217 &data,
212 ) {
218 ) {
213 Ok(data)
219 Ok(data)
214 } else {
220 } else {
215 Err(RevlogError::corrupted())
221 Err(RevlogError::corrupted())
216 }
222 }
217 }
223 }
218
224
219 /// Check the hash of some given data against the recorded hash.
225 /// Check the hash of some given data against the recorded hash.
220 pub fn check_hash(
226 pub fn check_hash(
221 &self,
227 &self,
222 p1: Revision,
228 p1: Revision,
223 p2: Revision,
229 p2: Revision,
224 expected: &[u8],
230 expected: &[u8],
225 data: &[u8],
231 data: &[u8],
226 ) -> bool {
232 ) -> bool {
227 let e1 = self.index.get_entry(p1);
233 let e1 = self.index.get_entry(p1);
228 let h1 = match e1 {
234 let h1 = match e1 {
229 Some(ref entry) => entry.hash(),
235 Some(ref entry) => entry.hash(),
230 None => &NULL_NODE,
236 None => &NULL_NODE,
231 };
237 };
232 let e2 = self.index.get_entry(p2);
238 let e2 = self.index.get_entry(p2);
233 let h2 = match e2 {
239 let h2 = match e2 {
234 Some(ref entry) => entry.hash(),
240 Some(ref entry) => entry.hash(),
235 None => &NULL_NODE,
241 None => &NULL_NODE,
236 };
242 };
237
243
238 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
244 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
239 }
245 }
240
246
241 /// Build the full data of a revision out its snapshot
247 /// Build the full data of a revision out its snapshot
242 /// and its deltas.
248 /// and its deltas.
243 #[timed]
249 #[timed]
244 fn build_data_from_deltas(
250 fn build_data_from_deltas(
245 snapshot: RevlogEntry,
251 snapshot: RevlogEntry,
246 deltas: &[RevlogEntry],
252 deltas: &[RevlogEntry],
247 ) -> Result<Vec<u8>, RevlogError> {
253 ) -> Result<Vec<u8>, RevlogError> {
248 let snapshot = snapshot.data()?;
254 let snapshot = snapshot.data()?;
249 let deltas = deltas
255 let deltas = deltas
250 .iter()
256 .iter()
251 .rev()
257 .rev()
252 .map(RevlogEntry::data)
258 .map(RevlogEntry::data)
253 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
259 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
254 let patches: Vec<_> =
260 let patches: Vec<_> =
255 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
261 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
256 let patch = patch::fold_patch_lists(&patches);
262 let patch = patch::fold_patch_lists(&patches);
257 Ok(patch.apply(&snapshot))
263 Ok(patch.apply(&snapshot))
258 }
264 }
259
265
260 /// Return the revlog data.
266 /// Return the revlog data.
261 fn data(&self) -> &[u8] {
267 fn data(&self) -> &[u8] {
262 match self.data_bytes {
268 match self.data_bytes {
263 Some(ref data_bytes) => &data_bytes,
269 Some(ref data_bytes) => &data_bytes,
264 None => panic!(
270 None => panic!(
265 "forgot to load the data or trying to access inline data"
271 "forgot to load the data or trying to access inline data"
266 ),
272 ),
267 }
273 }
268 }
274 }
269
275
270 /// Get an entry of the revlog.
276 /// Get an entry of the revlog.
271 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
277 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
272 let index_entry = self
278 let index_entry = self
273 .index
279 .index
274 .get_entry(rev)
280 .get_entry(rev)
275 .ok_or(RevlogError::InvalidRevision)?;
281 .ok_or(RevlogError::InvalidRevision)?;
276 let start = index_entry.offset();
282 let start = index_entry.offset();
277 let end = start + index_entry.compressed_len();
283 let end = start + index_entry.compressed_len();
278 let data = if self.index.is_inline() {
284 let data = if self.index.is_inline() {
279 self.index.data(start, end)
285 self.index.data(start, end)
280 } else {
286 } else {
281 &self.data()[start..end]
287 &self.data()[start..end]
282 };
288 };
283 let entry = RevlogEntry {
289 let entry = RevlogEntry {
284 rev,
290 rev,
285 bytes: data,
291 bytes: data,
286 compressed_len: index_entry.compressed_len(),
292 compressed_len: index_entry.compressed_len(),
287 uncompressed_len: index_entry.uncompressed_len(),
293 uncompressed_len: index_entry.uncompressed_len(),
288 base_rev: if index_entry.base_revision() == rev {
294 base_rev: if index_entry.base_revision() == rev {
289 None
295 None
290 } else {
296 } else {
291 Some(index_entry.base_revision())
297 Some(index_entry.base_revision())
292 },
298 },
293 };
299 };
294 Ok(entry)
300 Ok(entry)
295 }
301 }
296 }
302 }
297
303
298 /// The revlog entry's bytes and the necessary informations to extract
304 /// The revlog entry's bytes and the necessary informations to extract
299 /// the entry's data.
305 /// the entry's data.
300 #[derive(Debug)]
306 #[derive(Debug)]
301 pub struct RevlogEntry<'a> {
307 pub struct RevlogEntry<'a> {
302 rev: Revision,
308 rev: Revision,
303 bytes: &'a [u8],
309 bytes: &'a [u8],
304 compressed_len: usize,
310 compressed_len: usize,
305 uncompressed_len: usize,
311 uncompressed_len: usize,
306 base_rev: Option<Revision>,
312 base_rev: Option<Revision>,
307 }
313 }
308
314
309 impl<'a> RevlogEntry<'a> {
315 impl<'a> RevlogEntry<'a> {
310 pub fn revision(&self) -> Revision {
316 pub fn revision(&self) -> Revision {
311 self.rev
317 self.rev
312 }
318 }
313
319
314 /// Extract the data contained in the entry.
320 /// Extract the data contained in the entry.
315 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
321 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
316 if self.bytes.is_empty() {
322 if self.bytes.is_empty() {
317 return Ok(Cow::Borrowed(&[]));
323 return Ok(Cow::Borrowed(&[]));
318 }
324 }
319 match self.bytes[0] {
325 match self.bytes[0] {
320 // Revision data is the entirety of the entry, including this
326 // Revision data is the entirety of the entry, including this
321 // header.
327 // header.
322 b'\0' => Ok(Cow::Borrowed(self.bytes)),
328 b'\0' => Ok(Cow::Borrowed(self.bytes)),
323 // Raw revision data follows.
329 // Raw revision data follows.
324 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
330 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
325 // zlib (RFC 1950) data.
331 // zlib (RFC 1950) data.
326 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
332 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
327 // zstd data.
333 // zstd data.
328 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
334 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
329 // A proper new format should have had a repo/store requirement.
335 // A proper new format should have had a repo/store requirement.
330 _format_type => Err(RevlogError::corrupted()),
336 _format_type => Err(RevlogError::corrupted()),
331 }
337 }
332 }
338 }
333
339
334 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
340 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
335 let mut decoder = ZlibDecoder::new(self.bytes);
341 let mut decoder = ZlibDecoder::new(self.bytes);
336 if self.is_delta() {
342 if self.is_delta() {
337 let mut buf = Vec::with_capacity(self.compressed_len);
343 let mut buf = Vec::with_capacity(self.compressed_len);
338 decoder
344 decoder
339 .read_to_end(&mut buf)
345 .read_to_end(&mut buf)
340 .map_err(|_| RevlogError::corrupted())?;
346 .map_err(|_| RevlogError::corrupted())?;
341 Ok(buf)
347 Ok(buf)
342 } else {
348 } else {
343 let mut buf = vec![0; self.uncompressed_len];
349 let mut buf = vec![0; self.uncompressed_len];
344 decoder
350 decoder
345 .read_exact(&mut buf)
351 .read_exact(&mut buf)
346 .map_err(|_| RevlogError::corrupted())?;
352 .map_err(|_| RevlogError::corrupted())?;
347 Ok(buf)
353 Ok(buf)
348 }
354 }
349 }
355 }
350
356
351 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
357 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
352 if self.is_delta() {
358 if self.is_delta() {
353 let mut buf = Vec::with_capacity(self.compressed_len);
359 let mut buf = Vec::with_capacity(self.compressed_len);
354 zstd::stream::copy_decode(self.bytes, &mut buf)
360 zstd::stream::copy_decode(self.bytes, &mut buf)
355 .map_err(|_| RevlogError::corrupted())?;
361 .map_err(|_| RevlogError::corrupted())?;
356 Ok(buf)
362 Ok(buf)
357 } else {
363 } else {
358 let mut buf = vec![0; self.uncompressed_len];
364 let mut buf = vec![0; self.uncompressed_len];
359 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
365 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
360 .map_err(|_| RevlogError::corrupted())?;
366 .map_err(|_| RevlogError::corrupted())?;
361 if len != self.uncompressed_len {
367 if len != self.uncompressed_len {
362 Err(RevlogError::corrupted())
368 Err(RevlogError::corrupted())
363 } else {
369 } else {
364 Ok(buf)
370 Ok(buf)
365 }
371 }
366 }
372 }
367 }
373 }
368
374
369 /// Tell if the entry is a snapshot or a delta
375 /// Tell if the entry is a snapshot or a delta
370 /// (influences on decompression).
376 /// (influences on decompression).
371 fn is_delta(&self) -> bool {
377 fn is_delta(&self) -> bool {
372 self.base_rev.is_some()
378 self.base_rev.is_some()
373 }
379 }
374 }
380 }
375
381
376 /// Format version of the revlog.
382 /// Format version of the revlog.
377 pub fn get_version(index_bytes: &[u8]) -> Result<u16, HgError> {
383 pub fn get_version(index_bytes: &[u8]) -> Result<u16, HgError> {
378 if index_bytes.len() == 0 {
384 if index_bytes.len() == 0 {
379 return Ok(1);
385 return Ok(1);
380 };
386 };
381 if index_bytes.len() < 4 {
387 if index_bytes.len() < 4 {
382 return Err(HgError::corrupted(
388 return Err(HgError::corrupted(
383 "corrupted revlog: can't read the index format header",
389 "corrupted revlog: can't read the index format header",
384 ));
390 ));
385 };
391 };
386 Ok(BigEndian::read_u16(&index_bytes[2..=3]))
392 Ok(BigEndian::read_u16(&index_bytes[2..=3]))
387 }
393 }
388
394
389 /// Calculate the hash of a revision given its data and its parents.
395 /// Calculate the hash of a revision given its data and its parents.
390 fn hash(
396 fn hash(
391 data: &[u8],
397 data: &[u8],
392 p1_hash: &[u8],
398 p1_hash: &[u8],
393 p2_hash: &[u8],
399 p2_hash: &[u8],
394 ) -> [u8; NODE_BYTES_LENGTH] {
400 ) -> [u8; NODE_BYTES_LENGTH] {
395 let mut hasher = Sha1::new();
401 let mut hasher = Sha1::new();
396 let (a, b) = (p1_hash, p2_hash);
402 let (a, b) = (p1_hash, p2_hash);
397 if a > b {
403 if a > b {
398 hasher.update(b);
404 hasher.update(b);
399 hasher.update(a);
405 hasher.update(a);
400 } else {
406 } else {
401 hasher.update(a);
407 hasher.update(a);
402 hasher.update(b);
408 hasher.update(b);
403 }
409 }
404 hasher.update(data);
410 hasher.update(data);
405 *hasher.finalize().as_ref()
411 *hasher.finalize().as_ref()
406 }
412 }
407
413
408 #[cfg(test)]
414 #[cfg(test)]
409 mod tests {
415 mod tests {
410 use super::*;
416 use super::*;
411
417
412 use super::super::index::IndexEntryBuilder;
418 use super::super::index::IndexEntryBuilder;
413
419
414 #[test]
420 #[test]
415 fn version_test() {
421 fn version_test() {
416 let bytes = IndexEntryBuilder::new()
422 let bytes = IndexEntryBuilder::new()
417 .is_first(true)
423 .is_first(true)
418 .with_version(1)
424 .with_version(1)
419 .build();
425 .build();
420
426
421 assert_eq!(get_version(&bytes), 1)
427 assert_eq!(get_version(&bytes).map_err(|_err|()), Ok(1))
422 }
428 }
423 }
429 }
@@ -1,73 +1,100 b''
1 use crate::errors::{HgError, IoErrorContext, IoResultExt};
1 use crate::errors::{HgError, IoErrorContext, IoResultExt};
2 use memmap2::{Mmap, MmapOptions};
2 use memmap2::{Mmap, MmapOptions};
3 use std::io::ErrorKind;
3 use std::io::ErrorKind;
4 use std::path::{Path, PathBuf};
4 use std::path::{Path, PathBuf};
5
5
6 /// Filesystem access abstraction for the contents of a given "base" diretory
6 /// Filesystem access abstraction for the contents of a given "base" diretory
7 #[derive(Clone, Copy)]
7 #[derive(Clone, Copy)]
8 pub struct Vfs<'a> {
8 pub struct Vfs<'a> {
9 pub(crate) base: &'a Path,
9 pub(crate) base: &'a Path,
10 }
10 }
11
11
12 struct FileNotFound(std::io::Error, PathBuf);
13
12 impl Vfs<'_> {
14 impl Vfs<'_> {
13 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
15 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
14 self.base.join(relative_path)
16 self.base.join(relative_path)
15 }
17 }
16
18
17 pub fn read(
19 pub fn read(
18 &self,
20 &self,
19 relative_path: impl AsRef<Path>,
21 relative_path: impl AsRef<Path>,
20 ) -> Result<Vec<u8>, HgError> {
22 ) -> Result<Vec<u8>, HgError> {
21 let path = self.join(relative_path);
23 let path = self.join(relative_path);
22 std::fs::read(&path).when_reading_file(&path)
24 std::fs::read(&path).when_reading_file(&path)
23 }
25 }
24
26
27 fn mmap_open_gen(
28 &self,
29 relative_path: impl AsRef<Path>,
30 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
31 let path = self.join(relative_path);
32 let file = match std::fs::File::open(&path) {
33 Err(err) => {
34 if let ErrorKind::NotFound = err.kind() {
35 return Ok(Err(FileNotFound(err, path)));
36 };
37 return (Err(err)).when_reading_file(&path);
38 }
39 Ok(file) => file,
40 };
41 // TODO: what are the safety requirements here?
42 let mmap = unsafe { MmapOptions::new().map(&file) }
43 .when_reading_file(&path)?;
44 Ok(Ok(mmap))
45 }
46
47 pub fn mmap_open_opt(
48 &self,
49 relative_path: impl AsRef<Path>,
50 ) -> Result<Option<Mmap>, HgError> {
51 self.mmap_open_gen(relative_path).map(|res| res.ok())
52 }
53
25 pub fn mmap_open(
54 pub fn mmap_open(
26 &self,
55 &self,
27 relative_path: impl AsRef<Path>,
56 relative_path: impl AsRef<Path>,
28 ) -> Result<Mmap, HgError> {
57 ) -> Result<Mmap, HgError> {
29 let path = self.base.join(relative_path);
58 match self.mmap_open_gen(relative_path)? {
30 let file = std::fs::File::open(&path).when_reading_file(&path)?;
59 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
31 // TODO: what are the safety requirements here?
60 Ok(res) => Ok(res),
32 let mmap = unsafe { MmapOptions::new().map(&file) }
61 }
33 .when_reading_file(&path)?;
34 Ok(mmap)
35 }
62 }
36
63
37 pub fn rename(
64 pub fn rename(
38 &self,
65 &self,
39 relative_from: impl AsRef<Path>,
66 relative_from: impl AsRef<Path>,
40 relative_to: impl AsRef<Path>,
67 relative_to: impl AsRef<Path>,
41 ) -> Result<(), HgError> {
68 ) -> Result<(), HgError> {
42 let from = self.join(relative_from);
69 let from = self.join(relative_from);
43 let to = self.join(relative_to);
70 let to = self.join(relative_to);
44 std::fs::rename(&from, &to)
71 std::fs::rename(&from, &to)
45 .with_context(|| IoErrorContext::RenamingFile { from, to })
72 .with_context(|| IoErrorContext::RenamingFile { from, to })
46 }
73 }
47 }
74 }
48
75
49 fn fs_metadata(
76 fn fs_metadata(
50 path: impl AsRef<Path>,
77 path: impl AsRef<Path>,
51 ) -> Result<Option<std::fs::Metadata>, HgError> {
78 ) -> Result<Option<std::fs::Metadata>, HgError> {
52 let path = path.as_ref();
79 let path = path.as_ref();
53 match std::fs::metadata(path) {
80 match std::fs::metadata(path) {
54 Ok(meta) => Ok(Some(meta)),
81 Ok(meta) => Ok(Some(meta)),
55 Err(error) => match error.kind() {
82 Err(error) => match error.kind() {
56 // TODO: when we require a Rust version where `NotADirectory` is
83 // TODO: when we require a Rust version where `NotADirectory` is
57 // stable, invert this logic and return None for it and `NotFound`
84 // stable, invert this logic and return None for it and `NotFound`
58 // and propagate any other error.
85 // and propagate any other error.
59 ErrorKind::PermissionDenied => Err(error).with_context(|| {
86 ErrorKind::PermissionDenied => Err(error).with_context(|| {
60 IoErrorContext::ReadingMetadata(path.to_owned())
87 IoErrorContext::ReadingMetadata(path.to_owned())
61 }),
88 }),
62 _ => Ok(None),
89 _ => Ok(None),
63 },
90 },
64 }
91 }
65 }
92 }
66
93
67 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
94 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
68 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
95 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
69 }
96 }
70
97
71 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
98 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
72 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
99 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
73 }
100 }
@@ -1,23 +1,27 b''
1 Create a repo such that the changelog entry refers to a null manifest node:
1 Test null revisions (node 0000000000000000000000000000000000000000, aka rev -1)
2 in various circumstances.
3
4 Make an empty repo:
2
5
3 $ hg init a
6 $ hg init a
4 $ cd a
7 $ cd a
5 $ hg log
6 $ touch x
7 $ hg add x
8 $ hg commit -m "init"
9 $ hg rm x
10 $ hg commit -q --amend
11
8
12 $ wc -c < .hg/store/00manifest.i
9 $ hg files -r 0000000000000000000000000000000000000000
13 0
10 [1]
14
11 $ hg files -r .
15 Make sure that the manifest can be read (and is empty):
16
17 $ hg --config rhg.on-unsupported=abort files -r .
18 [1]
12 [1]
19
13
20 Test a null changelog rev, too:
14 Add an empty commit (this makes the changelog refer to a null manifest node):
15
16
17 $ hg commit -m "init" --config ui.allowemptycommit=true
21
18
22 $ hg --config rhg.on-unsupported=abort files -r 0000000000000000000000000000000000000000
19 $ hg files -r .
23 [1]
20 [1]
21
22 Strip that empty commit (this makes the changelog file empty, as opposed to missing):
23
24 $ hg --config 'extensions.strip=' strip . > /dev/null
25
26 $ hg files -r .
27 [1]
General Comments 0
You need to be logged in to leave comments. Login now