entry.rs
418 lines
| 12.4 KiB
| application/rls-services+xml
|
RustLexer
|
r48830 | use crate::errors::HgError; | ||
|
r48856 | use bitflags::bitflags; | ||
|
r48830 | use std::convert::TryFrom; | ||
#[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||||
pub enum EntryState { | ||||
Normal, | ||||
Added, | ||||
Removed, | ||||
Merged, | ||||
} | ||||
/// The C implementation uses all signed types. This will be an issue | ||||
/// either when 4GB+ source files are commonplace or in 2038, whichever | ||||
/// comes first. | ||||
#[derive(Debug, PartialEq, Copy, Clone)] | ||||
pub struct DirstateEntry { | ||||
|
r48951 | pub(crate) flags: Flags, | ||
r48950 | mode_size: Option<(i32, i32)>, | |||
mtime: Option<i32>, | ||||
|
r48830 | } | ||
|
r48856 | bitflags! { | ||
|
r48951 | pub(crate) struct Flags: u8 { | ||
|
r48856 | const WDIR_TRACKED = 1 << 0; | ||
const P1_TRACKED = 1 << 1; | ||||
r48950 | const P2_INFO = 1 << 2; | |||
|
r48856 | } | ||
} | ||||
|
r48830 | pub const V1_RANGEMASK: i32 = 0x7FFFFFFF; | ||
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 { | ||||
|
r48951 | pub fn from_v2_data( | ||
r48950 | wdir_tracked: bool, | |||
p1_tracked: bool, | ||||
p2_info: bool, | ||||
mode_size: Option<(i32, i32)>, | ||||
mtime: Option<i32>, | ||||
|
r48857 | ) -> Self { | ||
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); | ||||
|
r48857 | Self { | ||
flags, | ||||
r48950 | mode_size, | |||
|
r48857 | mtime, | ||
} | ||||
} | ||||
|
r48834 | pub fn from_v1_data( | ||
state: EntryState, | ||||
mode: i32, | ||||
size: i32, | ||||
mtime: i32, | ||||
) -> Self { | ||||
|
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, | ||||
} | ||||
|
r48856 | } else if size == SIZE_NON_NORMAL { | ||
r48972 | Self { | |||
flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
mode_size: None, | ||||
mtime: None, | ||||
} | ||||
|
r48856 | } else if mtime == MTIME_UNSET { | ||
Self { | ||||
r48950 | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | |||
mode_size: Some((mode, size)), | ||||
mtime: None, | ||||
|
r48856 | } | ||
} else { | ||||
|
r48865 | Self::new_normal(mode, size, mtime) | ||
|
r48856 | } | ||
} | ||||
r48968 | EntryState::Added => Self { | |||
flags: Flags::WDIR_TRACKED, | ||||
mode_size: None, | ||||
mtime: None, | ||||
}, | ||||
|
r48856 | EntryState::Removed => Self { | ||
flags: if size == SIZE_NON_NORMAL { | ||||
r48950 | Flags::P1_TRACKED | Flags::P2_INFO | |||
|
r48856 | } else if size == SIZE_FROM_OTHER_PARENT { | ||
// We don’t know if P1_TRACKED should be set (file history) | ||||
r48950 | Flags::P2_INFO | |||
|
r48856 | } else { | ||
Flags::P1_TRACKED | ||||
}, | ||||
r48950 | mode_size: None, | |||
mtime: None, | ||||
|
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, | ||||
}, | ||||
|
r48856 | } | ||
} | ||||
|
r48865 | pub fn new_possibly_dirty() -> Self { | ||
|
r48856 | Self { | ||
r48950 | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | |||
mode_size: None, | ||||
mtime: None, | ||||
|
r48856 | } | ||
} | ||||
|
r48865 | pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self { | ||
Self { | ||||
flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
r48950 | mode_size: Some((mode, size)), | |||
mtime: Some(mtime), | ||||
|
r48865 | } | ||
} | ||||
|
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 { | ||||
|
r48856 | Self::from_v1_data(EntryState::Removed, 0, size, 0) | ||
|
r48834 | } | ||
|
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) | ||||
|
r48856 | } | ||
|
r48857 | pub fn removed(&self) -> bool { | ||
r48950 | self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED) | |||
|
r48856 | } | ||
r48954 | pub fn p2_info(&self) -> bool { | |||
self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO) | ||||
} | ||||
|
r48857 | pub fn added(&self) -> bool { | ||
r48950 | self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent() | |||
|
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 | ) | |||
} | ||||
|
r48951 | /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)` | ||
pub(crate) fn v2_data( | ||||
&self, | ||||
) -> (bool, bool, bool, Option<(i32, i32)>, Option<i32>) { | ||||
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; | ||||
(wdir_tracked, p1_tracked, p2_info, mode_size, mtime) | ||||
} | ||||
r48950 | fn v1_state(&self) -> EntryState { | |||
if !self.any_tracked() { | ||||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_state of an untracked DirstateEntry") | ||||
} | ||||
|
r48856 | if self.removed() { | ||
EntryState::Removed | ||||
r48964 | } else if self | |||
.flags | ||||
.contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO) | ||||
{ | ||||
|
r48856 | EntryState::Merged | ||
} else if self.added() { | ||||
EntryState::Added | ||||
} else { | ||||
EntryState::Normal | ||||
} | ||||
|
r48834 | } | ||
r48950 | fn v1_mode(&self) -> i32 { | |||
if let Some((mode, _size)) = self.mode_size { | ||||
mode | ||||
} else { | ||||
0 | ||||
} | ||||
|
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) | ||||
{ | ||||
|
r48856 | SIZE_NON_NORMAL | ||
r48961 | } else if self.flags.contains(Flags::P2_INFO) { | |||
|
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 { | |||
size | ||||
|
r48856 | } else { | ||
r48950 | SIZE_NON_NORMAL | |||
|
r48856 | } | ||
|
r48834 | } | ||
r48950 | fn v1_mtime(&self) -> i32 { | |||
if !self.any_tracked() { | ||||
// TODO: return an Option instead? | ||||
panic!("Accessing v1_mtime of an untracked DirstateEntry") | ||||
} | ||||
|
r48856 | if self.removed() { | ||
0 | ||||
r48950 | } else if self.flags.contains(Flags::P2_INFO) { | |||
|
r48856 | MTIME_UNSET | ||
r48950 | } else if !self.flags.contains(Flags::P1_TRACKED) { | |||
|
r48856 | MTIME_UNSET | ||
} else { | ||||
r48950 | self.mtime.unwrap_or(MTIME_UNSET) | |||
|
r48856 | } | ||
|
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() | ||||
} | ||||
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 | } | |||
} | ||||
|
r48857 | pub fn set_possibly_dirty(&mut self) { | ||
r48950 | self.mtime = None | |||
|
r48857 | } | ||
pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) { | ||||
self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); | ||||
r48950 | self.mode_size = Some((mode, size)); | |||
self.mtime = Some(mtime); | ||||
|
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; | ||||
|
r48857 | } | ||
pub fn set_untracked(&mut self) { | ||||
self.flags.remove(Flags::WDIR_TRACKED); | ||||
r48950 | self.mode_size = None; | |||
self.mtime = None; | ||||
|
r48857 | } | ||
|
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(), | ||||
) | ||||
|
r48834 | } | ||
r48875 | pub(crate) fn is_from_other_parent(&self) -> bool { | |||
|
r48856 | self.state() == EntryState::Normal | ||
&& self.size() == SIZE_FROM_OTHER_PARENT | ||||
|
r48830 | } | ||
// TODO: other platforms | ||||
#[cfg(unix)] | ||||
pub fn mode_changed( | ||||
&self, | ||||
filesystem_metadata: &std::fs::Metadata, | ||||
) -> bool { | ||||
use std::os::unix::fs::MetadataExt; | ||||
const EXEC_BIT_MASK: u32 = 0o100; | ||||
|
r48856 | let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK; | ||
|
r48830 | let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK; | ||
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()) | |||
|
r48830 | } | ||
pub fn mtime_is_ambiguous(&self, now: i32) -> bool { | ||||
|
r48856 | self.state() == EntryState::Normal && self.mtime() == now | ||
|
r48830 | } | ||
pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool { | ||||
let ambiguous = self.mtime_is_ambiguous(now); | ||||
if ambiguous { | ||||
// The file was last modified "simultaneously" with the current | ||||
// write to dirstate (i.e. within the same second for file- | ||||
// systems with a granularity of 1 sec). This commonly happens | ||||
// for at least a couple of files on 'update'. | ||||
// The user could change the file without changing its size | ||||
// within the same second. Invalidate the file's mtime in | ||||
// dirstate, forcing future 'status' calls to compare the | ||||
// contents of the file if the size is the same. This prevents | ||||
// mistakenly treating such files as clean. | ||||
r48950 | self.set_possibly_dirty() | |||
|
r48830 | } | ||
ambiguous | ||||
} | ||||
} | ||||
impl EntryState { | ||||
pub fn is_tracked(self) -> bool { | ||||
use EntryState::*; | ||||
match self { | ||||
Normal | Added | Merged => true, | ||||
|
r48838 | Removed => false, | ||
|
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', | ||||
} | ||||
} | ||||
} | ||||