Changeset View
Changeset View
Standalone View
Standalone View
rust/hg-core/src/dirstate_tree/on_disk.rs
Show First 20 Lines • Show All 91 Lines • ▼ Show 20 Line(s) | pub(super) struct Node { | ||||
/// In bytes from `self.full_path.start` | /// In bytes from `self.full_path.start` | ||||
base_name_start: PathSize, | base_name_start: PathSize, | ||||
copy_source: OptPathSlice, | copy_source: OptPathSlice, | ||||
children: ChildNodes, | children: ChildNodes, | ||||
pub(super) descendants_with_entry_count: Size, | pub(super) descendants_with_entry_count: Size, | ||||
pub(super) tracked_descendants_count: Size, | pub(super) tracked_descendants_count: Size, | ||||
flags: Flags, | flags: Flags, | ||||
data: Entry, | size: U32Be, | ||||
mtime: PackedTruncatedTimestamp, | |||||
} | } | ||||
bitflags! { | bitflags! { | ||||
#[derive(BytesCast)] | #[derive(BytesCast)] | ||||
#[repr(C)] | #[repr(C)] | ||||
struct Flags: u8 { | struct Flags: u8 { | ||||
const WDIR_TRACKED = 1 << 0; | const WDIR_TRACKED = 1 << 0; | ||||
const P1_TRACKED = 1 << 1; | const P1_TRACKED = 1 << 1; | ||||
const P2_INFO = 1 << 2; | const P2_INFO = 1 << 2; | ||||
const HAS_MODE_AND_SIZE = 1 << 3; | const HAS_MODE_AND_SIZE = 1 << 3; | ||||
const HAS_MTIME = 1 << 4; | const HAS_MTIME = 1 << 4; | ||||
const MODE_EXEC_PERM = 1 << 5; | const MODE_EXEC_PERM = 1 << 5; | ||||
const MODE_IS_SYMLINK = 1 << 7; | const MODE_IS_SYMLINK = 1 << 6; | ||||
} | |||||
} | } | ||||
#[derive(BytesCast, Copy, Clone, Debug)] | |||||
#[repr(C)] | |||||
struct Entry { | |||||
_padding: U32Be, | |||||
size: U32Be, | |||||
mtime: U32Be, | |||||
} | } | ||||
/// Duration since the Unix epoch | /// Duration since the Unix epoch | ||||
#[derive(BytesCast, Copy, Clone)] | #[derive(BytesCast, Copy, Clone)] | ||||
#[repr(C)] | #[repr(C)] | ||||
struct PackedTimestamp { | struct PackedTruncatedTimestamp { | ||||
_padding: U32Be, | |||||
truncated_seconds: U32Be, | truncated_seconds: U32Be, | ||||
nanoseconds: U32Be, | nanoseconds: U32Be, | ||||
} | } | ||||
/// Counted in bytes from the start of the file | /// Counted in bytes from the start of the file | ||||
/// | /// | ||||
/// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB. | /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB. | ||||
type Offset = U32Be; | type Offset = U32Be; | ||||
▲ Show 20 Lines • Show All 186 Lines • ▼ Show 20 Line(s) | ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> { | ||||
} | } | ||||
} | } | ||||
pub(super) fn cached_directory_mtime( | pub(super) fn cached_directory_mtime( | ||||
&self, | &self, | ||||
) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> { | ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> { | ||||
Ok( | Ok( | ||||
if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() { | if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() { | ||||
Some(self.data.as_timestamp()?) | Some(self.mtime.try_into()?) | ||||
} else { | } else { | ||||
None | None | ||||
}, | }, | ||||
) | ) | ||||
} | } | ||||
fn synthesize_unix_mode(&self) -> u32 { | fn synthesize_unix_mode(&self) -> u32 { | ||||
let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) { | let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) { | ||||
Show All 10 Lines | impl Node { | ||||
} | } | ||||
fn assume_entry(&self) -> DirstateEntry { | fn assume_entry(&self) -> DirstateEntry { | ||||
// TODO: convert through raw bits instead? | // TODO: convert through raw bits instead? | ||||
let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); | let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); | ||||
let p1_tracked = self.flags.contains(Flags::P1_TRACKED); | let p1_tracked = self.flags.contains(Flags::P1_TRACKED); | ||||
let p2_info = self.flags.contains(Flags::P2_INFO); | let p2_info = self.flags.contains(Flags::P2_INFO); | ||||
let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) { | let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) { | ||||
Some((self.synthesize_unix_mode(), self.data.size.into())) | Some((self.synthesize_unix_mode(), self.size.into())) | ||||
} else { | } else { | ||||
None | None | ||||
}; | }; | ||||
let mtime = if self.flags.contains(Flags::HAS_MTIME) { | let mtime = if self.flags.contains(Flags::HAS_MTIME) { | ||||
Some(self.data.mtime.into()) | Some(self.mtime.truncated_seconds.into()) | ||||
} else { | } else { | ||||
None | None | ||||
}; | }; | ||||
DirstateEntry::from_v2_data( | DirstateEntry::from_v2_data( | ||||
wdir_tracked, | wdir_tracked, | ||||
p1_tracked, | p1_tracked, | ||||
p2_info, | p2_info, | ||||
mode_size, | mode_size, | ||||
Show All 29 Lines | ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> { | ||||
copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed), | copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed), | ||||
data: self.node_data()?, | data: self.node_data()?, | ||||
descendants_with_entry_count: self | descendants_with_entry_count: self | ||||
.descendants_with_entry_count | .descendants_with_entry_count | ||||
.get(), | .get(), | ||||
tracked_descendants_count: self.tracked_descendants_count.get(), | tracked_descendants_count: self.tracked_descendants_count.get(), | ||||
}) | }) | ||||
} | } | ||||
} | |||||
impl Entry { | fn from_dirstate_entry( | ||||
fn from_dirstate_entry(entry: &DirstateEntry) -> (Flags, Self) { | entry: &DirstateEntry, | ||||
) -> (Flags, U32Be, PackedTruncatedTimestamp) { | |||||
let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) = | let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) = | ||||
entry.v2_data(); | entry.v2_data(); | ||||
// TODO: convert throug raw flag bits instead? | // TODO: convert throug raw flag bits instead? | ||||
let mut flags = Flags::empty(); | let mut flags = Flags::empty(); | ||||
flags.set(Flags::WDIR_TRACKED, wdir_tracked); | flags.set(Flags::WDIR_TRACKED, wdir_tracked); | ||||
flags.set(Flags::P1_TRACKED, p1_tracked); | flags.set(Flags::P1_TRACKED, p1_tracked); | ||||
flags.set(Flags::P2_INFO, p2_info); | flags.set(Flags::P2_INFO, p2_info); | ||||
let (size, mtime); | let size = if let Some((m, s)) = mode_size_opt { | ||||
if let Some((m, s)) = mode_size_opt { | |||||
let exec_perm = m & libc::S_IXUSR != 0; | let exec_perm = m & libc::S_IXUSR != 0; | ||||
let is_symlink = m & libc::S_IFMT == libc::S_IFLNK; | let is_symlink = m & libc::S_IFMT == libc::S_IFLNK; | ||||
flags.set(Flags::MODE_EXEC_PERM, exec_perm); | flags.set(Flags::MODE_EXEC_PERM, exec_perm); | ||||
flags.set(Flags::MODE_IS_SYMLINK, is_symlink); | flags.set(Flags::MODE_IS_SYMLINK, is_symlink); | ||||
size = s; | flags.insert(Flags::HAS_MODE_AND_SIZE); | ||||
flags.insert(Flags::HAS_MODE_AND_SIZE) | s.into() | ||||
} else { | |||||
size = 0; | |||||
} | |||||
if let Some(m) = mtime_opt { | |||||
mtime = m; | |||||
flags.insert(Flags::HAS_MTIME); | |||||
} else { | } else { | ||||
mtime = 0; | 0.into() | ||||
} | |||||
let raw_entry = Entry { | |||||
_padding: 0.into(), | |||||
size: size.into(), | |||||
mtime: mtime.into(), | |||||
}; | }; | ||||
(flags, raw_entry) | let mtime = if let Some(m) = mtime_opt { | ||||
flags.insert(Flags::HAS_MTIME); | |||||
PackedTruncatedTimestamp { | |||||
truncated_seconds: m.into(), | |||||
nanoseconds: 0.into(), | |||||
} | } | ||||
} else { | |||||
fn from_timestamp(timestamp: TruncatedTimestamp) -> Self { | PackedTruncatedTimestamp::null() | ||||
let packed = PackedTimestamp { | |||||
_padding: 0.into(), | |||||
truncated_seconds: timestamp.truncated_seconds().into(), | |||||
nanoseconds: timestamp.nanoseconds().into(), | |||||
}; | }; | ||||
// Safety: both types implement the `ByteCast` trait, so we could | (flags, size, mtime) | ||||
// 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::<PackedTimestamp, Entry>(packed) } | |||||
} | |||||
fn as_timestamp(self) -> Result<TruncatedTimestamp, DirstateV2ParseError> { | |||||
// Safety: same as above in `from_timestamp` | |||||
let packed = | |||||
unsafe { std::mem::transmute::<Entry, PackedTimestamp>(self) }; | |||||
TruncatedTimestamp::from_already_truncated( | |||||
packed.truncated_seconds.get(), | |||||
packed.nanoseconds.get(), | |||||
) | |||||
} | } | ||||
} | } | ||||
fn read_hg_path( | fn read_hg_path( | ||||
on_disk: &[u8], | on_disk: &[u8], | ||||
slice: PathSlice, | slice: PathSlice, | ||||
) -> Result<&HgPath, DirstateV2ParseError> { | ) -> Result<&HgPath, DirstateV2ParseError> { | ||||
read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new) | read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new) | ||||
▲ Show 20 Lines • Show All 129 Lines • ▼ Show 20 Line(s) | ) -> Result<ChildNodes, DirstateError> { | ||||
} else { | } else { | ||||
PathSlice { | PathSlice { | ||||
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) => { | NodeRef::InMemory(path, node) => { | ||||
let (flags, data) = match &node.data { | let (flags, size, mtime) = match &node.data { | ||||
dirstate_map::NodeData::Entry(entry) => { | dirstate_map::NodeData::Entry(entry) => { | ||||
Entry::from_dirstate_entry(entry) | Node::from_dirstate_entry(entry) | ||||
} | } | ||||
dirstate_map::NodeData::CachedDirectory { mtime } => { | dirstate_map::NodeData::CachedDirectory { mtime } => { | ||||
(Flags::HAS_MTIME, Entry::from_timestamp(*mtime)) | (Flags::HAS_MTIME, 0.into(), (*mtime).into()) | ||||
} | } | ||||
dirstate_map::NodeData::None => ( | dirstate_map::NodeData::None => ( | ||||
Flags::empty(), | Flags::empty(), | ||||
Entry { | 0.into(), | ||||
_padding: 0.into(), | PackedTruncatedTimestamp::null(), | ||||
size: 0.into(), | |||||
mtime: 0.into(), | |||||
}, | |||||
), | ), | ||||
}; | }; | ||||
Node { | Node { | ||||
children, | children, | ||||
copy_source, | copy_source, | ||||
full_path, | full_path, | ||||
base_name_start: u16::try_from(path.base_name_start()) | base_name_start: u16::try_from(path.base_name_start()) | ||||
// Could only panic for paths over 64 KiB | // Could only panic for paths over 64 KiB | ||||
.expect("dirstate-v2 path length overflow") | .expect("dirstate-v2 path length overflow") | ||||
.into(), | .into(), | ||||
descendants_with_entry_count: node | descendants_with_entry_count: node | ||||
.descendants_with_entry_count | .descendants_with_entry_count | ||||
.into(), | .into(), | ||||
tracked_descendants_count: node | tracked_descendants_count: node | ||||
.tracked_descendants_count | .tracked_descendants_count | ||||
.into(), | .into(), | ||||
flags, | flags, | ||||
data, | size, | ||||
mtime, | |||||
} | } | ||||
} | } | ||||
NodeRef::OnDisk(node) => Node { | NodeRef::OnDisk(node) => Node { | ||||
children, | children, | ||||
copy_source, | copy_source, | ||||
full_path, | full_path, | ||||
..*node | ..*node | ||||
}, | }, | ||||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Line(s) | |||||
} | } | ||||
fn path_len_from_usize(x: usize) -> PathSize { | fn path_len_from_usize(x: usize) -> PathSize { | ||||
u16::try_from(x) | u16::try_from(x) | ||||
// Could only panic for paths over 64 KiB | // Could only panic for paths over 64 KiB | ||||
.expect("dirstate-v2 path length overflow") | .expect("dirstate-v2 path length overflow") | ||||
.into() | .into() | ||||
} | } | ||||
impl From<TruncatedTimestamp> for PackedTruncatedTimestamp { | |||||
fn from(timestamp: TruncatedTimestamp) -> Self { | |||||
Self { | |||||
truncated_seconds: timestamp.truncated_seconds().into(), | |||||
nanoseconds: timestamp.nanoseconds().into(), | |||||
} | |||||
} | |||||
} | |||||
impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp { | |||||
type Error = DirstateV2ParseError; | |||||
fn try_from( | |||||
timestamp: PackedTruncatedTimestamp, | |||||
) -> Result<Self, Self::Error> { | |||||
Self::from_already_truncated( | |||||
timestamp.truncated_seconds.get(), | |||||
timestamp.nanoseconds.get(), | |||||
) | |||||
} | |||||
} | |||||
impl PackedTruncatedTimestamp { | |||||
fn null() -> Self { | |||||
Self { | |||||
truncated_seconds: 0.into(), | |||||
nanoseconds: 0.into(), | |||||
} | |||||
} | |||||
} |