Timestamps are stored on 96 bits:
- 64 bits for the signed number of seconds since the Unix epoch
- 32 bits for the nanoseconds in the 0 <= ns < 1_000_000_000 range
For now timestamps are not used or set yet.
| hg-reviewers |
Timestamps are stored on 96 bits:
For now timestamps are not used or set yet.
| Automatic diff as part of commit; lint not applicable. |
| Automatic diff as part of commit; unit tests not applicable. |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-core/src/dirstate_tree/dirstate_map.rs (73 lines) | |||
| M | rust/hg-core/src/dirstate_tree/on_disk.rs (202 lines) |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin |
| match self { | match self { | ||||
| NodeRef::InMemory(_path, node) => { | NodeRef::InMemory(_path, node) => { | ||||
| Ok(node.copy_source.as_ref().map(|s| &**s)) | Ok(node.copy_source.as_ref().map(|s| &**s)) | ||||
| } | } | ||||
| NodeRef::OnDisk(node) => node.copy_source(on_disk), | NodeRef::OnDisk(node) => node.copy_source(on_disk), | ||||
| } | } | ||||
| } | } | ||||
| pub(super) fn has_entry(&self) -> bool { | |||||
| match self { | |||||
| NodeRef::InMemory(_path, node) => node.entry.is_some(), | |||||
| NodeRef::OnDisk(node) => node.has_entry(), | |||||
| } | |||||
| } | |||||
| pub(super) fn entry( | pub(super) fn entry( | ||||
| &self, | &self, | ||||
| ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ||||
| match self { | match self { | ||||
| NodeRef::InMemory(_path, node) => Ok(node.entry), | NodeRef::InMemory(_path, node) => { | ||||
| Ok(node.data.as_entry().copied()) | |||||
| } | |||||
| NodeRef::OnDisk(node) => node.entry(), | NodeRef::OnDisk(node) => node.entry(), | ||||
| } | } | ||||
| } | } | ||||
| pub(super) fn state( | pub(super) fn state( | ||||
| &self, | &self, | ||||
| ) -> Result<Option<EntryState>, DirstateV2ParseError> { | ) -> Result<Option<EntryState>, DirstateV2ParseError> { | ||||
| match self { | match self { | ||||
| NodeRef::InMemory(_path, node) => { | NodeRef::InMemory(_path, node) => { | ||||
| Ok(node.entry.as_ref().map(|entry| entry.state)) | Ok(node.data.as_entry().map(|entry| entry.state)) | ||||
| } | } | ||||
| NodeRef::OnDisk(node) => node.state(), | NodeRef::OnDisk(node) => node.state(), | ||||
| } | } | ||||
| } | } | ||||
| pub(super) fn tracked_descendants_count(&self) -> u32 { | pub(super) fn tracked_descendants_count(&self) -> u32 { | ||||
| match self { | match self { | ||||
| NodeRef::InMemory(_path, node) => node.tracked_descendants_count, | NodeRef::InMemory(_path, node) => node.tracked_descendants_count, | ||||
| NodeRef::OnDisk(node) => node.tracked_descendants_count.get(), | NodeRef::OnDisk(node) => node.tracked_descendants_count.get(), | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /// Represents a file or a directory | /// Represents a file or a directory | ||||
| #[derive(Default)] | #[derive(Default)] | ||||
| pub(super) struct Node<'on_disk> { | pub(super) struct Node<'on_disk> { | ||||
| /// `None` for directories | pub(super) data: NodeData, | ||||
| pub(super) entry: Option<DirstateEntry>, | |||||
| pub(super) copy_source: Option<Cow<'on_disk, HgPath>>, | pub(super) copy_source: Option<Cow<'on_disk, HgPath>>, | ||||
| pub(super) children: ChildNodes<'on_disk>, | pub(super) children: ChildNodes<'on_disk>, | ||||
| /// How many (non-inclusive) descendants of this node are tracked files | /// How many (non-inclusive) descendants of this node are tracked files | ||||
| pub(super) tracked_descendants_count: u32, | pub(super) tracked_descendants_count: u32, | ||||
| } | } | ||||
| pub(super) enum NodeData { | |||||
| Entry(DirstateEntry), | |||||
| CachedDirectory { mtime: on_disk::Timestamp }, | |||||
| None, | |||||
| } | |||||
| impl Default for NodeData { | |||||
| fn default() -> Self { | |||||
| NodeData::None | |||||
| } | |||||
| } | |||||
| impl NodeData { | |||||
| fn has_entry(&self) -> bool { | |||||
| match self { | |||||
| NodeData::Entry(_) => true, | |||||
| _ => false, | |||||
| } | |||||
| } | |||||
| fn as_entry(&self) -> Option<&DirstateEntry> { | |||||
| match self { | |||||
| NodeData::Entry(entry) => Some(entry), | |||||
| _ => None, | |||||
| } | |||||
| } | |||||
| } | |||||
| impl<'on_disk> DirstateMap<'on_disk> { | impl<'on_disk> DirstateMap<'on_disk> { | ||||
| pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self { | pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self { | ||||
| Self { | Self { | ||||
| on_disk, | on_disk, | ||||
| root: ChildNodes::default(), | root: ChildNodes::default(), | ||||
| nodes_with_entry_count: 0, | nodes_with_entry_count: 0, | ||||
| nodes_with_copy_source_count: 0, | nodes_with_copy_source_count: 0, | ||||
| } | } | ||||
| WithBasename::to_cow_borrowed, | WithBasename::to_cow_borrowed, | ||||
| |ancestor| { | |ancestor| { | ||||
| if tracked { | if tracked { | ||||
| ancestor.tracked_descendants_count += 1 | ancestor.tracked_descendants_count += 1 | ||||
| } | } | ||||
| }, | }, | ||||
| )?; | )?; | ||||
| assert!( | assert!( | ||||
| node.entry.is_none(), | !node.data.has_entry(), | ||||
| "duplicate dirstate entry in read" | "duplicate dirstate entry in read" | ||||
| ); | ); | ||||
| assert!( | assert!( | ||||
| node.copy_source.is_none(), | node.copy_source.is_none(), | ||||
| "duplicate dirstate entry in read" | "duplicate dirstate entry in read" | ||||
| ); | ); | ||||
| node.entry = Some(*entry); | node.data = NodeData::Entry(*entry); | ||||
| node.copy_source = copy_source.map(Cow::Borrowed); | node.copy_source = copy_source.map(Cow::Borrowed); | ||||
| map.nodes_with_entry_count += 1; | map.nodes_with_entry_count += 1; | ||||
| if copy_source.is_some() { | if copy_source.is_some() { | ||||
| map.nodes_with_copy_source_count += 1 | map.nodes_with_copy_source_count += 1 | ||||
| } | } | ||||
| Ok(()) | Ok(()) | ||||
| }, | }, | ||||
| )?; | )?; | ||||
| // through zero | // through zero | ||||
| match tracked_count_increment { | match tracked_count_increment { | ||||
| 1 => ancestor.tracked_descendants_count += 1, | 1 => ancestor.tracked_descendants_count += 1, | ||||
| -1 => ancestor.tracked_descendants_count -= 1, | -1 => ancestor.tracked_descendants_count -= 1, | ||||
| _ => {} | _ => {} | ||||
| } | } | ||||
| }, | }, | ||||
| )?; | )?; | ||||
| if node.entry.is_none() { | if !node.data.has_entry() { | ||||
| self.nodes_with_entry_count += 1 | self.nodes_with_entry_count += 1 | ||||
| } | } | ||||
| node.entry = Some(new_entry); | node.data = NodeData::Entry(new_entry); | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| fn iter_nodes<'tree>( | fn iter_nodes<'tree>( | ||||
| &'tree self, | &'tree self, | ||||
| ) -> impl Iterator< | ) -> impl Iterator< | ||||
| Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>, | Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>, | ||||
| > + 'tree { | > + 'tree { | ||||
| paths: &[impl AsRef<HgPath>], | paths: &[impl AsRef<HgPath>], | ||||
| ) -> Result<(), DirstateV2ParseError> { | ) -> Result<(), DirstateV2ParseError> { | ||||
| for path in paths { | for path in paths { | ||||
| if let Some(node) = Self::get_node_mut( | if let Some(node) = Self::get_node_mut( | ||||
| self.on_disk, | self.on_disk, | ||||
| &mut self.root, | &mut self.root, | ||||
| path.as_ref(), | path.as_ref(), | ||||
| )? { | )? { | ||||
| if let Some(entry) = node.entry.as_mut() { | if let NodeData::Entry(entry) = &mut node.data { | ||||
| entry.clear_mtime(); | entry.clear_mtime(); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| /// Return a faillilble iterator of full paths of nodes that have an | /// Return a faillilble iterator of full paths of nodes that have an | ||||
| dropped = d; | dropped = d; | ||||
| if dropped.was_tracked { | if dropped.was_tracked { | ||||
| node.tracked_descendants_count -= 1; | node.tracked_descendants_count -= 1; | ||||
| } | } | ||||
| } else { | } else { | ||||
| return Ok(None); | return Ok(None); | ||||
| } | } | ||||
| } else { | } else { | ||||
| let had_entry = node.data.has_entry(); | |||||
| if had_entry { | |||||
| node.data = NodeData::None | |||||
| } | |||||
| dropped = Dropped { | dropped = Dropped { | ||||
| was_tracked: node | was_tracked: node | ||||
| .entry | .data | ||||
| .as_ref() | .as_entry() | ||||
| .map_or(false, |entry| entry.state.is_tracked()), | .map_or(false, |entry| entry.state.is_tracked()), | ||||
| had_entry: node.entry.take().is_some(), | had_entry, | ||||
| had_copy_source: node.copy_source.take().is_some(), | had_copy_source: node.copy_source.take().is_some(), | ||||
| }; | }; | ||||
| } | } | ||||
| // After recursion, for both leaf (rest_of_path is None) nodes and | // After recursion, for both leaf (rest_of_path is None) nodes and | ||||
| // parent nodes, remove a node if it just became empty. | // parent nodes, remove a node if it just became empty. | ||||
| if node.entry.is_none() | if !node.data.has_entry() | ||||
| && node.copy_source.is_none() | && node.copy_source.is_none() | ||||
| && node.children.is_empty() | && node.children.is_empty() | ||||
| { | { | ||||
| nodes.make_mut(on_disk)?.remove(first_path_component); | nodes.make_mut(on_disk)?.remove(first_path_component); | ||||
| } | } | ||||
| Ok(Some(dropped)) | Ok(Some(dropped)) | ||||
| } | } | ||||
| &mut self, | &mut self, | ||||
| filenames: Vec<HgPathBuf>, | filenames: Vec<HgPathBuf>, | ||||
| now: i32, | now: i32, | ||||
| ) -> Result<(), DirstateV2ParseError> { | ) -> Result<(), DirstateV2ParseError> { | ||||
| for filename in filenames { | for filename in filenames { | ||||
| if let Some(node) = | if let Some(node) = | ||||
| Self::get_node_mut(self.on_disk, &mut self.root, &filename)? | Self::get_node_mut(self.on_disk, &mut self.root, &filename)? | ||||
| { | { | ||||
| if let Some(entry) = node.entry.as_mut() { | if let NodeData::Entry(entry) = &mut node.data { | ||||
| entry.clear_ambiguous_mtime(now); | entry.clear_ambiguous_mtime(now); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| fn non_normal_entries_contains( | fn non_normal_entries_contains( | ||||
| fn has_tracked_dir( | fn has_tracked_dir( | ||||
| &mut self, | &mut self, | ||||
| directory: &HgPath, | directory: &HgPath, | ||||
| ) -> Result<bool, DirstateError> { | ) -> Result<bool, DirstateError> { | ||||
| if let Some(node) = self.get_node(directory)? { | if let Some(node) = self.get_node(directory)? { | ||||
| // A node without a `DirstateEntry` was created to hold child | // A node without a `DirstateEntry` was created to hold child | ||||
| // nodes, and is therefore a directory. | // nodes, and is therefore a directory. | ||||
| Ok(!node.has_entry() && node.tracked_descendants_count() > 0) | let state = node.state()?; | ||||
| Ok(state.is_none() && node.tracked_descendants_count() > 0) | |||||
| } else { | } else { | ||||
| Ok(false) | Ok(false) | ||||
| } | } | ||||
| } | } | ||||
| fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> { | fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> { | ||||
| if let Some(node) = self.get_node(directory)? { | if let Some(node) = self.get_node(directory)? { | ||||
| // A node without a `DirstateEntry` was created to hold child | // A node without a `DirstateEntry` was created to hold child | ||||
| // nodes, and is therefore a directory. | // nodes, and is therefore a directory. | ||||
| Ok(!node.has_entry()) | Ok(node.state()?.is_none()) | ||||
| } else { | } else { | ||||
| Ok(false) | Ok(false) | ||||
| } | } | ||||
| } | } | ||||
| #[timed] | #[timed] | ||||
| fn pack_v1( | fn pack_v1( | ||||
| &mut self, | &mut self, | ||||
| use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef}; | use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef}; | ||||
| use crate::dirstate_tree::path_with_basename::WithBasename; | use crate::dirstate_tree::path_with_basename::WithBasename; | ||||
| use crate::errors::HgError; | use crate::errors::HgError; | ||||
| use crate::utils::hg_path::HgPath; | use crate::utils::hg_path::HgPath; | ||||
| use crate::DirstateEntry; | use crate::DirstateEntry; | ||||
| use crate::DirstateError; | use crate::DirstateError; | ||||
| use crate::DirstateParents; | use crate::DirstateParents; | ||||
| use crate::EntryState; | use crate::EntryState; | ||||
| use bytes_cast::unaligned::{I32Be, U32Be, U64Be}; | use bytes_cast::unaligned::{I32Be, I64Be, U32Be, U64Be}; | ||||
| use bytes_cast::BytesCast; | use bytes_cast::BytesCast; | ||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||
| use std::convert::{TryFrom, TryInto}; | use std::convert::TryFrom; | ||||
| use std::time::{Duration, SystemTime, UNIX_EPOCH}; | |||||
| /// Added at the start of `.hg/dirstate` when the "v2" format is used. | /// Added at the start of `.hg/dirstate` when the "v2" format is used. | ||||
| /// This a redundant sanity check more than an actual "magic number" since | /// This a redundant sanity check more than an actual "magic number" since | ||||
| /// `.hg/requires` already governs which format should be used. | /// `.hg/requires` already governs which format should be used. | ||||
| pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n"; | pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n"; | ||||
| #[derive(BytesCast)] | #[derive(BytesCast)] | ||||
| #[repr(C)] | #[repr(C)] | ||||
| #[repr(C)] | #[repr(C)] | ||||
| pub(super) struct Node { | pub(super) struct Node { | ||||
| full_path: PathSlice, | full_path: PathSlice, | ||||
| /// In bytes from `self.full_path.start` | /// In bytes from `self.full_path.start` | ||||
| base_name_start: Size, | base_name_start: Size, | ||||
| copy_source: OptPathSlice, | copy_source: OptPathSlice, | ||||
| entry: OptEntry, | |||||
| children: ChildNodes, | children: ChildNodes, | ||||
| pub(super) tracked_descendants_count: Size, | pub(super) tracked_descendants_count: Size, | ||||
| /// Dependending on the value of `state`: | |||||
| /// | |||||
| /// * A null byte: `data` represents nothing | |||||
| /// * A `n`, `a`, `r`, or `m` ASCII byte: `state` and `data` together | |||||
| /// represents a dirstate entry like in the v1 format. | |||||
| /// * A `d` ASCII byte: the bytes of `data` should instead be interpreted | |||||
| /// as the `Timestamp` for the mtime of a cached directory. | |||||
| /// | |||||
| /// TODO: document directory caching | |||||
| state: u8, | |||||
| data: Entry, | |||||
| } | } | ||||
| /// Either nothing if `state == b'\0'`, or a dirstate entry like in the v1 | |||||
| /// format | |||||
| #[derive(BytesCast, Copy, Clone)] | #[derive(BytesCast, Copy, Clone)] | ||||
| #[repr(C)] | #[repr(C)] | ||||
| struct OptEntry { | struct Entry { | ||||
| state: u8, | |||||
| mode: I32Be, | mode: I32Be, | ||||
| mtime: I32Be, | mtime: I32Be, | ||||
| size: I32Be, | size: I32Be, | ||||
| } | } | ||||
| /// Duration since the Unix epoch | |||||
| #[derive(BytesCast, Copy, Clone)] | |||||
| #[repr(C)] | |||||
| pub(super) struct Timestamp { | |||||
| seconds: I64Be, | |||||
| /// In `0 .. 1_000_000_000`. | |||||
| /// | |||||
| /// This timestamp is later or earlier than `(seconds, 0)` by this many | |||||
| /// nanoseconds, if `seconds` is non-negative or negative, respectively. | |||||
| nanoseconds: U32Be, | |||||
| } | |||||
| /// Counted in bytes from the start of the file | /// Counted in bytes from the start of the file | ||||
| /// | /// | ||||
| /// NOTE: If we decide to never support `.hg/dirstate` files larger than 4 GiB | /// NOTE: If we decide to never support `.hg/dirstate` files larger than 4 GiB | ||||
| /// we could save space by using `U32Be` instead. | /// we could save space by using `U32Be` instead. | ||||
| type Offset = U64Be; | type Offset = U64Be; | ||||
| /// Counted in number of items | /// Counted in number of items | ||||
| /// | /// | ||||
| ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> { | ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> { | ||||
| Ok(if self.has_copy_source() { | Ok(if self.has_copy_source() { | ||||
| Some(read_hg_path(on_disk, self.copy_source)?) | Some(read_hg_path(on_disk, self.copy_source)?) | ||||
| } else { | } else { | ||||
| None | None | ||||
| }) | }) | ||||
| } | } | ||||
| pub(super) fn has_entry(&self) -> bool { | pub(super) fn node_data( | ||||
| self.entry.state != b'\0' | &self, | ||||
| ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> { | |||||
| let entry = |state| { | |||||
| dirstate_map::NodeData::Entry(self.entry_with_given_state(state)) | |||||
| }; | |||||
| match self.state { | |||||
| b'\0' => Ok(dirstate_map::NodeData::None), | |||||
| b'd' => Ok(dirstate_map::NodeData::CachedDirectory { | |||||
| mtime: *self.data.as_timestamp(), | |||||
| }), | |||||
| b'n' => Ok(entry(EntryState::Normal)), | |||||
| b'a' => Ok(entry(EntryState::Added)), | |||||
| b'r' => Ok(entry(EntryState::Removed)), | |||||
| b'm' => Ok(entry(EntryState::Merged)), | |||||
| _ => Err(DirstateV2ParseError), | |||||
| } | |||||
| } | } | ||||
| pub(super) fn state( | pub(super) fn state( | ||||
| &self, | &self, | ||||
| ) -> Result<Option<EntryState>, DirstateV2ParseError> { | ) -> Result<Option<EntryState>, DirstateV2ParseError> { | ||||
| Ok(if self.has_entry() { | match self.state { | ||||
| Some( | b'\0' | b'd' => Ok(None), | ||||
| self.entry | b'n' => Ok(Some(EntryState::Normal)), | ||||
| .state | b'a' => Ok(Some(EntryState::Added)), | ||||
| .try_into() | b'r' => Ok(Some(EntryState::Removed)), | ||||
| .map_err(|_| DirstateV2ParseError)?, | b'm' => Ok(Some(EntryState::Merged)), | ||||
| ) | _ => Err(DirstateV2ParseError), | ||||
| } else { | } | ||||
| None | } | ||||
| }) | |||||
| fn entry_with_given_state(&self, state: EntryState) -> DirstateEntry { | |||||
| DirstateEntry { | |||||
| state, | |||||
| mode: self.data.mode.get(), | |||||
| mtime: self.data.mtime.get(), | |||||
| size: self.data.size.get(), | |||||
| } | |||||
| } | } | ||||
| pub(super) fn entry( | pub(super) fn entry( | ||||
| &self, | &self, | ||||
| ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ||||
| Ok(self.state()?.map(|state| DirstateEntry { | Ok(self | ||||
| state, | .state()? | ||||
| mode: self.entry.mode.get(), | .map(|state| self.entry_with_given_state(state))) | ||||
| mtime: self.entry.mtime.get(), | |||||
| size: self.entry.size.get(), | |||||
| })) | |||||
| } | } | ||||
| pub(super) fn children<'on_disk>( | pub(super) fn children<'on_disk>( | ||||
| &self, | &self, | ||||
| on_disk: &'on_disk [u8], | on_disk: &'on_disk [u8], | ||||
| ) -> Result<&'on_disk [Node], DirstateV2ParseError> { | ) -> Result<&'on_disk [Node], DirstateV2ParseError> { | ||||
| read_slice::<Node>(on_disk, self.children) | read_slice::<Node>(on_disk, self.children) | ||||
| } | } | ||||
| pub(super) fn to_in_memory_node<'on_disk>( | pub(super) fn to_in_memory_node<'on_disk>( | ||||
| &self, | &self, | ||||
| on_disk: &'on_disk [u8], | on_disk: &'on_disk [u8], | ||||
| ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> { | ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> { | ||||
| Ok(dirstate_map::Node { | Ok(dirstate_map::Node { | ||||
| children: dirstate_map::ChildNodes::OnDisk( | children: dirstate_map::ChildNodes::OnDisk( | ||||
| self.children(on_disk)?, | self.children(on_disk)?, | ||||
| ), | ), | ||||
| copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed), | copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed), | ||||
| entry: self.entry()?, | data: self.node_data()?, | ||||
| tracked_descendants_count: self.tracked_descendants_count.get(), | tracked_descendants_count: self.tracked_descendants_count.get(), | ||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| impl Entry { | |||||
| fn from_timestamp(timestamp: Timestamp) -> Self { | |||||
| // Safety: both types implement the `ByteCast` trait, so we could | |||||
| // safely use `as_bytes` and `from_bytes` to do this conversion. Using | |||||
| // `transmute` instead makes the compiler check that the two types | |||||
| // have the same size, which eliminates the error case of | |||||
| // `from_bytes`. | |||||
| unsafe { std::mem::transmute::<Timestamp, Entry>(timestamp) } | |||||
| } | |||||
| fn as_timestamp(&self) -> &Timestamp { | |||||
| // Safety: same as above in `from_timestamp` | |||||
| unsafe { &*(self as *const Entry as *const Timestamp) } | |||||
| } | |||||
| } | |||||
| impl From<&'_ SystemTime> for Timestamp { | |||||
| fn from(system_time: &'_ SystemTime) -> Self { | |||||
| let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) { | |||||
| Ok(duration) => { | |||||
| (duration.as_secs() as i64, duration.subsec_nanos()) | |||||
| } | |||||
| Err(error) => { | |||||
| let negative = error.duration(); | |||||
| (-(negative.as_secs() as i64), negative.subsec_nanos()) | |||||
| } | |||||
| }; | |||||
| Timestamp { | |||||
| seconds: secs.into(), | |||||
| nanoseconds: nanos.into(), | |||||
| } | |||||
| } | |||||
| } | |||||
| impl From<&'_ Timestamp> for SystemTime { | |||||
| fn from(timestamp: &'_ Timestamp) -> Self { | |||||
| let secs = timestamp.seconds.get(); | |||||
| let nanos = timestamp.nanoseconds.get(); | |||||
| if secs >= 0 { | |||||
| UNIX_EPOCH + Duration::new(secs as u64, nanos) | |||||
| } else { | |||||
| UNIX_EPOCH - Duration::new((-secs) as u64, nanos) | |||||
| } | |||||
| } | |||||
| } | |||||
| fn read_hg_path( | fn read_hg_path( | ||||
| on_disk: &[u8], | on_disk: &[u8], | ||||
| slice: Slice, | slice: Slice, | ||||
| ) -> Result<&HgPath, DirstateV2ParseError> { | ) -> Result<&HgPath, DirstateV2ParseError> { | ||||
| let bytes = read_slice::<u8>(on_disk, slice)?; | let bytes = read_slice::<u8>(on_disk, slice)?; | ||||
| Ok(HgPath::new(bytes)) | Ok(HgPath::new(bytes)) | ||||
| } | } | ||||
| write_slice::<u8>(source.as_bytes(), out) | write_slice::<u8>(source.as_bytes(), out) | ||||
| } else { | } else { | ||||
| Slice { | Slice { | ||||
| start: 0.into(), | start: 0.into(), | ||||
| len: 0.into(), | len: 0.into(), | ||||
| } | } | ||||
| }; | }; | ||||
| on_disk_nodes.push(match node { | on_disk_nodes.push(match node { | ||||
| NodeRef::InMemory(path, node) => Node { | NodeRef::InMemory(path, node) => { | ||||
| let (state, data) = match &node.data { | |||||
| dirstate_map::NodeData::Entry(entry) => ( | |||||
| entry.state.into(), | |||||
| Entry { | |||||
| mode: entry.mode.into(), | |||||
| mtime: entry.mtime.into(), | |||||
| size: entry.size.into(), | |||||
| }, | |||||
| ), | |||||
| dirstate_map::NodeData::CachedDirectory { mtime } => { | |||||
| (b'd', Entry::from_timestamp(*mtime)) | |||||
| } | |||||
| dirstate_map::NodeData::None => ( | |||||
| b'\0', | |||||
| Entry { | |||||
| mode: 0.into(), | |||||
| mtime: 0.into(), | |||||
| size: 0.into(), | |||||
| }, | |||||
| ), | |||||
| }; | |||||
| Node { | |||||
| children, | children, | ||||
| copy_source, | copy_source, | ||||
| full_path, | full_path, | ||||
| base_name_start: u32::try_from(path.base_name_start()) | base_name_start: u32::try_from(path.base_name_start()) | ||||
| // Could only panic for paths over 4 GiB | // Could only panic for paths over 4 GiB | ||||
| .expect("dirstate-v2 offset overflow") | .expect("dirstate-v2 offset overflow") | ||||
| .into(), | .into(), | ||||
| tracked_descendants_count: node | tracked_descendants_count: node | ||||
| .tracked_descendants_count | .tracked_descendants_count | ||||
| .into(), | .into(), | ||||
| entry: if let Some(entry) = &node.entry { | state, | ||||
| OptEntry { | data, | ||||
| state: entry.state.into(), | |||||
| mode: entry.mode.into(), | |||||
| mtime: entry.mtime.into(), | |||||
| size: entry.size.into(), | |||||
| } | } | ||||
| } else { | |||||
| OptEntry { | |||||
| state: b'\0', | |||||
| mode: 0.into(), | |||||
| mtime: 0.into(), | |||||
| size: 0.into(), | |||||
| } | } | ||||
| }, | |||||
| }, | |||||
| NodeRef::OnDisk(node) => Node { | NodeRef::OnDisk(node) => Node { | ||||
| children, | children, | ||||
| copy_source, | copy_source, | ||||
| full_path, | full_path, | ||||
| ..*node | ..*node | ||||
| }, | }, | ||||
| }) | }) | ||||
| } | } | ||||