]>
Commit | Line | Data |
---|---|---|
49aad941 FG |
1 | use std::{ |
2 | cmp::Ordering, | |
3 | time::{SystemTime, SystemTimeError}, | |
4 | }; | |
5 | ||
6 | use filetime::FileTime; | |
7 | ||
8 | use crate::entry::Stat; | |
9 | ||
10 | impl Stat { | |
11 | /// Detect whether this stat entry is racy if stored in a file index with `timestamp`. | |
12 | /// | |
13 | /// An index entry is considered racy if it's `mtime` is larger or equal to the index `timestamp`. | |
14 | /// The index `timestamp` marks the point in time before which we definitely resolved the racy git problem | |
15 | /// for all index entries so any index entries that changed afterwards will need to be examined for | |
16 | /// changes by actually reading the file from disk at least once. | |
17 | pub fn is_racy( | |
18 | &self, | |
19 | timestamp: FileTime, | |
20 | Options { | |
21 | check_stat, use_nsec, .. | |
22 | }: Options, | |
23 | ) -> bool { | |
24 | match timestamp.unix_seconds().cmp(&(self.mtime.secs as i64)) { | |
25 | Ordering::Less => true, | |
26 | Ordering::Equal if use_nsec && check_stat => timestamp.nanoseconds() <= self.mtime.nsecs, | |
27 | Ordering::Equal => true, | |
28 | Ordering::Greater => false, | |
29 | } | |
30 | } | |
31 | ||
32 | /// Compares the stat information of two index entries. | |
33 | /// | |
34 | /// Intuitively this is basically equivalent to `self == other`. | |
35 | /// However there a lot of nobs in git that tweak whether certain stat information is used when checking | |
36 | /// equality, see [`Options`]. | |
37 | /// This function respects those options while performing the stat comparison and may therefore ignore some fields. | |
38 | pub fn matches( | |
39 | &self, | |
40 | other: &Self, | |
41 | Options { | |
42 | trust_ctime, | |
43 | check_stat, | |
44 | use_nsec, | |
45 | use_stdev, | |
46 | }: Options, | |
47 | ) -> bool { | |
48 | if self.mtime.secs != other.mtime.secs { | |
49 | return false; | |
50 | } | |
51 | if check_stat && use_nsec && self.mtime.nsecs != other.mtime.nsecs { | |
52 | return false; | |
53 | } | |
54 | ||
55 | if self.size != other.size { | |
56 | return false; | |
57 | } | |
58 | ||
59 | if trust_ctime { | |
60 | if self.ctime.secs != other.ctime.secs { | |
61 | return false; | |
62 | } | |
63 | if check_stat && use_nsec && self.ctime.nsecs != other.ctime.nsecs { | |
64 | return false; | |
65 | } | |
66 | } | |
67 | ||
68 | if check_stat { | |
69 | if use_stdev && self.dev != other.dev { | |
70 | return false; | |
71 | } | |
72 | self.ino == other.ino && self.gid == other.gid && self.uid == other.uid | |
73 | } else { | |
74 | true | |
75 | } | |
76 | } | |
77 | ||
fe692bf9 | 78 | /// Creates stat information from the result of `symlink_metadata`. |
49aad941 FG |
79 | pub fn from_fs(fstat: &std::fs::Metadata) -> Result<Stat, SystemTimeError> { |
80 | let mtime = fstat.modified().unwrap_or(std::time::UNIX_EPOCH); | |
81 | let ctime = fstat.created().unwrap_or(std::time::UNIX_EPOCH); | |
82 | ||
83 | #[cfg(not(unix))] | |
84 | let res = Stat { | |
85 | mtime: mtime.try_into()?, | |
86 | ctime: ctime.try_into()?, | |
87 | dev: 0, | |
88 | ino: 0, | |
89 | uid: 0, | |
90 | gid: 0, | |
91 | // truncation to 32 bits is on purpose (git does the same). | |
92 | size: fstat.len() as u32, | |
93 | }; | |
94 | #[cfg(unix)] | |
ed00b5ec FG |
95 | let res = { |
96 | use std::os::unix::fs::MetadataExt; | |
97 | Stat { | |
98 | mtime: mtime.try_into().unwrap_or_default(), | |
99 | ctime: ctime.try_into().unwrap_or_default(), | |
100 | // truncating to 32 bits is fine here because | |
101 | // that's what the linux syscalls returns | |
102 | // just rust upcasts to 64 bits for some reason? | |
103 | // numbers this large are impractical anyway (that's a lot of hard-drives). | |
104 | dev: fstat.dev() as u32, | |
105 | ino: fstat.ino() as u32, | |
106 | uid: fstat.uid(), | |
107 | gid: fstat.gid(), | |
108 | // truncation to 32 bits is on purpose (git does the same). | |
109 | size: fstat.len() as u32, | |
110 | } | |
49aad941 FG |
111 | }; |
112 | ||
113 | Ok(res) | |
114 | } | |
115 | } | |
116 | ||
117 | impl TryFrom<SystemTime> for Time { | |
118 | type Error = SystemTimeError; | |
119 | fn try_from(s: SystemTime) -> Result<Self, SystemTimeError> { | |
120 | let d = s.duration_since(std::time::UNIX_EPOCH)?; | |
121 | Ok(Time { | |
122 | // truncation to 32 bits is on purpose (we only compare the low bits) | |
123 | secs: d.as_secs() as u32, | |
124 | nsecs: d.subsec_nanos(), | |
125 | }) | |
126 | } | |
127 | } | |
128 | ||
129 | impl From<Time> for SystemTime { | |
130 | fn from(s: Time) -> Self { | |
131 | std::time::UNIX_EPOCH + std::time::Duration::new(s.secs.into(), s.nsecs) | |
132 | } | |
133 | } | |
134 | ||
135 | /// The time component in a [`Stat`] struct. | |
136 | #[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] | |
137 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | |
138 | pub struct Time { | |
139 | /// The amount of seconds elapsed since EPOCH. | |
140 | pub secs: u32, | |
141 | /// The amount of nanoseconds elapsed in the current second, ranging from 0 to 999.999.999 . | |
142 | pub nsecs: u32, | |
143 | } | |
144 | ||
145 | impl From<FileTime> for Time { | |
146 | fn from(value: FileTime) -> Self { | |
147 | Time { | |
148 | secs: value.unix_seconds().try_into().expect("can't represent non-unix times"), | |
149 | nsecs: value.nanoseconds(), | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | impl PartialEq<FileTime> for Time { | |
155 | fn eq(&self, other: &FileTime) -> bool { | |
156 | *self == Time::from(*other) | |
157 | } | |
158 | } | |
159 | ||
160 | impl PartialOrd<FileTime> for Time { | |
161 | fn partial_cmp(&self, other: &FileTime) -> Option<Ordering> { | |
162 | self.partial_cmp(&Time::from(*other)) | |
163 | } | |
164 | } | |
165 | ||
166 | /// Configuration for comparing stat entries | |
167 | #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] | |
168 | pub struct Options { | |
169 | /// If true, a files creation time is taken into consideration when checking if a file changed. | |
170 | /// Can be set to false in case other tools alter the creation time in ways that interfere with our operation. | |
171 | /// | |
172 | /// Default `true`. | |
173 | pub trust_ctime: bool, | |
174 | /// If true, all stat fields will be used when checking for up-to-date'ness of the entry. Otherwise | |
175 | /// nano-second parts of mtime and ctime,uid, gid, inode and device number _will not_ be used, leaving only | |
176 | /// the whole-second part of ctime and mtime and the file size to be checked. | |
177 | /// | |
178 | /// Default `true`. | |
179 | pub check_stat: bool, | |
180 | /// Whether to compare nano secs when comparing timestamps. This currently | |
181 | /// leads to many false positives on linux and is therefore disabled there. | |
182 | /// | |
183 | /// Default `false` | |
184 | pub use_nsec: bool, | |
185 | /// Whether to compare network devices secs when comparing timestamps. | |
186 | /// Disabled by default because this can cause many false positives on network | |
187 | /// devices where the device number is not stable | |
188 | /// | |
189 | /// Default `false`. | |
190 | pub use_stdev: bool, | |
191 | } | |
192 | ||
193 | impl Default for Options { | |
194 | fn default() -> Self { | |
195 | Self { | |
196 | trust_ctime: true, | |
197 | check_stat: true, | |
198 | use_nsec: false, | |
199 | use_stdev: false, | |
200 | } | |
201 | } | |
202 | } |