207 lines · 7.0 KB · Rust
RawDownload
use std::{    cmp::Ordering,    time::{SystemTime, SystemTimeError},}; use filetime::FileTime; use crate::entry::Stat; impl Stat {    /// Detect whether this stat entry is racy if stored in a file index with `timestamp`.    ///    /// An index entry is considered racy if it's `mtime` is larger or equal to the index `timestamp`.    /// The index `timestamp` marks the point in time before which we definitely resolved the racy git problem    /// for all index entries so any index entries that changed afterwards will need to be examined for    /// changes by actually reading the file from disk at least once.    pub fn is_racy(        &self,        timestamp: FileTime,        Options {            check_stat, use_nsec, ..        }: Options,    ) -> bool {        match timestamp.unix_seconds().cmp(&i64::from(self.mtime.secs)) {            Ordering::Less => true,            Ordering::Equal if use_nsec && check_stat => timestamp.nanoseconds() <= self.mtime.nsecs,            Ordering::Equal => true,            Ordering::Greater => false,        }    }     /// Compares the stat information of two index entries.    ///    /// Intuitively this is basically equivalent to `self == other`.    /// However there a lot of nobs in git that tweak whether certain stat information is used when checking    /// equality, see [`Options`].    /// This function respects those options while performing the stat comparison and may therefore ignore some fields.    pub fn matches(        &self,        other: &Self,        Options {            trust_ctime,            check_stat,            use_nsec,            use_stdev,        }: Options,    ) -> bool {        if self.mtime.secs != other.mtime.secs {            return false;        }        if check_stat && use_nsec && self.mtime.nsecs != other.mtime.nsecs {            return false;        }         if self.size != other.size {            return false;        }         if trust_ctime {            if self.ctime.secs != other.ctime.secs {                return false;            }            if check_stat && use_nsec && self.ctime.nsecs != other.ctime.nsecs {                return false;            }        }         if check_stat {            if use_stdev && self.dev != other.dev {                return false;            }            self.ino == other.ino && self.gid == other.gid && self.uid == other.uid        } else {            true        }    }     /// Creates stat information from file metadata.    ///    /// The information passed to this function should originate from a function like    /// `symlink_metadata`/`lstat` or `File::metadata`/`fstat`.    ///    /// The data are adjusted for use in the index, using default values of fields that are not    /// meaningful on the target operating system or that are unavailable, and truncating data    /// where doing so does not lose essential information for keeping track of file status.    pub fn from_fs(stat: &crate::fs::Metadata) -> Result<Stat, SystemTimeError> {        let mtime = stat.modified().unwrap_or(std::time::UNIX_EPOCH);        let ctime = stat.created().unwrap_or(std::time::UNIX_EPOCH);         #[cfg(windows)]        let res = Stat {            mtime: mtime.try_into()?,            ctime: ctime.try_into()?,            dev: 0,            ino: 0,            uid: 0,            gid: 0,            // Truncation to 32 bits is on purpose (git does the same).            size: stat.len() as u32,        };        #[cfg(not(windows))]        let res = {            Stat {                mtime: mtime.try_into().unwrap_or_default(),                ctime: ctime.try_into().unwrap_or_default(),                // Truncating the device and inode numbers to 32 bits should be fine even on                // targets where they are represented as 64 bits, since we do not use them                // precisely for tracking changes and we do not map them back to the inode.                dev: stat.dev() as u32,                ino: stat.ino() as u32,                uid: stat.uid(),                gid: stat.gid(),                // Truncation to 32 bits is on purpose (git does the same).                size: stat.len() as u32,            }        };         Ok(res)    }} impl TryFrom<SystemTime> for Time {    type Error = SystemTimeError;    fn try_from(s: SystemTime) -> Result<Self, SystemTimeError> {        let d = s.duration_since(std::time::UNIX_EPOCH)?;        Ok(Time {            // truncation to 32 bits is on purpose (we only compare the low bits)            secs: d.as_secs() as u32,            nsecs: d.subsec_nanos(),        })    }} impl From<Time> for SystemTime {    fn from(s: Time) -> Self {        std::time::UNIX_EPOCH + std::time::Duration::new(s.secs.into(), s.nsecs)    }} /// The time component in a [`Stat`] struct.#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub struct Time {    /// The amount of seconds elapsed since EPOCH.    pub secs: u32,    /// The amount of nanoseconds elapsed in the current second, ranging from 0 to 999.999.999 .    pub nsecs: u32,} impl From<FileTime> for Time {    fn from(value: FileTime) -> Self {        Time {            secs: value.unix_seconds().try_into().expect("can't represent non-unix times"),            nsecs: value.nanoseconds(),        }    }} impl PartialEq<FileTime> for Time {    fn eq(&self, other: &FileTime) -> bool {        *self == Time::from(*other)    }} impl PartialOrd<FileTime> for Time {    fn partial_cmp(&self, other: &FileTime) -> Option<Ordering> {        self.partial_cmp(&Time::from(*other))    }} /// Configuration for comparing stat entries#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]pub struct Options {    /// If true, a files creation time is taken into consideration when checking if a file changed.    /// Can be set to false in case other tools alter the creation time in ways that interfere with our operation.    ///    /// Default `true`.    pub trust_ctime: bool,    /// If true, all stat fields will be used when checking for up-to-date'ness of the entry. Otherwise    /// nano-second parts of mtime and ctime,uid, gid, inode and device number _will not_ be used, leaving only    /// the whole-second part of ctime and mtime and the file size to be checked.    ///    /// Default `true`.    pub check_stat: bool,    /// Whether to compare nano secs when comparing timestamps. This currently    /// leads to many false positives on linux and is therefore disabled there.    ///    /// Default `false`    pub use_nsec: bool,    /// Whether to compare network devices secs when comparing timestamps.    /// Disabled by default because this can cause many false positives on network    /// devices where the device number is not stable    ///    /// Default `false`.    pub use_stdev: bool,} impl Default for Options {    fn default() -> Self {        Self {            trust_ctime: true,            check_stat: true,            use_nsec: false,            use_stdev: false,        }    }}