entry.rs
707 lines
| 22.6 KiB
| application/rls-services+xml
|
RustLexer
Simon Sapin
|
r49007 | use crate::dirstate_tree::on_disk::DirstateV2ParseError; | ||
Simon Sapin
|
r48830 | use crate::errors::HgError; | ||
Simon Sapin
|
r48856 | use bitflags::bitflags; | ||
Simon Sapin
|
r49032 | use std::convert::{TryFrom, TryInto}; | ||
use std::fs; | ||||
use std::io; | ||||
Simon Sapin
|
r49006 | use std::time::{SystemTime, UNIX_EPOCH}; | ||
Simon Sapin
|
r48830 | |||
#[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||||
pub enum EntryState { | ||||
Normal, | ||||
Added, | ||||
Removed, | ||||
Merged, | ||||
} | ||||
Simon Sapin
|
r49079 | /// `size` and `mtime.seconds` are truncated to 31 bits. | ||
/// | ||||
/// TODO: double-check status algorithm correctness for files | ||||
/// larger than 2 GiB or modified after 2038. | ||||
#[derive(Debug, Copy, Clone)] | ||||
Simon Sapin
|
r48830 | pub struct DirstateEntry { | ||
Simon Sapin
|
r48951 | pub(crate) flags: Flags, | ||
Simon Sapin
|
r49008 | mode_size: Option<(u32, u32)>, | ||
Simon Sapin
|
r49079 | mtime: Option<TruncatedTimestamp>, | ||
Simon Sapin
|
r48830 | } | ||
Simon Sapin
|
r48856 | bitflags! { | ||
Simon Sapin
|
r48951 | pub(crate) struct Flags: u8 { | ||
Simon Sapin
|
r48856 | const WDIR_TRACKED = 1 << 0; | ||
const P1_TRACKED = 1 << 1; | ||||
r48950 | const P2_INFO = 1 << 2; | |||
r49068 | const HAS_FALLBACK_EXEC = 1 << 3; | |||
const FALLBACK_EXEC = 1 << 4; | ||||
const HAS_FALLBACK_SYMLINK = 1 << 5; | ||||
const FALLBACK_SYMLINK = 1 << 6; | ||||
Simon Sapin
|
r48856 | } | ||
} | ||||
Simon Sapin
|
r49007 | /// A Unix timestamp with nanoseconds precision | ||
Simon Sapin
|
r49079 | #[derive(Debug, Copy, Clone)] | ||
Simon Sapin
|
r49007 | pub struct TruncatedTimestamp { | ||
truncated_seconds: u32, | ||||
/// Always in the `0 .. 1_000_000_000` range. | ||||
Simon Sapin
|
r49006 | nanoseconds: u32, | ||
r49230 | /// TODO this should be in DirstateEntry, but the current code needs | |||
/// refactoring to use DirstateEntry instead of TruncatedTimestamp for | ||||
/// comparison. | ||||
pub second_ambiguous: bool, | ||||
Simon Sapin
|
r49006 | } | ||
Simon Sapin
|
r49007 | impl TruncatedTimestamp { | ||
/// Constructs from a timestamp potentially outside of the supported range, | ||||
/// and truncate the seconds components to its lower 31 bits. | ||||
/// | ||||
/// Panics if the nanoseconds components is not in the expected range. | ||||
r49227 | pub fn new_truncate( | |||
seconds: i64, | ||||
nanoseconds: u32, | ||||
second_ambiguous: bool, | ||||
) -> Self { | ||||
Simon Sapin
|
r49007 | assert!(nanoseconds < NSEC_PER_SEC); | ||
Simon Sapin
|
r49006 | Self { | ||
Simon Sapin
|
r49007 | truncated_seconds: seconds as u32 & RANGE_MASK_31BIT, | ||
Simon Sapin
|
r49006 | nanoseconds, | ||
r49227 | second_ambiguous, | |||
Simon Sapin
|
r49006 | } | ||
} | ||||
Simon Sapin
|
r49007 | /// Construct from components. Returns an error if they are not in the | ||
/// expcted range. | ||||
pub fn from_already_truncated( | ||||
truncated_seconds: u32, | ||||
nanoseconds: u32, | ||||
r49227 | second_ambiguous: bool, | |||
Simon Sapin
|
r49007 | ) -> Result<Self, DirstateV2ParseError> { | ||
if truncated_seconds & !RANGE_MASK_31BIT == 0 | ||||
&& nanoseconds < NSEC_PER_SEC | ||||
{ | ||||
Ok(Self { | ||||
truncated_seconds, | ||||
nanoseconds, | ||||
r49227 | second_ambiguous, | |||
Simon Sapin
|
r49007 | }) | ||
} else { | ||||
Err(DirstateV2ParseError) | ||||
} | ||||
Simon Sapin
|
r49006 | } | ||
Simon Sapin
|
r49272 | /// Returns a `TruncatedTimestamp` for the modification time of `metadata`. | ||
/// | ||||
/// Propagates errors from `std` on platforms where modification time | ||||
/// is not available at all. | ||||
Simon Sapin
|
r49032 | pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> { | ||
#[cfg(unix)] | ||||
{ | ||||
use std::os::unix::fs::MetadataExt; | ||||
let seconds = metadata.mtime(); | ||||
// i64Â -> u32 with value always in the `0 .. NSEC_PER_SEC` range | ||||
let nanoseconds = metadata.mtime_nsec().try_into().unwrap(); | ||||
r49227 | Ok(Self::new_truncate(seconds, nanoseconds, false)) | |||
Simon Sapin
|
r49032 | } | ||
#[cfg(not(unix))] | ||||
{ | ||||
metadata.modified().map(Self::from) | ||||
} | ||||
} | ||||
Simon Sapin
|
r49272 | /// Like `for_mtime_of`, but may return `None` or a value with | ||
/// `second_ambiguous` set if the mtime is not "reliable". | ||||
Simon Sapin
|
r49250 | /// | ||
/// A modification time is reliable if it is older than `boundary` (or | ||||
/// sufficiently in the future). | ||||
/// | ||||
/// Otherwise a concurrent modification might happens with the same mtime. | ||||
Simon Sapin
|
r49272 | pub fn for_reliable_mtime_of( | ||
metadata: &fs::Metadata, | ||||
boundary: &Self, | ||||
) -> io::Result<Option<Self>> { | ||||
let mut mtime = Self::for_mtime_of(metadata)?; | ||||
Simon Sapin
|
r49250 | // If the mtime of the ambiguous file is younger (or equal) to the | ||
// starting point of the `status` walk, we cannot garantee that | ||||
// another, racy, write will not happen right after with the same mtime | ||||
// and we cannot cache the information. | ||||
// | ||||
// However if the mtime is far away in the future, this is likely some | ||||
// mismatch between the current clock and previous file system | ||||
// operation. So mtime more than one days in the future are considered | ||||
// fine. | ||||
Simon Sapin
|
r49272 | let reliable = if mtime.truncated_seconds == boundary.truncated_seconds | ||
{ | ||||
mtime.second_ambiguous = true; | ||||
mtime.nanoseconds != 0 | ||||
Simon Sapin
|
r49250 | && boundary.nanoseconds != 0 | ||
Simon Sapin
|
r49272 | && mtime.nanoseconds < boundary.nanoseconds | ||
Simon Sapin
|
r49250 | } else { | ||
// `truncated_seconds` is less than 2**31, | ||||
// so this does not overflow `u32`: | ||||
let one_day_later = boundary.truncated_seconds + 24 * 3600; | ||||
Simon Sapin
|
r49272 | mtime.truncated_seconds < boundary.truncated_seconds | ||
|| mtime.truncated_seconds > one_day_later | ||||
}; | ||||
if reliable { | ||||
Ok(Some(mtime)) | ||||
} else { | ||||
Ok(None) | ||||
Simon Sapin
|
r49250 | } | ||
} | ||||
Simon Sapin
|
r49007 | /// The lower 31 bits of the number of seconds since the epoch. | ||
pub fn truncated_seconds(&self) -> u32 { | ||||
self.truncated_seconds | ||||
} | ||||
/// The sub-second component of this timestamp, in nanoseconds. | ||||
/// Always in the `0 .. 1_000_000_000` range. | ||||
/// | ||||
/// This timestamp is after `(seconds, 0)` by this many nanoseconds. | ||||
Simon Sapin
|
r49006 | pub fn nanoseconds(&self) -> u32 { | ||
self.nanoseconds | ||||
} | ||||
Simon Sapin
|
r49007 | |||
/// Returns whether two timestamps are equal modulo 2**31 seconds. | ||||
/// | ||||
/// If this returns `true`, the original values converted from `SystemTime` | ||||
/// or given to `new_truncate` were very likely equal. A false positive is | ||||
/// possible if they were exactly a multiple of 2**31 seconds apart (around | ||||
/// 68 years). This is deemed very unlikely to happen by chance, especially | ||||
/// on filesystems that support sub-second precision. | ||||
/// | ||||
/// If someone is manipulating the modification times of some files to | ||||
/// intentionally make `hg status` return incorrect results, not truncating | ||||
/// wouldn’t help much since they can set exactly the expected timestamp. | ||||
Simon Sapin
|
r49081 | /// | ||
/// Sub-second precision is ignored if it is zero in either value. | ||||
/// Some APIs simply return zero when more precision is not available. | ||||
/// When comparing values from different sources, if only one is truncated | ||||
/// in that way, doing a simple comparison would cause many false | ||||
/// negatives. | ||||
Simon Sapin
|
r49076 | pub fn likely_equal(self, other: Self) -> bool { | ||
r49228 | if self.truncated_seconds != other.truncated_seconds { | |||
false | ||||
} else if self.nanoseconds == 0 || other.nanoseconds == 0 { | ||||
if self.second_ambiguous { | ||||
false | ||||
} else { | ||||
true | ||||
} | ||||
} else { | ||||
self.nanoseconds == other.nanoseconds | ||||
} | ||||
Simon Sapin
|
r49007 | } | ||
Simon Sapin
|
r49032 | |||
Simon Sapin
|
r49076 | pub fn likely_equal_to_mtime_of( | ||
Simon Sapin
|
r49032 | self, | ||
metadata: &fs::Metadata, | ||||
) -> io::Result<bool> { | ||||
Simon Sapin
|
r49076 | Ok(self.likely_equal(Self::for_mtime_of(metadata)?)) | ||
Simon Sapin
|
r49032 | } | ||
Simon Sapin
|
r49006 | } | ||
Simon Sapin
|
r49007 | impl From<SystemTime> for TruncatedTimestamp { | ||
Simon Sapin
|
r49006 | fn from(system_time: SystemTime) -> Self { | ||
// On Unix, `SystemTime` is a wrapper for the `timespec` C struct: | ||||
// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec | ||||
// We want to effectively access its fields, but the Rust standard | ||||
// library does not expose them. The best we can do is: | ||||
let seconds; | ||||
let nanoseconds; | ||||
match system_time.duration_since(UNIX_EPOCH) { | ||||
Ok(duration) => { | ||||
seconds = duration.as_secs() as i64; | ||||
nanoseconds = duration.subsec_nanos(); | ||||
} | ||||
Err(error) => { | ||||
// `system_time` is before `UNIX_EPOCH`. | ||||
// We need to undo this algorithm: | ||||
// https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41 | ||||
let negative = error.duration(); | ||||
let negative_secs = negative.as_secs() as i64; | ||||
let negative_nanos = negative.subsec_nanos(); | ||||
if negative_nanos == 0 { | ||||
seconds = -negative_secs; | ||||
nanoseconds = 0; | ||||
} else { | ||||
// For example if `system_time` was 4.3Â seconds before | ||||
// the Unix epoch we get a Duration that represents | ||||
// `(-4, -0.3)` but we want `(-5, +0.7)`: | ||||
seconds = -1 - negative_secs; | ||||
nanoseconds = NSEC_PER_SEC - negative_nanos; | ||||
} | ||||
} | ||||
}; | ||||
r49227 | Self::new_truncate(seconds, nanoseconds, false) | |||
Simon Sapin
|
r49006 | } | ||
} | ||||
Simon Sapin
|
r49007 | const NSEC_PER_SEC: u32 = 1_000_000_000; | ||
Simon Sapin
|
r49250 | pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; | ||
Simon Sapin
|
r48830 | |||
pub const MTIME_UNSET: i32 = -1; | ||||
/// A `DirstateEntry` with a size of `-2` means that it was merged from the | ||||
/// other parent. This allows revert to pick the right status back during a | ||||
/// merge. | ||||
pub const SIZE_FROM_OTHER_PARENT: i32 = -2; | ||||
/// A special value used for internal representation of special case in | ||||
/// dirstate v1 format. | ||||
pub const SIZE_NON_NORMAL: i32 = -1; | ||||
impl DirstateEntry { | ||||
Simon Sapin
|
r48951 | pub fn from_v2_data( | ||
r48950 | wdir_tracked: bool, | |||
p1_tracked: bool, | ||||
p2_info: bool, | ||||
Simon Sapin
|
r49008 | mode_size: Option<(u32, u32)>, | ||
Simon Sapin
|
r49079 | mtime: Option<TruncatedTimestamp>, | ||
r49069 | fallback_exec: Option<bool>, | |||
fallback_symlink: Option<bool>, | ||||
Simon Sapin
|
r48857 | ) -> Self { | ||
Simon Sapin
|
r49008 | if let Some((mode, size)) = mode_size { | ||
// TODO: return an error for out of range values? | ||||
assert!(mode & !RANGE_MASK_31BIT == 0); | ||||
assert!(size & !RANGE_MASK_31BIT == 0); | ||||
} | ||||
r48950 | let mut flags = Flags::empty(); | |||
flags.set(Flags::WDIR_TRACKED, wdir_tracked); | ||||
flags.set(Flags::P1_TRACKED, p1_tracked); | ||||
flags.set(Flags::P2_INFO, p2_info); | ||||
r49069 | if let Some(exec) = fallback_exec { | |||
flags.insert(Flags::HAS_FALLBACK_EXEC); | ||||
if exec { | ||||
flags.insert(Flags::FALLBACK_EXEC); | ||||
} | ||||
} | ||||
if let Some(exec) = fallback_symlink { | ||||
flags.insert(Flags::HAS_FALLBACK_SYMLINK); | ||||
if exec { | ||||
flags.insert(Flags::FALLBACK_SYMLINK); | ||||
} | ||||
} | ||||
Simon Sapin
|
r48857 | Self { | ||
flags, | ||||
r48950 | mode_size, | |||
Simon Sapin
|
r48857 | mtime, | ||
} | ||||
} | ||||
Simon Sapin
|
r48834 | pub fn from_v1_data( | ||
state: EntryState, | ||||
mode: i32, | ||||
size: i32, | ||||
mtime: i32, | ||||
) -> Self { | ||||
Simon Sapin
|
r48856 | match state { | ||
EntryState::Normal => { | ||||
if size == SIZE_FROM_OTHER_PARENT { | ||||
r48970 | Self { | |||
// might be missing P1_TRACKED | ||||
flags: Flags::WDIR_TRACKED | Flags::P2_INFO, | ||||
mode_size: None, | ||||
mtime: None, | ||||
} | ||||
Simon Sapin
|
r48856 | } else if size == SIZE_NON_NORMAL { | ||
r48972 | Self { | |||
flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
mode_size: None, | ||||
mtime: None, | ||||
} | ||||
Simon Sapin
|
r48856 | } else if mtime == MTIME_UNSET { | ||
Simon Sapin
|
r49008 | // TODO:Â return an error for negative values? | ||
let mode = u32::try_from(mode).unwrap(); | ||||
let size = u32::try_from(size).unwrap(); | ||||
Simon Sapin
|
r48856 | Self { | ||
r48950 | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | |||
mode_size: Some((mode, size)), | ||||
mtime: None, | ||||
Simon Sapin
|
r48856 | } | ||
} else { | ||||
Simon Sapin
|
r49008 | // TODO:Â return an error for negative values? | ||
let mode = u32::try_from(mode).unwrap(); | ||||
let size = u32::try_from(size).unwrap(); | ||||
let mtime = u32::try_from(mtime).unwrap(); | ||||
r49227 | let mtime = TruncatedTimestamp::from_already_truncated( | |||
mtime, 0, false, | ||||
) | ||||
.unwrap(); | ||||
r48975 | Self { | |||
flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
mode_size: Some((mode, size)), | ||||
mtime: Some(mtime), | ||||
} | ||||
Simon Sapin
|
r48856 | } | ||
} | ||||
r48968 | EntryState::Added => Self { | |||
flags: Flags::WDIR_TRACKED, | ||||
mode_size: None, | ||||
mtime: None, | ||||
}, | ||||
Simon Sapin
|
r48856 | EntryState::Removed => Self { | ||
flags: if size == SIZE_NON_NORMAL { | ||||
r48950 | Flags::P1_TRACKED | Flags::P2_INFO | |||
Simon Sapin
|
r48856 | } else if size == SIZE_FROM_OTHER_PARENT { | ||
// We don’t know if P1_TRACKED should be set (file history) | ||||
r48950 | Flags::P2_INFO | |||
Simon Sapin
|
r48856 | } else { | ||
Flags::P1_TRACKED | ||||
}, | ||||
r48950 | mode_size: None, | |||
mtime: None, | ||||
Simon Sapin
|
r48856 | }, | ||
r48966 | EntryState::Merged => Self { | |||
flags: Flags::WDIR_TRACKED | ||||
| Flags::P1_TRACKED // might not be true because of rename ? | ||||
| Flags::P2_INFO, // might not be true because of rename ? | ||||
mode_size: None, | ||||
mtime: None, | ||||
}, | ||||
Simon Sapin
|
r48856 | } | ||
} | ||||
Simon Sapin
|
r48834 | /// Creates a new entry in "removed" state. | ||
/// | ||||
/// `size` is expected to be zero, `SIZE_NON_NORMAL`, or | ||||
/// `SIZE_FROM_OTHER_PARENT` | ||||
pub fn new_removed(size: i32) -> Self { | ||||
Simon Sapin
|
r48856 | Self::from_v1_data(EntryState::Removed, 0, size, 0) | ||
Simon Sapin
|
r48834 | } | ||
Simon Sapin
|
r48857 | pub fn tracked(&self) -> bool { | ||
self.flags.contains(Flags::WDIR_TRACKED) | ||||
} | ||||
r48955 | pub fn p1_tracked(&self) -> bool { | |||
self.flags.contains(Flags::P1_TRACKED) | ||||
} | ||||
r48950 | fn in_either_parent(&self) -> bool { | |||
self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO) | ||||
Simon Sapin
|
r48856 | } | ||
Simon Sapin
|
r48857 | pub fn removed(&self) -> bool { | ||
r48950 | self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED) | |||
Simon Sapin
|
r48856 | } | ||
r48954 | pub fn p2_info(&self) -> bool { | |||
self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO) | ||||
} | ||||
Simon Sapin
|
r48857 | pub fn added(&self) -> bool { | ||
r48950 | self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent() | |||
Simon Sapin
|
r48856 | } | ||
r48898 | pub fn maybe_clean(&self) -> bool { | |||
if !self.flags.contains(Flags::WDIR_TRACKED) { | ||||
false | ||||
r48950 | } else if !self.flags.contains(Flags::P1_TRACKED) { | |||
r48898 | false | |||
r48950 | } else if self.flags.contains(Flags::P2_INFO) { | |||
r48898 | false | |||
} else { | ||||
true | ||||
} | ||||
} | ||||
r48899 | pub fn any_tracked(&self) -> bool { | |||
self.flags.intersects( | ||||
r48950 | Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, | |||
r48899 | ) | |||
} | ||||
Simon Sapin
|
r48951 | /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)` | ||
pub(crate) fn v2_data( | ||||
&self, | ||||
r49070 | ) -> ( | |||
bool, | ||||
bool, | ||||
bool, | ||||
Option<(u32, u32)>, | ||||
Simon Sapin
|
r49079 | Option<TruncatedTimestamp>, | ||
r49070 | Option<bool>, | |||
Option<bool>, | ||||
) { | ||||
Simon Sapin
|
r48951 | if !self.any_tracked() { | ||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_state of an untracked DirstateEntry") | ||||
} | ||||
let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); | ||||
let p1_tracked = self.flags.contains(Flags::P1_TRACKED); | ||||
let p2_info = self.flags.contains(Flags::P2_INFO); | ||||
let mode_size = self.mode_size; | ||||
let mtime = self.mtime; | ||||
r49070 | ( | |||
wdir_tracked, | ||||
p1_tracked, | ||||
p2_info, | ||||
mode_size, | ||||
mtime, | ||||
self.get_fallback_exec(), | ||||
self.get_fallback_symlink(), | ||||
) | ||||
Simon Sapin
|
r48951 | } | ||
r48950 | fn v1_state(&self) -> EntryState { | |||
if !self.any_tracked() { | ||||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_state of an untracked DirstateEntry") | ||||
} | ||||
Simon Sapin
|
r48856 | if self.removed() { | ||
EntryState::Removed | ||||
r48964 | } else if self | |||
.flags | ||||
.contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO) | ||||
{ | ||||
Simon Sapin
|
r48856 | EntryState::Merged | ||
} else if self.added() { | ||||
EntryState::Added | ||||
} else { | ||||
EntryState::Normal | ||||
} | ||||
Simon Sapin
|
r48834 | } | ||
r48950 | fn v1_mode(&self) -> i32 { | |||
if let Some((mode, _size)) = self.mode_size { | ||||
Simon Sapin
|
r49008 | i32::try_from(mode).unwrap() | ||
r48950 | } else { | |||
0 | ||||
} | ||||
Simon Sapin
|
r48834 | } | ||
r48950 | fn v1_size(&self) -> i32 { | |||
if !self.any_tracked() { | ||||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_size of an untracked DirstateEntry") | ||||
} | ||||
if self.removed() | ||||
&& self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO) | ||||
{ | ||||
Simon Sapin
|
r48856 | SIZE_NON_NORMAL | ||
r48961 | } else if self.flags.contains(Flags::P2_INFO) { | |||
Simon Sapin
|
r48856 | SIZE_FROM_OTHER_PARENT | ||
} else if self.removed() { | ||||
0 | ||||
} else if self.added() { | ||||
SIZE_NON_NORMAL | ||||
r48950 | } else if let Some((_mode, size)) = self.mode_size { | |||
Simon Sapin
|
r49008 | i32::try_from(size).unwrap() | ||
Simon Sapin
|
r48856 | } else { | ||
r48950 | SIZE_NON_NORMAL | |||
Simon Sapin
|
r48856 | } | ||
Simon Sapin
|
r48834 | } | ||
r48950 | fn v1_mtime(&self) -> i32 { | |||
if !self.any_tracked() { | ||||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_mtime of an untracked DirstateEntry") | ||||
} | ||||
Simon Sapin
|
r48856 | if self.removed() { | ||
0 | ||||
r48950 | } else if self.flags.contains(Flags::P2_INFO) { | |||
Simon Sapin
|
r48856 | MTIME_UNSET | ||
r48950 | } else if !self.flags.contains(Flags::P1_TRACKED) { | |||
Simon Sapin
|
r48856 | MTIME_UNSET | ||
Simon Sapin
|
r49008 | } else if let Some(mtime) = self.mtime { | ||
r49229 | if mtime.second_ambiguous { | |||
MTIME_UNSET | ||||
} else { | ||||
i32::try_from(mtime.truncated_seconds()).unwrap() | ||||
} | ||||
Simon Sapin
|
r48856 | } else { | ||
Simon Sapin
|
r49008 | MTIME_UNSET | ||
Simon Sapin
|
r48856 | } | ||
Simon Sapin
|
r48834 | } | ||
r48950 | // TODO: return `Option<EntryState>`? None when `!self.any_tracked` | |||
pub fn state(&self) -> EntryState { | ||||
self.v1_state() | ||||
} | ||||
// TODO: return Option? | ||||
pub fn mode(&self) -> i32 { | ||||
self.v1_mode() | ||||
} | ||||
// TODO: return Option? | ||||
pub fn size(&self) -> i32 { | ||||
self.v1_size() | ||||
} | ||||
// TODO: return Option? | ||||
pub fn mtime(&self) -> i32 { | ||||
self.v1_mtime() | ||||
} | ||||
r49068 | pub fn get_fallback_exec(&self) -> Option<bool> { | |||
if self.flags.contains(Flags::HAS_FALLBACK_EXEC) { | ||||
Some(self.flags.contains(Flags::FALLBACK_EXEC)) | ||||
} else { | ||||
None | ||||
} | ||||
} | ||||
pub fn set_fallback_exec(&mut self, value: Option<bool>) { | ||||
match value { | ||||
None => { | ||||
self.flags.remove(Flags::HAS_FALLBACK_EXEC); | ||||
self.flags.remove(Flags::FALLBACK_EXEC); | ||||
} | ||||
Some(exec) => { | ||||
self.flags.insert(Flags::HAS_FALLBACK_EXEC); | ||||
if exec { | ||||
self.flags.insert(Flags::FALLBACK_EXEC); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
pub fn get_fallback_symlink(&self) -> Option<bool> { | ||||
if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) { | ||||
Some(self.flags.contains(Flags::FALLBACK_SYMLINK)) | ||||
} else { | ||||
None | ||||
} | ||||
} | ||||
pub fn set_fallback_symlink(&mut self, value: Option<bool>) { | ||||
match value { | ||||
None => { | ||||
self.flags.remove(Flags::HAS_FALLBACK_SYMLINK); | ||||
self.flags.remove(Flags::FALLBACK_SYMLINK); | ||||
} | ||||
Some(symlink) => { | ||||
self.flags.insert(Flags::HAS_FALLBACK_SYMLINK); | ||||
if symlink { | ||||
self.flags.insert(Flags::FALLBACK_SYMLINK); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
Simon Sapin
|
r49079 | pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> { | ||
self.mtime | ||||
} | ||||
r48946 | pub fn drop_merge_data(&mut self) { | |||
r48950 | if self.flags.contains(Flags::P2_INFO) { | |||
self.flags.remove(Flags::P2_INFO); | ||||
self.mode_size = None; | ||||
self.mtime = None; | ||||
r48946 | } | |||
} | ||||
Simon Sapin
|
r48857 | pub fn set_possibly_dirty(&mut self) { | ||
r48950 | self.mtime = None | |||
Simon Sapin
|
r48857 | } | ||
Simon Sapin
|
r49079 | pub fn set_clean( | ||
&mut self, | ||||
mode: u32, | ||||
size: u32, | ||||
mtime: TruncatedTimestamp, | ||||
) { | ||||
Simon Sapin
|
r49008 | let size = size & RANGE_MASK_31BIT; | ||
Simon Sapin
|
r48857 | self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); | ||
r48950 | self.mode_size = Some((mode, size)); | |||
self.mtime = Some(mtime); | ||||
Simon Sapin
|
r48857 | } | ||
pub fn set_tracked(&mut self) { | ||||
r48950 | self.flags.insert(Flags::WDIR_TRACKED); | |||
// `set_tracked` is replacing various `normallookup` call. So we mark | ||||
// the files as needing lookup | ||||
// | ||||
// Consider dropping this in the future in favor of something less | ||||
// broad. | ||||
self.mtime = None; | ||||
Simon Sapin
|
r48857 | } | ||
pub fn set_untracked(&mut self) { | ||||
self.flags.remove(Flags::WDIR_TRACKED); | ||||
r48950 | self.mode_size = None; | |||
self.mtime = None; | ||||
Simon Sapin
|
r48857 | } | ||
Simon Sapin
|
r48834 | /// Returns `(state, mode, size, mtime)` for the puprose of serialization | ||
/// in the dirstate-v1 format. | ||||
/// | ||||
/// This includes marker values such as `mtime == -1`. In the future we may | ||||
/// want to not represent these cases that way in memory, but serialization | ||||
/// will need to keep the same format. | ||||
pub fn v1_data(&self) -> (u8, i32, i32, i32) { | ||||
r48950 | ( | |||
self.v1_state().into(), | ||||
self.v1_mode(), | ||||
self.v1_size(), | ||||
self.v1_mtime(), | ||||
) | ||||
Simon Sapin
|
r48834 | } | ||
r48875 | pub(crate) fn is_from_other_parent(&self) -> bool { | |||
Simon Sapin
|
r48856 | self.state() == EntryState::Normal | ||
&& self.size() == SIZE_FROM_OTHER_PARENT | ||||
Simon Sapin
|
r48830 | } | ||
// TODO: other platforms | ||||
#[cfg(unix)] | ||||
pub fn mode_changed( | ||||
&self, | ||||
filesystem_metadata: &std::fs::Metadata, | ||||
) -> bool { | ||||
Simon Sapin
|
r49168 | let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0; | ||
let fs_exec_bit = has_exec_bit(filesystem_metadata); | ||||
Simon Sapin
|
r48830 | dirstate_exec_bit != fs_exec_bit | ||
} | ||||
/// Returns a `(state, mode, size, mtime)` tuple as for | ||||
/// `DirstateMapMethods::debug_iter`. | ||||
pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { | ||||
r48950 | (self.state().into(), self.mode(), self.size(), self.mtime()) | |||
Simon Sapin
|
r48830 | } | ||
} | ||||
impl EntryState { | ||||
pub fn is_tracked(self) -> bool { | ||||
use EntryState::*; | ||||
match self { | ||||
Normal | Added | Merged => true, | ||||
Simon Sapin
|
r48838 | Removed => false, | ||
Simon Sapin
|
r48830 | } | ||
} | ||||
} | ||||
impl TryFrom<u8> for EntryState { | ||||
type Error = HgError; | ||||
fn try_from(value: u8) -> Result<Self, Self::Error> { | ||||
match value { | ||||
b'n' => Ok(EntryState::Normal), | ||||
b'a' => Ok(EntryState::Added), | ||||
b'r' => Ok(EntryState::Removed), | ||||
b'm' => Ok(EntryState::Merged), | ||||
_ => Err(HgError::CorruptedRepository(format!( | ||||
"Incorrect dirstate entry state {}", | ||||
value | ||||
))), | ||||
} | ||||
} | ||||
} | ||||
impl Into<u8> for EntryState { | ||||
fn into(self) -> u8 { | ||||
match self { | ||||
EntryState::Normal => b'n', | ||||
EntryState::Added => b'a', | ||||
EntryState::Removed => b'r', | ||||
EntryState::Merged => b'm', | ||||
} | ||||
} | ||||
} | ||||
Simon Sapin
|
r49168 | |||
const EXEC_BIT_MASK: u32 = 0o100; | ||||
pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool { | ||||
// TODO: How to handle executable permissions on Windows? | ||||
use std::os::unix::fs::MetadataExt; | ||||
(metadata.mode() & EXEC_BIT_MASK) != 0 | ||||
} | ||||