Show More
@@ -951,7 +951,7 b' impl OwningDirstateMap {' | |||||
951 | pub fn pack_v2( |
|
951 | pub fn pack_v2( | |
952 | &self, |
|
952 | &self, | |
953 | can_append: bool, |
|
953 | can_append: bool, | |
954 |
) -> Result<(Vec<u8>, |
|
954 | ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> { | |
955 | let map = self.get_map(); |
|
955 | let map = self.get_map(); | |
956 | on_disk::write(map, can_append) |
|
956 | on_disk::write(map, can_append) | |
957 | } |
|
957 | } |
@@ -14,8 +14,10 b' use bitflags::bitflags;' | |||||
14 | use bytes_cast::unaligned::{U16Be, U32Be}; |
|
14 | use bytes_cast::unaligned::{U16Be, U32Be}; | |
15 | use bytes_cast::BytesCast; |
|
15 | use bytes_cast::BytesCast; | |
16 | use format_bytes::format_bytes; |
|
16 | use format_bytes::format_bytes; | |
|
17 | use rand::Rng; | |||
17 | use std::borrow::Cow; |
|
18 | use std::borrow::Cow; | |
18 | use std::convert::{TryFrom, TryInto}; |
|
19 | use std::convert::{TryFrom, TryInto}; | |
|
20 | use std::fmt::Write; | |||
19 |
|
21 | |||
20 | /// Added at the start of `.hg/dirstate` when the "v2" format is used. |
|
22 | /// Added at the start of `.hg/dirstate` when the "v2" format is used. | |
21 | /// This a redundant sanity check more than an actual "magic number" since |
|
23 | /// This a redundant sanity check more than an actual "magic number" since | |
@@ -68,7 +70,7 b" pub struct Docket<'on_disk> {" | |||||
68 | /// section of `mercurial/helptext/internals/dirstate-v2.txt` |
|
70 | /// section of `mercurial/helptext/internals/dirstate-v2.txt` | |
69 | #[derive(BytesCast)] |
|
71 | #[derive(BytesCast)] | |
70 | #[repr(C)] |
|
72 | #[repr(C)] | |
71 | struct TreeMetadata { |
|
73 | pub struct TreeMetadata { | |
72 | root_nodes: ChildNodes, |
|
74 | root_nodes: ChildNodes, | |
73 | nodes_with_entry_count: Size, |
|
75 | nodes_with_entry_count: Size, | |
74 | nodes_with_copy_source_count: Size, |
|
76 | nodes_with_copy_source_count: Size, | |
@@ -186,7 +188,51 b' impl From<DirstateV2ParseError> for crat' | |||||
186 | } |
|
188 | } | |
187 | } |
|
189 | } | |
188 |
|
190 | |||
|
191 | impl TreeMetadata { | |||
|
192 | pub fn as_bytes(&self) -> &[u8] { | |||
|
193 | BytesCast::as_bytes(self) | |||
|
194 | } | |||
|
195 | } | |||
|
196 | ||||
189 | impl<'on_disk> Docket<'on_disk> { |
|
197 | impl<'on_disk> Docket<'on_disk> { | |
|
198 | /// Generate the identifier for a new data file | |||
|
199 | /// | |||
|
200 | /// TODO: support the `HGTEST_UUIDFILE` environment variable. | |||
|
201 | /// See `mercurial/revlogutils/docket.py` | |||
|
202 | pub fn new_uid() -> String { | |||
|
203 | const ID_LENGTH: usize = 8; | |||
|
204 | let mut id = String::with_capacity(ID_LENGTH); | |||
|
205 | let mut rng = rand::thread_rng(); | |||
|
206 | for _ in 0..ID_LENGTH { | |||
|
207 | // One random hexadecimal digit. | |||
|
208 | // `unwrap` never panics because `impl Write for String` | |||
|
209 | // never returns an error. | |||
|
210 | write!(&mut id, "{:x}", rng.gen_range(0, 16)).unwrap(); | |||
|
211 | } | |||
|
212 | id | |||
|
213 | } | |||
|
214 | ||||
|
215 | pub fn serialize( | |||
|
216 | parents: DirstateParents, | |||
|
217 | tree_metadata: TreeMetadata, | |||
|
218 | data_size: u64, | |||
|
219 | uuid: &[u8], | |||
|
220 | ) -> Result<Vec<u8>, std::num::TryFromIntError> { | |||
|
221 | let header = DocketHeader { | |||
|
222 | marker: *V2_FORMAT_MARKER, | |||
|
223 | parent_1: parents.p1.pad_to_256_bits(), | |||
|
224 | parent_2: parents.p2.pad_to_256_bits(), | |||
|
225 | metadata: tree_metadata, | |||
|
226 | data_size: u32::try_from(data_size)?.into(), | |||
|
227 | uuid_size: uuid.len().try_into()?, | |||
|
228 | }; | |||
|
229 | let header = header.as_bytes(); | |||
|
230 | let mut docket = Vec::with_capacity(header.len() + uuid.len()); | |||
|
231 | docket.extend_from_slice(header); | |||
|
232 | docket.extend_from_slice(uuid); | |||
|
233 | Ok(docket) | |||
|
234 | } | |||
|
235 | ||||
190 | pub fn parents(&self) -> DirstateParents { |
|
236 | pub fn parents(&self) -> DirstateParents { | |
191 | use crate::Node; |
|
237 | use crate::Node; | |
192 | let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES]) |
|
238 | let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES]) | |
@@ -555,7 +601,7 b" pub(crate) fn for_each_tracked_path<'on_" | |||||
555 | pub(super) fn write( |
|
601 | pub(super) fn write( | |
556 | dirstate_map: &DirstateMap, |
|
602 | dirstate_map: &DirstateMap, | |
557 | can_append: bool, |
|
603 | can_append: bool, | |
558 |
) -> Result<(Vec<u8>, |
|
604 | ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> { | |
559 | let append = can_append && dirstate_map.write_should_append(); |
|
605 | let append = can_append && dirstate_map.write_should_append(); | |
560 |
|
606 | |||
561 | // This ignores the space for paths, and for nodes without an entry. |
|
607 | // This ignores the space for paths, and for nodes without an entry. | |
@@ -581,7 +627,7 b' pub(super) fn write(' | |||||
581 | unused: [0; 4], |
|
627 | unused: [0; 4], | |
582 | ignore_patterns_hash: dirstate_map.ignore_patterns_hash, |
|
628 | ignore_patterns_hash: dirstate_map.ignore_patterns_hash, | |
583 | }; |
|
629 | }; | |
584 |
Ok((writer.out, meta |
|
630 | Ok((writer.out, meta, append)) | |
585 | } |
|
631 | } | |
586 |
|
632 | |||
587 | struct Writer<'dmap, 'on_disk> { |
|
633 | struct Writer<'dmap, 'on_disk> { |
@@ -2,9 +2,10 b' use crate::changelog::Changelog;' | |||||
2 | use crate::config::{Config, ConfigError, ConfigParseError}; |
|
2 | use crate::config::{Config, ConfigError, ConfigParseError}; | |
3 | use crate::dirstate::DirstateParents; |
|
3 | use crate::dirstate::DirstateParents; | |
4 | use crate::dirstate_tree::dirstate_map::DirstateMap; |
|
4 | use crate::dirstate_tree::dirstate_map::DirstateMap; | |
|
5 | use crate::dirstate_tree::on_disk::Docket as DirstateDocket; | |||
5 | use crate::dirstate_tree::owning::OwningDirstateMap; |
|
6 | use crate::dirstate_tree::owning::OwningDirstateMap; | |
6 | use crate::errors::HgError; |
|
|||
7 | use crate::errors::HgResultExt; |
|
7 | use crate::errors::HgResultExt; | |
|
8 | use crate::errors::{HgError, IoResultExt}; | |||
8 | use crate::exit_codes; |
|
9 | use crate::exit_codes; | |
9 | use crate::lock::{try_with_lock_no_wait, LockError}; |
|
10 | use crate::lock::{try_with_lock_no_wait, LockError}; | |
10 | use crate::manifest::{Manifest, Manifestlog}; |
|
11 | use crate::manifest::{Manifest, Manifestlog}; | |
@@ -18,6 +19,9 b' use crate::{requirements, NodePrefix};' | |||||
18 | use crate::{DirstateError, Revision}; |
|
19 | use crate::{DirstateError, Revision}; | |
19 | use std::cell::{Ref, RefCell, RefMut}; |
|
20 | use std::cell::{Ref, RefCell, RefMut}; | |
20 | use std::collections::HashSet; |
|
21 | use std::collections::HashSet; | |
|
22 | use std::io::Seek; | |||
|
23 | use std::io::SeekFrom; | |||
|
24 | use std::io::Write as IoWrite; | |||
21 | use std::path::{Path, PathBuf}; |
|
25 | use std::path::{Path, PathBuf}; | |
22 |
|
26 | |||
23 | /// A repository on disk |
|
27 | /// A repository on disk | |
@@ -416,6 +420,70 b' impl Repo {' | |||||
416 | pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> { |
|
420 | pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> { | |
417 | Filelog::open(self, path) |
|
421 | Filelog::open(self, path) | |
418 | } |
|
422 | } | |
|
423 | ||||
|
424 | /// Write to disk any updates that were made through `dirstate_map_mut`. | |||
|
425 | /// | |||
|
426 | /// The "wlock" must be held while calling this. | |||
|
427 | /// See for example `try_with_wlock_no_wait`. | |||
|
428 | /// | |||
|
429 | /// TODO: have a `WritableRepo` type only accessible while holding the | |||
|
430 | /// lock? | |||
|
431 | pub fn write_dirstate(&self) -> Result<(), DirstateError> { | |||
|
432 | let map = self.dirstate_map()?; | |||
|
433 | // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if | |||
|
434 | // it’s unset | |||
|
435 | let parents = self.dirstate_parents()?; | |||
|
436 | let packed_dirstate = if self.has_dirstate_v2() { | |||
|
437 | let uuid = self.dirstate_data_file_uuid.get_or_init(self)?; | |||
|
438 | let mut uuid = uuid.as_ref(); | |||
|
439 | let can_append = uuid.is_some(); | |||
|
440 | let (data, tree_metadata, append) = map.pack_v2(can_append)?; | |||
|
441 | if !append { | |||
|
442 | uuid = None | |||
|
443 | } | |||
|
444 | let uuid = if let Some(uuid) = uuid { | |||
|
445 | std::str::from_utf8(uuid) | |||
|
446 | .map_err(|_| { | |||
|
447 | HgError::corrupted("non-UTF-8 dirstate data file ID") | |||
|
448 | })? | |||
|
449 | .to_owned() | |||
|
450 | } else { | |||
|
451 | DirstateDocket::new_uid() | |||
|
452 | }; | |||
|
453 | let data_filename = format!("dirstate.{}", uuid); | |||
|
454 | let data_filename = self.hg_vfs().join(data_filename); | |||
|
455 | let mut options = std::fs::OpenOptions::new(); | |||
|
456 | if append { | |||
|
457 | options.append(true); | |||
|
458 | } else { | |||
|
459 | options.write(true).create_new(true); | |||
|
460 | } | |||
|
461 | let data_size = (|| { | |||
|
462 | // TODO: loop and try another random ID if !append and this | |||
|
463 | // returns `ErrorKind::AlreadyExists`? Collision chance of two | |||
|
464 | // random IDs is one in 2**32 | |||
|
465 | let mut file = options.open(&data_filename)?; | |||
|
466 | file.write_all(&data)?; | |||
|
467 | file.flush()?; | |||
|
468 | // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+ | |||
|
469 | file.seek(SeekFrom::Current(0)) | |||
|
470 | })() | |||
|
471 | .when_writing_file(&data_filename)?; | |||
|
472 | DirstateDocket::serialize( | |||
|
473 | parents, | |||
|
474 | tree_metadata, | |||
|
475 | data_size, | |||
|
476 | uuid.as_bytes(), | |||
|
477 | ) | |||
|
478 | .map_err(|_: std::num::TryFromIntError| { | |||
|
479 | HgError::corrupted("overflow in dirstate docket serialization") | |||
|
480 | })? | |||
|
481 | } else { | |||
|
482 | map.pack_v1(parents)? | |||
|
483 | }; | |||
|
484 | self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?; | |||
|
485 | Ok(()) | |||
|
486 | } | |||
419 | } |
|
487 | } | |
420 |
|
488 | |||
421 | /// Lazily-initialized component of `Repo` with interior mutability |
|
489 | /// Lazily-initialized component of `Repo` with interior mutability |
@@ -174,6 +174,12 b' impl Node {' | |||||
174 | data: self.data, |
|
174 | data: self.data, | |
175 | } |
|
175 | } | |
176 | } |
|
176 | } | |
|
177 | ||||
|
178 | pub fn pad_to_256_bits(&self) -> [u8; 32] { | |||
|
179 | let mut bits = [0; 32]; | |||
|
180 | bits[..NODE_BYTES_LENGTH].copy_from_slice(&self.data); | |||
|
181 | bits | |||
|
182 | } | |||
177 | } |
|
183 | } | |
178 |
|
184 | |||
179 | /// The beginning of a binary revision SHA. |
|
185 | /// The beginning of a binary revision SHA. |
@@ -222,7 +222,7 b' py_class!(pub class DirstateMap |py| {' | |||||
222 | match result { |
|
222 | match result { | |
223 | Ok((packed, tree_metadata, append)) => { |
|
223 | Ok((packed, tree_metadata, append)) => { | |
224 | let packed = PyBytes::new(py, &packed); |
|
224 | let packed = PyBytes::new(py, &packed); | |
225 |
let tree_metadata = PyBytes::new(py, |
|
225 | let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes()); | |
226 | let tuple = (packed, tree_metadata, append); |
|
226 | let tuple = (packed, tree_metadata, append); | |
227 | Ok(tuple.to_py_object(py).into_object()) |
|
227 | Ok(tuple.to_py_object(py).into_object()) | |
228 | }, |
|
228 | }, |
General Comments 0
You need to be logged in to leave comments.
Login now