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(&(self.mtime.secs as i64)) { 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 the result of symlink_metadata. pub fn from_fs(fstat: &std::fs::Metadata) -> Result { let mtime = fstat.modified().unwrap_or(std::time::UNIX_EPOCH); let ctime = fstat.created().unwrap_or(std::time::UNIX_EPOCH); #[cfg(not(unix))] 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: fstat.len() as u32, }; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(unix)] let res = Stat { mtime: mtime.try_into()?, ctime: ctime.try_into()?, // truncating to 32 bits is fine here because // that's what the linux syscalls returns // just rust upcasts to 64 bits for some reason? // numbers this large are impractical anyway (that's a lot of hard-drives). dev: fstat.dev() as u32, ino: fstat.ino() as u32, uid: fstat.uid(), gid: fstat.gid(), // truncation to 32 bits is on purpose (git does the same). size: fstat.len() as u32, }; Ok(res) } } impl TryFrom for Time { type Error = SystemTimeError; fn try_from(s: SystemTime) -> Result { 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