##// END OF EJS Templates
dirstate-v2: Store a bitfield on disk instead of v1-like state...
Simon Sapin -
r48951:ab5a7fdb default
parent child Browse files
Show More
@@ -15,13 +15,13 b' pub enum EntryState {'
15 /// comes first.
15 /// comes first.
16 #[derive(Debug, PartialEq, Copy, Clone)]
16 #[derive(Debug, PartialEq, Copy, Clone)]
17 pub struct DirstateEntry {
17 pub struct DirstateEntry {
18 flags: Flags,
18 pub(crate) flags: Flags,
19 mode_size: Option<(i32, i32)>,
19 mode_size: Option<(i32, i32)>,
20 mtime: Option<i32>,
20 mtime: Option<i32>,
21 }
21 }
22
22
23 bitflags! {
23 bitflags! {
24 struct Flags: u8 {
24 pub(crate) struct Flags: u8 {
25 const WDIR_TRACKED = 1 << 0;
25 const WDIR_TRACKED = 1 << 0;
26 const P1_TRACKED = 1 << 1;
26 const P1_TRACKED = 1 << 1;
27 const P2_INFO = 1 << 2;
27 const P2_INFO = 1 << 2;
@@ -41,7 +41,7 b' pub const SIZE_FROM_OTHER_PARENT: i32 = '
41 pub const SIZE_NON_NORMAL: i32 = -1;
41 pub const SIZE_NON_NORMAL: i32 = -1;
42
42
43 impl DirstateEntry {
43 impl DirstateEntry {
44 pub fn new(
44 pub fn from_v2_data(
45 wdir_tracked: bool,
45 wdir_tracked: bool,
46 p1_tracked: bool,
46 p1_tracked: bool,
47 p2_info: bool,
47 p2_info: bool,
@@ -193,6 +193,22 b' impl DirstateEntry {'
193 )
193 )
194 }
194 }
195
195
196 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
197 pub(crate) fn v2_data(
198 &self,
199 ) -> (bool, bool, bool, Option<(i32, i32)>, Option<i32>) {
200 if !self.any_tracked() {
201 // TODO: return an Option instead?
202 panic!("Accessing v1_state of an untracked DirstateEntry")
203 }
204 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
205 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
206 let p2_info = self.flags.contains(Flags::P2_INFO);
207 let mode_size = self.mode_size;
208 let mtime = self.mtime;
209 (wdir_tracked, p1_tracked, p2_info, mode_size, mtime)
210 }
211
196 fn v1_state(&self) -> EntryState {
212 fn v1_state(&self) -> EntryState {
197 if !self.any_tracked() {
213 if !self.any_tracked() {
198 // TODO: return an Option instead?
214 // TODO: return an Option instead?
@@ -325,12 +325,7 b" impl<'tree, 'on_disk> NodeRef<'tree, 'on"
325 pub(super) fn state(
325 pub(super) fn state(
326 &self,
326 &self,
327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
328 match self {
328 Ok(self.entry()?.map(|e| e.state()))
329 NodeRef::InMemory(_path, node) => {
330 Ok(node.data.as_entry().map(|entry| entry.state()))
331 }
332 NodeRef::OnDisk(node) => node.state(),
333 }
334 }
329 }
335
330
336 pub(super) fn cached_directory_mtime(
331 pub(super) fn cached_directory_mtime(
@@ -25,7 +25,7 b' use crate::utils::hg_path::HgPath;'
25 use crate::DirstateEntry;
25 use crate::DirstateEntry;
26 use crate::DirstateError;
26 use crate::DirstateError;
27 use crate::DirstateParents;
27 use crate::DirstateParents;
28 use crate::EntryState;
28 use bitflags::bitflags;
29 use bytes_cast::unaligned::{I32Be, I64Be, U16Be, U32Be};
29 use bytes_cast::unaligned::{I32Be, I64Be, U16Be, U32Be};
30 use bytes_cast::BytesCast;
30 use bytes_cast::BytesCast;
31 use format_bytes::format_bytes;
31 use format_bytes::format_bytes;
@@ -131,18 +131,26 b' pub(super) struct Node {'
131 pub(super) descendants_with_entry_count: Size,
131 pub(super) descendants_with_entry_count: Size,
132 pub(super) tracked_descendants_count: Size,
132 pub(super) tracked_descendants_count: Size,
133
133
134 /// Depending on the value of `state`:
134 /// Depending on the bits in `flags`:
135 ///
136 /// * If any of `WDIR_TRACKED`, `P1_TRACKED`, or `P2_INFO` are set, the
137 /// node has an entry.
135 ///
138 ///
136 /// * A null byte: `data` is not used.
139 /// - If `HAS_MODE_AND_SIZE` is set, `data.mode` and `data.size` are
140 /// meaningful. Otherwise they are set to zero
141 /// - If `HAS_MTIME` is set, `data.mtime` is meaningful. Otherwise it is
142 /// set to zero.
137 ///
143 ///
138 /// * A `n`, `a`, `r`, or `m` ASCII byte: `state` and `data` together
144 /// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO`, or `HAS_MTIME`
139 /// represent a dirstate entry like in the v1 format.
145 /// are set, the node does not have an entry and `data` is set to all
146 /// zeros.
140 ///
147 ///
141 /// * A `d` ASCII byte: the bytes of `data` should instead be interpreted
148 /// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO` are set, but
142 /// as the `Timestamp` for the mtime of a cached directory.
149 /// `HAS_MTIME` is set, the bytes of `data` should instead be
150 /// interpreted as the `Timestamp` for the mtime of a cached directory.
143 ///
151 ///
144 /// The presence of this state means that at some point, this path in
152 /// The presence of this combination of flags means that at some point,
145 /// the working directory was observed:
153 /// this path in the working directory was observed:
146 ///
154 ///
147 /// - To be a directory
155 /// - To be a directory
148 /// - With the modification time as given by `Timestamp`
156 /// - With the modification time as given by `Timestamp`
@@ -161,11 +169,23 b' pub(super) struct Node {'
161 /// of status that is not listing ignored files can skip calling
169 /// of status that is not listing ignored files can skip calling
162 /// `std::fs::read_dir` again for this directory, iterate child
170 /// `std::fs::read_dir` again for this directory, iterate child
163 /// dirstate nodes instead.
171 /// dirstate nodes instead.
164 state: u8,
172 flags: Flags,
165 data: Entry,
173 data: Entry,
166 }
174 }
167
175
168 #[derive(BytesCast, Copy, Clone)]
176 bitflags! {
177 #[derive(BytesCast)]
178 #[repr(C)]
179 struct Flags: u8 {
180 const WDIR_TRACKED = 1 << 0;
181 const P1_TRACKED = 1 << 1;
182 const P2_INFO = 1 << 2;
183 const HAS_MODE_AND_SIZE = 1 << 3;
184 const HAS_MTIME = 1 << 4;
185 }
186 }
187
188 #[derive(BytesCast, Copy, Clone, Debug)]
169 #[repr(C)]
189 #[repr(C)]
170 struct Entry {
190 struct Entry {
171 mode: I32Be,
191 mode: I32Be,
@@ -361,65 +381,64 b' impl Node {'
361 })
381 })
362 }
382 }
363
383
384 fn has_entry(&self) -> bool {
385 self.flags.intersects(
386 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
387 )
388 }
389
364 pub(super) fn node_data(
390 pub(super) fn node_data(
365 &self,
391 &self,
366 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
392 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
367 let entry = |state| {
393 if self.has_entry() {
368 dirstate_map::NodeData::Entry(self.entry_with_given_state(state))
394 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
369 };
395 } else if let Some(&mtime) = self.cached_directory_mtime() {
370
396 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
371 match self.state {
397 } else {
372 b'\0' => Ok(dirstate_map::NodeData::None),
398 Ok(dirstate_map::NodeData::None)
373 b'd' => Ok(dirstate_map::NodeData::CachedDirectory {
374 mtime: *self.data.as_timestamp(),
375 }),
376 b'n' => Ok(entry(EntryState::Normal)),
377 b'a' => Ok(entry(EntryState::Added)),
378 b'r' => Ok(entry(EntryState::Removed)),
379 b'm' => Ok(entry(EntryState::Merged)),
380 _ => Err(DirstateV2ParseError),
381 }
399 }
382 }
400 }
383
401
384 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
402 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
385 if self.state == b'd' {
403 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
386 Some(self.data.as_timestamp())
404 Some(self.data.as_timestamp())
387 } else {
405 } else {
388 None
406 None
389 }
407 }
390 }
408 }
391
409
392 pub(super) fn state(
410 fn assume_entry(&self) -> DirstateEntry {
393 &self,
411 // TODO: convert through raw bits instead?
394 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
412 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
395 match self.state {
413 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
396 b'\0' | b'd' => Ok(None),
414 let p2_info = self.flags.contains(Flags::P2_INFO);
397 b'n' => Ok(Some(EntryState::Normal)),
415 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
398 b'a' => Ok(Some(EntryState::Added)),
416 Some((self.data.mode.into(), self.data.size.into()))
399 b'r' => Ok(Some(EntryState::Removed)),
417 } else {
400 b'm' => Ok(Some(EntryState::Merged)),
418 None
401 _ => Err(DirstateV2ParseError),
419 };
402 }
420 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
403 }
421 Some(self.data.mtime.into())
404
422 } else {
405 fn entry_with_given_state(&self, state: EntryState) -> DirstateEntry {
423 None
406 // For now, the on-disk representation of DirstateEntry in dirstate-v2
424 };
407 // format is equivalent to that of dirstate-v1. When that changes, add
425 DirstateEntry::from_v2_data(
408 // a new constructor.
426 wdir_tracked,
409 DirstateEntry::from_v1_data(
427 p1_tracked,
410 state,
428 p2_info,
411 self.data.mode.get(),
429 mode_size,
412 self.data.size.get(),
430 mtime,
413 self.data.mtime.get(),
414 )
431 )
415 }
432 }
416
433
417 pub(super) fn entry(
434 pub(super) fn entry(
418 &self,
435 &self,
419 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
436 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
420 Ok(self
437 if self.has_entry() {
421 .state()?
438 Ok(Some(self.assume_entry()))
422 .map(|state| self.entry_with_given_state(state)))
439 } else {
440 Ok(None)
441 }
423 }
442 }
424
443
425 pub(super) fn children<'on_disk>(
444 pub(super) fn children<'on_disk>(
@@ -448,6 +467,37 b' impl Node {'
448 }
467 }
449
468
450 impl Entry {
469 impl Entry {
470 fn from_dirstate_entry(entry: &DirstateEntry) -> (Flags, Self) {
471 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
472 entry.v2_data();
473 // TODO: convert throug raw flag bits instead?
474 let mut flags = Flags::empty();
475 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
476 flags.set(Flags::P1_TRACKED, p1_tracked);
477 flags.set(Flags::P2_INFO, p2_info);
478 let (mode, size, mtime);
479 if let Some((m, s)) = mode_size_opt {
480 mode = m;
481 size = s;
482 flags.insert(Flags::HAS_MODE_AND_SIZE)
483 } else {
484 mode = 0;
485 size = 0;
486 }
487 if let Some(m) = mtime_opt {
488 mtime = m;
489 flags.insert(Flags::HAS_MTIME);
490 } else {
491 mtime = 0;
492 }
493 let raw_entry = Entry {
494 mode: mode.into(),
495 size: size.into(),
496 mtime: mtime.into(),
497 };
498 (flags, raw_entry)
499 }
500
451 fn from_timestamp(timestamp: Timestamp) -> Self {
501 fn from_timestamp(timestamp: Timestamp) -> Self {
452 // Safety: both types implement the `ByteCast` trait, so we could
502 // Safety: both types implement the `ByteCast` trait, so we could
453 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
503 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
@@ -546,8 +596,8 b" pub(crate) fn for_each_tracked_path<'on_"
546 f: &mut impl FnMut(&'on_disk HgPath),
596 f: &mut impl FnMut(&'on_disk HgPath),
547 ) -> Result<(), DirstateV2ParseError> {
597 ) -> Result<(), DirstateV2ParseError> {
548 for node in read_nodes(on_disk, nodes)? {
598 for node in read_nodes(on_disk, nodes)? {
549 if let Some(state) = node.state()? {
599 if let Some(entry) = node.entry()? {
550 if state.is_tracked() {
600 if entry.state().is_tracked() {
551 f(node.full_path(on_disk)?)
601 f(node.full_path(on_disk)?)
552 }
602 }
553 }
603 }
@@ -641,24 +691,19 b" impl Writer<'_, '_> {"
641 };
691 };
642 on_disk_nodes.push(match node {
692 on_disk_nodes.push(match node {
643 NodeRef::InMemory(path, node) => {
693 NodeRef::InMemory(path, node) => {
644 let (state, data) = match &node.data {
694 let (flags, data) = match &node.data {
645 dirstate_map::NodeData::Entry(entry) => (
695 dirstate_map::NodeData::Entry(entry) => {
646 entry.state().into(),
696 Entry::from_dirstate_entry(entry)
647 Entry {
697 }
648 mode: entry.mode().into(),
649 mtime: entry.mtime().into(),
650 size: entry.size().into(),
651 },
652 ),
653 dirstate_map::NodeData::CachedDirectory { mtime } => {
698 dirstate_map::NodeData::CachedDirectory { mtime } => {
654 (b'd', Entry::from_timestamp(*mtime))
699 (Flags::HAS_MTIME, Entry::from_timestamp(*mtime))
655 }
700 }
656 dirstate_map::NodeData::None => (
701 dirstate_map::NodeData::None => (
657 b'\0',
702 Flags::empty(),
658 Entry {
703 Entry {
659 mode: 0.into(),
704 mode: 0.into(),
705 size: 0.into(),
660 mtime: 0.into(),
706 mtime: 0.into(),
661 size: 0.into(),
662 },
707 },
663 ),
708 ),
664 };
709 };
@@ -676,7 +721,7 b" impl Writer<'_, '_> {"
676 tracked_descendants_count: node
721 tracked_descendants_count: node
677 .tracked_descendants_count
722 .tracked_descendants_count
678 .into(),
723 .into(),
679 state,
724 flags,
680 data,
725 data,
681 }
726 }
682 }
727 }
@@ -34,7 +34,7 b' py_class!(pub class DirstateItem |py| {'
34 mtime_opt = Some(mtime)
34 mtime_opt = Some(mtime)
35 }
35 }
36 }
36 }
37 let entry = DirstateEntry::new(
37 let entry = DirstateEntry::from_v2_data(
38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
39 );
39 );
40 DirstateItem::create_instance(py, Cell::new(entry))
40 DirstateItem::create_instance(py, Cell::new(entry))
General Comments 0
You need to be logged in to leave comments. Login now