gix_index/entry/stat.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
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 the result of `symlink_metadata`.
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 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: 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,
}
}
}