##// END OF EJS Templates
rust-dirstatemap: stop using `.state` in `is_from_other_parent`...
Raphaël Gomès -
r50030:7241b372 default
parent child Browse files
Show More
@@ -1,724 +1,723 b''
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
2 use crate::errors::HgError;
2 use crate::errors::HgError;
3 use bitflags::bitflags;
3 use bitflags::bitflags;
4 use std::convert::{TryFrom, TryInto};
4 use std::convert::{TryFrom, TryInto};
5 use std::fs;
5 use std::fs;
6 use std::io;
6 use std::io;
7 use std::time::{SystemTime, UNIX_EPOCH};
7 use std::time::{SystemTime, UNIX_EPOCH};
8
8
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
10 pub enum EntryState {
10 pub enum EntryState {
11 Normal,
11 Normal,
12 Added,
12 Added,
13 Removed,
13 Removed,
14 Merged,
14 Merged,
15 }
15 }
16
16
17 /// `size` and `mtime.seconds` are truncated to 31 bits.
17 /// `size` and `mtime.seconds` are truncated to 31 bits.
18 ///
18 ///
19 /// TODO: double-check status algorithm correctness for files
19 /// TODO: double-check status algorithm correctness for files
20 /// larger than 2 GiB or modified after 2038.
20 /// larger than 2 GiB or modified after 2038.
21 #[derive(Debug, Copy, Clone)]
21 #[derive(Debug, Copy, Clone)]
22 pub struct DirstateEntry {
22 pub struct DirstateEntry {
23 pub(crate) flags: Flags,
23 pub(crate) flags: Flags,
24 mode_size: Option<(u32, u32)>,
24 mode_size: Option<(u32, u32)>,
25 mtime: Option<TruncatedTimestamp>,
25 mtime: Option<TruncatedTimestamp>,
26 }
26 }
27
27
28 bitflags! {
28 bitflags! {
29 pub(crate) struct Flags: u8 {
29 pub(crate) struct Flags: u8 {
30 const WDIR_TRACKED = 1 << 0;
30 const WDIR_TRACKED = 1 << 0;
31 const P1_TRACKED = 1 << 1;
31 const P1_TRACKED = 1 << 1;
32 const P2_INFO = 1 << 2;
32 const P2_INFO = 1 << 2;
33 const HAS_FALLBACK_EXEC = 1 << 3;
33 const HAS_FALLBACK_EXEC = 1 << 3;
34 const FALLBACK_EXEC = 1 << 4;
34 const FALLBACK_EXEC = 1 << 4;
35 const HAS_FALLBACK_SYMLINK = 1 << 5;
35 const HAS_FALLBACK_SYMLINK = 1 << 5;
36 const FALLBACK_SYMLINK = 1 << 6;
36 const FALLBACK_SYMLINK = 1 << 6;
37 }
37 }
38 }
38 }
39
39
40 /// A Unix timestamp with nanoseconds precision
40 /// A Unix timestamp with nanoseconds precision
41 #[derive(Debug, Copy, Clone)]
41 #[derive(Debug, Copy, Clone)]
42 pub struct TruncatedTimestamp {
42 pub struct TruncatedTimestamp {
43 truncated_seconds: u32,
43 truncated_seconds: u32,
44 /// Always in the `0 .. 1_000_000_000` range.
44 /// Always in the `0 .. 1_000_000_000` range.
45 nanoseconds: u32,
45 nanoseconds: u32,
46 /// TODO this should be in DirstateEntry, but the current code needs
46 /// TODO this should be in DirstateEntry, but the current code needs
47 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
47 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
48 /// comparison.
48 /// comparison.
49 pub second_ambiguous: bool,
49 pub second_ambiguous: bool,
50 }
50 }
51
51
52 impl TruncatedTimestamp {
52 impl TruncatedTimestamp {
53 /// Constructs from a timestamp potentially outside of the supported range,
53 /// Constructs from a timestamp potentially outside of the supported range,
54 /// and truncate the seconds components to its lower 31 bits.
54 /// and truncate the seconds components to its lower 31 bits.
55 ///
55 ///
56 /// Panics if the nanoseconds components is not in the expected range.
56 /// Panics if the nanoseconds components is not in the expected range.
57 pub fn new_truncate(
57 pub fn new_truncate(
58 seconds: i64,
58 seconds: i64,
59 nanoseconds: u32,
59 nanoseconds: u32,
60 second_ambiguous: bool,
60 second_ambiguous: bool,
61 ) -> Self {
61 ) -> Self {
62 assert!(nanoseconds < NSEC_PER_SEC);
62 assert!(nanoseconds < NSEC_PER_SEC);
63 Self {
63 Self {
64 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
64 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
65 nanoseconds,
65 nanoseconds,
66 second_ambiguous,
66 second_ambiguous,
67 }
67 }
68 }
68 }
69
69
70 /// Construct from components. Returns an error if they are not in the
70 /// Construct from components. Returns an error if they are not in the
71 /// expcted range.
71 /// expcted range.
72 pub fn from_already_truncated(
72 pub fn from_already_truncated(
73 truncated_seconds: u32,
73 truncated_seconds: u32,
74 nanoseconds: u32,
74 nanoseconds: u32,
75 second_ambiguous: bool,
75 second_ambiguous: bool,
76 ) -> Result<Self, DirstateV2ParseError> {
76 ) -> Result<Self, DirstateV2ParseError> {
77 if truncated_seconds & !RANGE_MASK_31BIT == 0
77 if truncated_seconds & !RANGE_MASK_31BIT == 0
78 && nanoseconds < NSEC_PER_SEC
78 && nanoseconds < NSEC_PER_SEC
79 {
79 {
80 Ok(Self {
80 Ok(Self {
81 truncated_seconds,
81 truncated_seconds,
82 nanoseconds,
82 nanoseconds,
83 second_ambiguous,
83 second_ambiguous,
84 })
84 })
85 } else {
85 } else {
86 Err(DirstateV2ParseError)
86 Err(DirstateV2ParseError)
87 }
87 }
88 }
88 }
89
89
90 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
90 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
91 ///
91 ///
92 /// Propagates errors from `std` on platforms where modification time
92 /// Propagates errors from `std` on platforms where modification time
93 /// is not available at all.
93 /// is not available at all.
94 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
94 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
95 #[cfg(unix)]
95 #[cfg(unix)]
96 {
96 {
97 use std::os::unix::fs::MetadataExt;
97 use std::os::unix::fs::MetadataExt;
98 let seconds = metadata.mtime();
98 let seconds = metadata.mtime();
99 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
99 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
100 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
100 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
101 Ok(Self::new_truncate(seconds, nanoseconds, false))
101 Ok(Self::new_truncate(seconds, nanoseconds, false))
102 }
102 }
103 #[cfg(not(unix))]
103 #[cfg(not(unix))]
104 {
104 {
105 metadata.modified().map(Self::from)
105 metadata.modified().map(Self::from)
106 }
106 }
107 }
107 }
108
108
109 /// Like `for_mtime_of`, but may return `None` or a value with
109 /// Like `for_mtime_of`, but may return `None` or a value with
110 /// `second_ambiguous` set if the mtime is not "reliable".
110 /// `second_ambiguous` set if the mtime is not "reliable".
111 ///
111 ///
112 /// A modification time is reliable if it is older than `boundary` (or
112 /// A modification time is reliable if it is older than `boundary` (or
113 /// sufficiently in the future).
113 /// sufficiently in the future).
114 ///
114 ///
115 /// Otherwise a concurrent modification might happens with the same mtime.
115 /// Otherwise a concurrent modification might happens with the same mtime.
116 pub fn for_reliable_mtime_of(
116 pub fn for_reliable_mtime_of(
117 metadata: &fs::Metadata,
117 metadata: &fs::Metadata,
118 boundary: &Self,
118 boundary: &Self,
119 ) -> io::Result<Option<Self>> {
119 ) -> io::Result<Option<Self>> {
120 let mut mtime = Self::for_mtime_of(metadata)?;
120 let mut mtime = Self::for_mtime_of(metadata)?;
121 // If the mtime of the ambiguous file is younger (or equal) to the
121 // If the mtime of the ambiguous file is younger (or equal) to the
122 // starting point of the `status` walk, we cannot garantee that
122 // starting point of the `status` walk, we cannot garantee that
123 // another, racy, write will not happen right after with the same mtime
123 // another, racy, write will not happen right after with the same mtime
124 // and we cannot cache the information.
124 // and we cannot cache the information.
125 //
125 //
126 // However if the mtime is far away in the future, this is likely some
126 // However if the mtime is far away in the future, this is likely some
127 // mismatch between the current clock and previous file system
127 // mismatch between the current clock and previous file system
128 // operation. So mtime more than one days in the future are considered
128 // operation. So mtime more than one days in the future are considered
129 // fine.
129 // fine.
130 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
130 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
131 {
131 {
132 mtime.second_ambiguous = true;
132 mtime.second_ambiguous = true;
133 mtime.nanoseconds != 0
133 mtime.nanoseconds != 0
134 && boundary.nanoseconds != 0
134 && boundary.nanoseconds != 0
135 && mtime.nanoseconds < boundary.nanoseconds
135 && mtime.nanoseconds < boundary.nanoseconds
136 } else {
136 } else {
137 // `truncated_seconds` is less than 2**31,
137 // `truncated_seconds` is less than 2**31,
138 // so this does not overflow `u32`:
138 // so this does not overflow `u32`:
139 let one_day_later = boundary.truncated_seconds + 24 * 3600;
139 let one_day_later = boundary.truncated_seconds + 24 * 3600;
140 mtime.truncated_seconds < boundary.truncated_seconds
140 mtime.truncated_seconds < boundary.truncated_seconds
141 || mtime.truncated_seconds > one_day_later
141 || mtime.truncated_seconds > one_day_later
142 };
142 };
143 if reliable {
143 if reliable {
144 Ok(Some(mtime))
144 Ok(Some(mtime))
145 } else {
145 } else {
146 Ok(None)
146 Ok(None)
147 }
147 }
148 }
148 }
149
149
150 /// The lower 31 bits of the number of seconds since the epoch.
150 /// The lower 31 bits of the number of seconds since the epoch.
151 pub fn truncated_seconds(&self) -> u32 {
151 pub fn truncated_seconds(&self) -> u32 {
152 self.truncated_seconds
152 self.truncated_seconds
153 }
153 }
154
154
155 /// The sub-second component of this timestamp, in nanoseconds.
155 /// The sub-second component of this timestamp, in nanoseconds.
156 /// Always in the `0 .. 1_000_000_000` range.
156 /// Always in the `0 .. 1_000_000_000` range.
157 ///
157 ///
158 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
158 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
159 pub fn nanoseconds(&self) -> u32 {
159 pub fn nanoseconds(&self) -> u32 {
160 self.nanoseconds
160 self.nanoseconds
161 }
161 }
162
162
163 /// Returns whether two timestamps are equal modulo 2**31 seconds.
163 /// Returns whether two timestamps are equal modulo 2**31 seconds.
164 ///
164 ///
165 /// If this returns `true`, the original values converted from `SystemTime`
165 /// If this returns `true`, the original values converted from `SystemTime`
166 /// or given to `new_truncate` were very likely equal. A false positive is
166 /// or given to `new_truncate` were very likely equal. A false positive is
167 /// possible if they were exactly a multiple of 2**31 seconds apart (around
167 /// possible if they were exactly a multiple of 2**31 seconds apart (around
168 /// 68 years). This is deemed very unlikely to happen by chance, especially
168 /// 68 years). This is deemed very unlikely to happen by chance, especially
169 /// on filesystems that support sub-second precision.
169 /// on filesystems that support sub-second precision.
170 ///
170 ///
171 /// If someone is manipulating the modification times of some files to
171 /// If someone is manipulating the modification times of some files to
172 /// intentionally make `hg status` return incorrect results, not truncating
172 /// intentionally make `hg status` return incorrect results, not truncating
173 /// wouldn’t help much since they can set exactly the expected timestamp.
173 /// wouldn’t help much since they can set exactly the expected timestamp.
174 ///
174 ///
175 /// Sub-second precision is ignored if it is zero in either value.
175 /// Sub-second precision is ignored if it is zero in either value.
176 /// Some APIs simply return zero when more precision is not available.
176 /// Some APIs simply return zero when more precision is not available.
177 /// When comparing values from different sources, if only one is truncated
177 /// When comparing values from different sources, if only one is truncated
178 /// in that way, doing a simple comparison would cause many false
178 /// in that way, doing a simple comparison would cause many false
179 /// negatives.
179 /// negatives.
180 pub fn likely_equal(self, other: Self) -> bool {
180 pub fn likely_equal(self, other: Self) -> bool {
181 if self.truncated_seconds != other.truncated_seconds {
181 if self.truncated_seconds != other.truncated_seconds {
182 false
182 false
183 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
183 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
184 if self.second_ambiguous {
184 if self.second_ambiguous {
185 false
185 false
186 } else {
186 } else {
187 true
187 true
188 }
188 }
189 } else {
189 } else {
190 self.nanoseconds == other.nanoseconds
190 self.nanoseconds == other.nanoseconds
191 }
191 }
192 }
192 }
193
193
194 pub fn likely_equal_to_mtime_of(
194 pub fn likely_equal_to_mtime_of(
195 self,
195 self,
196 metadata: &fs::Metadata,
196 metadata: &fs::Metadata,
197 ) -> io::Result<bool> {
197 ) -> io::Result<bool> {
198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
199 }
199 }
200 }
200 }
201
201
202 impl From<SystemTime> for TruncatedTimestamp {
202 impl From<SystemTime> for TruncatedTimestamp {
203 fn from(system_time: SystemTime) -> Self {
203 fn from(system_time: SystemTime) -> Self {
204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
205 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
205 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
206 // We want to effectively access its fields, but the Rust standard
206 // We want to effectively access its fields, but the Rust standard
207 // library does not expose them. The best we can do is:
207 // library does not expose them. The best we can do is:
208 let seconds;
208 let seconds;
209 let nanoseconds;
209 let nanoseconds;
210 match system_time.duration_since(UNIX_EPOCH) {
210 match system_time.duration_since(UNIX_EPOCH) {
211 Ok(duration) => {
211 Ok(duration) => {
212 seconds = duration.as_secs() as i64;
212 seconds = duration.as_secs() as i64;
213 nanoseconds = duration.subsec_nanos();
213 nanoseconds = duration.subsec_nanos();
214 }
214 }
215 Err(error) => {
215 Err(error) => {
216 // `system_time` is before `UNIX_EPOCH`.
216 // `system_time` is before `UNIX_EPOCH`.
217 // We need to undo this algorithm:
217 // We need to undo this algorithm:
218 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
218 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
219 let negative = error.duration();
219 let negative = error.duration();
220 let negative_secs = negative.as_secs() as i64;
220 let negative_secs = negative.as_secs() as i64;
221 let negative_nanos = negative.subsec_nanos();
221 let negative_nanos = negative.subsec_nanos();
222 if negative_nanos == 0 {
222 if negative_nanos == 0 {
223 seconds = -negative_secs;
223 seconds = -negative_secs;
224 nanoseconds = 0;
224 nanoseconds = 0;
225 } else {
225 } else {
226 // For example if `system_time` was 4.3 seconds before
226 // For example if `system_time` was 4.3 seconds before
227 // the Unix epoch we get a Duration that represents
227 // the Unix epoch we get a Duration that represents
228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
229 seconds = -1 - negative_secs;
229 seconds = -1 - negative_secs;
230 nanoseconds = NSEC_PER_SEC - negative_nanos;
230 nanoseconds = NSEC_PER_SEC - negative_nanos;
231 }
231 }
232 }
232 }
233 };
233 };
234 Self::new_truncate(seconds, nanoseconds, false)
234 Self::new_truncate(seconds, nanoseconds, false)
235 }
235 }
236 }
236 }
237
237
238 const NSEC_PER_SEC: u32 = 1_000_000_000;
238 const NSEC_PER_SEC: u32 = 1_000_000_000;
239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
240
240
241 pub const MTIME_UNSET: i32 = -1;
241 pub const MTIME_UNSET: i32 = -1;
242
242
243 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
243 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
244 /// other parent. This allows revert to pick the right status back during a
244 /// other parent. This allows revert to pick the right status back during a
245 /// merge.
245 /// merge.
246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
247 /// A special value used for internal representation of special case in
247 /// A special value used for internal representation of special case in
248 /// dirstate v1 format.
248 /// dirstate v1 format.
249 pub const SIZE_NON_NORMAL: i32 = -1;
249 pub const SIZE_NON_NORMAL: i32 = -1;
250
250
251 #[derive(Debug, Default, Copy, Clone)]
251 #[derive(Debug, Default, Copy, Clone)]
252 pub struct DirstateV2Data {
252 pub struct DirstateV2Data {
253 pub wc_tracked: bool,
253 pub wc_tracked: bool,
254 pub p1_tracked: bool,
254 pub p1_tracked: bool,
255 pub p2_info: bool,
255 pub p2_info: bool,
256 pub mode_size: Option<(u32, u32)>,
256 pub mode_size: Option<(u32, u32)>,
257 pub mtime: Option<TruncatedTimestamp>,
257 pub mtime: Option<TruncatedTimestamp>,
258 pub fallback_exec: Option<bool>,
258 pub fallback_exec: Option<bool>,
259 pub fallback_symlink: Option<bool>,
259 pub fallback_symlink: Option<bool>,
260 }
260 }
261
261
262 #[derive(Debug, Default, Copy, Clone)]
262 #[derive(Debug, Default, Copy, Clone)]
263 pub struct ParentFileData {
263 pub struct ParentFileData {
264 pub mode_size: Option<(u32, u32)>,
264 pub mode_size: Option<(u32, u32)>,
265 pub mtime: Option<TruncatedTimestamp>,
265 pub mtime: Option<TruncatedTimestamp>,
266 }
266 }
267
267
268 impl DirstateEntry {
268 impl DirstateEntry {
269 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
269 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
270 let DirstateV2Data {
270 let DirstateV2Data {
271 wc_tracked,
271 wc_tracked,
272 p1_tracked,
272 p1_tracked,
273 p2_info,
273 p2_info,
274 mode_size,
274 mode_size,
275 mtime,
275 mtime,
276 fallback_exec,
276 fallback_exec,
277 fallback_symlink,
277 fallback_symlink,
278 } = v2_data;
278 } = v2_data;
279 if let Some((mode, size)) = mode_size {
279 if let Some((mode, size)) = mode_size {
280 // TODO: return an error for out of range values?
280 // TODO: return an error for out of range values?
281 assert!(mode & !RANGE_MASK_31BIT == 0);
281 assert!(mode & !RANGE_MASK_31BIT == 0);
282 assert!(size & !RANGE_MASK_31BIT == 0);
282 assert!(size & !RANGE_MASK_31BIT == 0);
283 }
283 }
284 let mut flags = Flags::empty();
284 let mut flags = Flags::empty();
285 flags.set(Flags::WDIR_TRACKED, wc_tracked);
285 flags.set(Flags::WDIR_TRACKED, wc_tracked);
286 flags.set(Flags::P1_TRACKED, p1_tracked);
286 flags.set(Flags::P1_TRACKED, p1_tracked);
287 flags.set(Flags::P2_INFO, p2_info);
287 flags.set(Flags::P2_INFO, p2_info);
288 if let Some(exec) = fallback_exec {
288 if let Some(exec) = fallback_exec {
289 flags.insert(Flags::HAS_FALLBACK_EXEC);
289 flags.insert(Flags::HAS_FALLBACK_EXEC);
290 if exec {
290 if exec {
291 flags.insert(Flags::FALLBACK_EXEC);
291 flags.insert(Flags::FALLBACK_EXEC);
292 }
292 }
293 }
293 }
294 if let Some(exec) = fallback_symlink {
294 if let Some(exec) = fallback_symlink {
295 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
295 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
296 if exec {
296 if exec {
297 flags.insert(Flags::FALLBACK_SYMLINK);
297 flags.insert(Flags::FALLBACK_SYMLINK);
298 }
298 }
299 }
299 }
300 Self {
300 Self {
301 flags,
301 flags,
302 mode_size,
302 mode_size,
303 mtime,
303 mtime,
304 }
304 }
305 }
305 }
306
306
307 pub fn from_v1_data(
307 pub fn from_v1_data(
308 state: EntryState,
308 state: EntryState,
309 mode: i32,
309 mode: i32,
310 size: i32,
310 size: i32,
311 mtime: i32,
311 mtime: i32,
312 ) -> Self {
312 ) -> Self {
313 match state {
313 match state {
314 EntryState::Normal => {
314 EntryState::Normal => {
315 if size == SIZE_FROM_OTHER_PARENT {
315 if size == SIZE_FROM_OTHER_PARENT {
316 Self {
316 Self {
317 // might be missing P1_TRACKED
317 // might be missing P1_TRACKED
318 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
318 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
319 mode_size: None,
319 mode_size: None,
320 mtime: None,
320 mtime: None,
321 }
321 }
322 } else if size == SIZE_NON_NORMAL {
322 } else if size == SIZE_NON_NORMAL {
323 Self {
323 Self {
324 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
324 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
325 mode_size: None,
325 mode_size: None,
326 mtime: None,
326 mtime: None,
327 }
327 }
328 } else if mtime == MTIME_UNSET {
328 } else if mtime == MTIME_UNSET {
329 // TODO: return an error for negative values?
329 // TODO: return an error for negative values?
330 let mode = u32::try_from(mode).unwrap();
330 let mode = u32::try_from(mode).unwrap();
331 let size = u32::try_from(size).unwrap();
331 let size = u32::try_from(size).unwrap();
332 Self {
332 Self {
333 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
333 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
334 mode_size: Some((mode, size)),
334 mode_size: Some((mode, size)),
335 mtime: None,
335 mtime: None,
336 }
336 }
337 } else {
337 } else {
338 // TODO: return an error for negative values?
338 // TODO: return an error for negative values?
339 let mode = u32::try_from(mode).unwrap();
339 let mode = u32::try_from(mode).unwrap();
340 let size = u32::try_from(size).unwrap();
340 let size = u32::try_from(size).unwrap();
341 let mtime = u32::try_from(mtime).unwrap();
341 let mtime = u32::try_from(mtime).unwrap();
342 let mtime = TruncatedTimestamp::from_already_truncated(
342 let mtime = TruncatedTimestamp::from_already_truncated(
343 mtime, 0, false,
343 mtime, 0, false,
344 )
344 )
345 .unwrap();
345 .unwrap();
346 Self {
346 Self {
347 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
347 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
348 mode_size: Some((mode, size)),
348 mode_size: Some((mode, size)),
349 mtime: Some(mtime),
349 mtime: Some(mtime),
350 }
350 }
351 }
351 }
352 }
352 }
353 EntryState::Added => Self {
353 EntryState::Added => Self {
354 flags: Flags::WDIR_TRACKED,
354 flags: Flags::WDIR_TRACKED,
355 mode_size: None,
355 mode_size: None,
356 mtime: None,
356 mtime: None,
357 },
357 },
358 EntryState::Removed => Self {
358 EntryState::Removed => Self {
359 flags: if size == SIZE_NON_NORMAL {
359 flags: if size == SIZE_NON_NORMAL {
360 Flags::P1_TRACKED | Flags::P2_INFO
360 Flags::P1_TRACKED | Flags::P2_INFO
361 } else if size == SIZE_FROM_OTHER_PARENT {
361 } else if size == SIZE_FROM_OTHER_PARENT {
362 // We don’t know if P1_TRACKED should be set (file history)
362 // We don’t know if P1_TRACKED should be set (file history)
363 Flags::P2_INFO
363 Flags::P2_INFO
364 } else {
364 } else {
365 Flags::P1_TRACKED
365 Flags::P1_TRACKED
366 },
366 },
367 mode_size: None,
367 mode_size: None,
368 mtime: None,
368 mtime: None,
369 },
369 },
370 EntryState::Merged => Self {
370 EntryState::Merged => Self {
371 flags: Flags::WDIR_TRACKED
371 flags: Flags::WDIR_TRACKED
372 | Flags::P1_TRACKED // might not be true because of rename ?
372 | Flags::P1_TRACKED // might not be true because of rename ?
373 | Flags::P2_INFO, // might not be true because of rename ?
373 | Flags::P2_INFO, // might not be true because of rename ?
374 mode_size: None,
374 mode_size: None,
375 mtime: None,
375 mtime: None,
376 },
376 },
377 }
377 }
378 }
378 }
379
379
380 /// Creates a new entry in "removed" state.
380 /// Creates a new entry in "removed" state.
381 ///
381 ///
382 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
382 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
383 /// `SIZE_FROM_OTHER_PARENT`
383 /// `SIZE_FROM_OTHER_PARENT`
384 pub fn new_removed(size: i32) -> Self {
384 pub fn new_removed(size: i32) -> Self {
385 Self::from_v1_data(EntryState::Removed, 0, size, 0)
385 Self::from_v1_data(EntryState::Removed, 0, size, 0)
386 }
386 }
387
387
388 pub fn new_tracked() -> Self {
388 pub fn new_tracked() -> Self {
389 let data = DirstateV2Data {
389 let data = DirstateV2Data {
390 wc_tracked: true,
390 wc_tracked: true,
391 ..Default::default()
391 ..Default::default()
392 };
392 };
393 Self::from_v2_data(data)
393 Self::from_v2_data(data)
394 }
394 }
395
395
396 pub fn tracked(&self) -> bool {
396 pub fn tracked(&self) -> bool {
397 self.flags.contains(Flags::WDIR_TRACKED)
397 self.flags.contains(Flags::WDIR_TRACKED)
398 }
398 }
399
399
400 pub fn p1_tracked(&self) -> bool {
400 pub fn p1_tracked(&self) -> bool {
401 self.flags.contains(Flags::P1_TRACKED)
401 self.flags.contains(Flags::P1_TRACKED)
402 }
402 }
403
403
404 fn in_either_parent(&self) -> bool {
404 fn in_either_parent(&self) -> bool {
405 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
405 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
406 }
406 }
407
407
408 pub fn removed(&self) -> bool {
408 pub fn removed(&self) -> bool {
409 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
409 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
410 }
410 }
411
411
412 pub fn p2_info(&self) -> bool {
412 pub fn p2_info(&self) -> bool {
413 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
413 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
414 }
414 }
415
415
416 pub fn added(&self) -> bool {
416 pub fn added(&self) -> bool {
417 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
417 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
418 }
418 }
419
419
420 pub fn modified(&self) -> bool {
420 pub fn modified(&self) -> bool {
421 self.flags
421 self.flags
422 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
422 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
423 }
423 }
424
424
425 pub fn maybe_clean(&self) -> bool {
425 pub fn maybe_clean(&self) -> bool {
426 if !self.flags.contains(Flags::WDIR_TRACKED) {
426 if !self.flags.contains(Flags::WDIR_TRACKED) {
427 false
427 false
428 } else if !self.flags.contains(Flags::P1_TRACKED) {
428 } else if !self.flags.contains(Flags::P1_TRACKED) {
429 false
429 false
430 } else if self.flags.contains(Flags::P2_INFO) {
430 } else if self.flags.contains(Flags::P2_INFO) {
431 false
431 false
432 } else {
432 } else {
433 true
433 true
434 }
434 }
435 }
435 }
436
436
437 pub fn any_tracked(&self) -> bool {
437 pub fn any_tracked(&self) -> bool {
438 self.flags.intersects(
438 self.flags.intersects(
439 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
439 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
440 )
440 )
441 }
441 }
442
442
443 pub(crate) fn v2_data(&self) -> DirstateV2Data {
443 pub(crate) fn v2_data(&self) -> DirstateV2Data {
444 if !self.any_tracked() {
444 if !self.any_tracked() {
445 // TODO: return an Option instead?
445 // TODO: return an Option instead?
446 panic!("Accessing v2_data of an untracked DirstateEntry")
446 panic!("Accessing v2_data of an untracked DirstateEntry")
447 }
447 }
448 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
448 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
449 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
449 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
450 let p2_info = self.flags.contains(Flags::P2_INFO);
450 let p2_info = self.flags.contains(Flags::P2_INFO);
451 let mode_size = self.mode_size;
451 let mode_size = self.mode_size;
452 let mtime = self.mtime;
452 let mtime = self.mtime;
453 DirstateV2Data {
453 DirstateV2Data {
454 wc_tracked,
454 wc_tracked,
455 p1_tracked,
455 p1_tracked,
456 p2_info,
456 p2_info,
457 mode_size,
457 mode_size,
458 mtime,
458 mtime,
459 fallback_exec: self.get_fallback_exec(),
459 fallback_exec: self.get_fallback_exec(),
460 fallback_symlink: self.get_fallback_symlink(),
460 fallback_symlink: self.get_fallback_symlink(),
461 }
461 }
462 }
462 }
463
463
464 fn v1_state(&self) -> EntryState {
464 fn v1_state(&self) -> EntryState {
465 if !self.any_tracked() {
465 if !self.any_tracked() {
466 // TODO: return an Option instead?
466 // TODO: return an Option instead?
467 panic!("Accessing v1_state of an untracked DirstateEntry")
467 panic!("Accessing v1_state of an untracked DirstateEntry")
468 }
468 }
469 if self.removed() {
469 if self.removed() {
470 EntryState::Removed
470 EntryState::Removed
471 } else if self.modified() {
471 } else if self.modified() {
472 EntryState::Merged
472 EntryState::Merged
473 } else if self.added() {
473 } else if self.added() {
474 EntryState::Added
474 EntryState::Added
475 } else {
475 } else {
476 EntryState::Normal
476 EntryState::Normal
477 }
477 }
478 }
478 }
479
479
480 fn v1_mode(&self) -> i32 {
480 fn v1_mode(&self) -> i32 {
481 if let Some((mode, _size)) = self.mode_size {
481 if let Some((mode, _size)) = self.mode_size {
482 i32::try_from(mode).unwrap()
482 i32::try_from(mode).unwrap()
483 } else {
483 } else {
484 0
484 0
485 }
485 }
486 }
486 }
487
487
488 fn v1_size(&self) -> i32 {
488 fn v1_size(&self) -> i32 {
489 if !self.any_tracked() {
489 if !self.any_tracked() {
490 // TODO: return an Option instead?
490 // TODO: return an Option instead?
491 panic!("Accessing v1_size of an untracked DirstateEntry")
491 panic!("Accessing v1_size of an untracked DirstateEntry")
492 }
492 }
493 if self.removed()
493 if self.removed()
494 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
494 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
495 {
495 {
496 SIZE_NON_NORMAL
496 SIZE_NON_NORMAL
497 } else if self.flags.contains(Flags::P2_INFO) {
497 } else if self.flags.contains(Flags::P2_INFO) {
498 SIZE_FROM_OTHER_PARENT
498 SIZE_FROM_OTHER_PARENT
499 } else if self.removed() {
499 } else if self.removed() {
500 0
500 0
501 } else if self.added() {
501 } else if self.added() {
502 SIZE_NON_NORMAL
502 SIZE_NON_NORMAL
503 } else if let Some((_mode, size)) = self.mode_size {
503 } else if let Some((_mode, size)) = self.mode_size {
504 i32::try_from(size).unwrap()
504 i32::try_from(size).unwrap()
505 } else {
505 } else {
506 SIZE_NON_NORMAL
506 SIZE_NON_NORMAL
507 }
507 }
508 }
508 }
509
509
510 fn v1_mtime(&self) -> i32 {
510 fn v1_mtime(&self) -> i32 {
511 if !self.any_tracked() {
511 if !self.any_tracked() {
512 // TODO: return an Option instead?
512 // TODO: return an Option instead?
513 panic!("Accessing v1_mtime of an untracked DirstateEntry")
513 panic!("Accessing v1_mtime of an untracked DirstateEntry")
514 }
514 }
515 if self.removed() {
515 if self.removed() {
516 0
516 0
517 } else if self.flags.contains(Flags::P2_INFO) {
517 } else if self.flags.contains(Flags::P2_INFO) {
518 MTIME_UNSET
518 MTIME_UNSET
519 } else if !self.flags.contains(Flags::P1_TRACKED) {
519 } else if !self.flags.contains(Flags::P1_TRACKED) {
520 MTIME_UNSET
520 MTIME_UNSET
521 } else if let Some(mtime) = self.mtime {
521 } else if let Some(mtime) = self.mtime {
522 if mtime.second_ambiguous {
522 if mtime.second_ambiguous {
523 MTIME_UNSET
523 MTIME_UNSET
524 } else {
524 } else {
525 i32::try_from(mtime.truncated_seconds()).unwrap()
525 i32::try_from(mtime.truncated_seconds()).unwrap()
526 }
526 }
527 } else {
527 } else {
528 MTIME_UNSET
528 MTIME_UNSET
529 }
529 }
530 }
530 }
531
531
532 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
532 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
533 pub fn state(&self) -> EntryState {
533 pub fn state(&self) -> EntryState {
534 self.v1_state()
534 self.v1_state()
535 }
535 }
536
536
537 // TODO: return Option?
537 // TODO: return Option?
538 pub fn mode(&self) -> i32 {
538 pub fn mode(&self) -> i32 {
539 self.v1_mode()
539 self.v1_mode()
540 }
540 }
541
541
542 // TODO: return Option?
542 // TODO: return Option?
543 pub fn size(&self) -> i32 {
543 pub fn size(&self) -> i32 {
544 self.v1_size()
544 self.v1_size()
545 }
545 }
546
546
547 // TODO: return Option?
547 // TODO: return Option?
548 pub fn mtime(&self) -> i32 {
548 pub fn mtime(&self) -> i32 {
549 self.v1_mtime()
549 self.v1_mtime()
550 }
550 }
551
551
552 pub fn get_fallback_exec(&self) -> Option<bool> {
552 pub fn get_fallback_exec(&self) -> Option<bool> {
553 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
553 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
554 Some(self.flags.contains(Flags::FALLBACK_EXEC))
554 Some(self.flags.contains(Flags::FALLBACK_EXEC))
555 } else {
555 } else {
556 None
556 None
557 }
557 }
558 }
558 }
559
559
560 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
560 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
561 match value {
561 match value {
562 None => {
562 None => {
563 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
563 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
564 self.flags.remove(Flags::FALLBACK_EXEC);
564 self.flags.remove(Flags::FALLBACK_EXEC);
565 }
565 }
566 Some(exec) => {
566 Some(exec) => {
567 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
567 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
568 if exec {
568 if exec {
569 self.flags.insert(Flags::FALLBACK_EXEC);
569 self.flags.insert(Flags::FALLBACK_EXEC);
570 }
570 }
571 }
571 }
572 }
572 }
573 }
573 }
574
574
575 pub fn get_fallback_symlink(&self) -> Option<bool> {
575 pub fn get_fallback_symlink(&self) -> Option<bool> {
576 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
576 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
577 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
577 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
578 } else {
578 } else {
579 None
579 None
580 }
580 }
581 }
581 }
582
582
583 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
583 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
584 match value {
584 match value {
585 None => {
585 None => {
586 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
586 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
587 self.flags.remove(Flags::FALLBACK_SYMLINK);
587 self.flags.remove(Flags::FALLBACK_SYMLINK);
588 }
588 }
589 Some(symlink) => {
589 Some(symlink) => {
590 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
590 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
591 if symlink {
591 if symlink {
592 self.flags.insert(Flags::FALLBACK_SYMLINK);
592 self.flags.insert(Flags::FALLBACK_SYMLINK);
593 }
593 }
594 }
594 }
595 }
595 }
596 }
596 }
597
597
598 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
598 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
599 self.mtime
599 self.mtime
600 }
600 }
601
601
602 pub fn drop_merge_data(&mut self) {
602 pub fn drop_merge_data(&mut self) {
603 if self.flags.contains(Flags::P2_INFO) {
603 if self.flags.contains(Flags::P2_INFO) {
604 self.flags.remove(Flags::P2_INFO);
604 self.flags.remove(Flags::P2_INFO);
605 self.mode_size = None;
605 self.mode_size = None;
606 self.mtime = None;
606 self.mtime = None;
607 }
607 }
608 }
608 }
609
609
610 pub fn set_possibly_dirty(&mut self) {
610 pub fn set_possibly_dirty(&mut self) {
611 self.mtime = None
611 self.mtime = None
612 }
612 }
613
613
614 pub fn set_clean(
614 pub fn set_clean(
615 &mut self,
615 &mut self,
616 mode: u32,
616 mode: u32,
617 size: u32,
617 size: u32,
618 mtime: TruncatedTimestamp,
618 mtime: TruncatedTimestamp,
619 ) {
619 ) {
620 let size = size & RANGE_MASK_31BIT;
620 let size = size & RANGE_MASK_31BIT;
621 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
621 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
622 self.mode_size = Some((mode, size));
622 self.mode_size = Some((mode, size));
623 self.mtime = Some(mtime);
623 self.mtime = Some(mtime);
624 }
624 }
625
625
626 pub fn set_tracked(&mut self) {
626 pub fn set_tracked(&mut self) {
627 self.flags.insert(Flags::WDIR_TRACKED);
627 self.flags.insert(Flags::WDIR_TRACKED);
628 // `set_tracked` is replacing various `normallookup` call. So we mark
628 // `set_tracked` is replacing various `normallookup` call. So we mark
629 // the files as needing lookup
629 // the files as needing lookup
630 //
630 //
631 // Consider dropping this in the future in favor of something less
631 // Consider dropping this in the future in favor of something less
632 // broad.
632 // broad.
633 self.mtime = None;
633 self.mtime = None;
634 }
634 }
635
635
636 pub fn set_untracked(&mut self) {
636 pub fn set_untracked(&mut self) {
637 self.flags.remove(Flags::WDIR_TRACKED);
637 self.flags.remove(Flags::WDIR_TRACKED);
638 self.mode_size = None;
638 self.mode_size = None;
639 self.mtime = None;
639 self.mtime = None;
640 }
640 }
641
641
642 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
642 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
643 /// in the dirstate-v1 format.
643 /// in the dirstate-v1 format.
644 ///
644 ///
645 /// This includes marker values such as `mtime == -1`. In the future we may
645 /// This includes marker values such as `mtime == -1`. In the future we may
646 /// want to not represent these cases that way in memory, but serialization
646 /// want to not represent these cases that way in memory, but serialization
647 /// will need to keep the same format.
647 /// will need to keep the same format.
648 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
648 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
649 (
649 (
650 self.v1_state().into(),
650 self.v1_state().into(),
651 self.v1_mode(),
651 self.v1_mode(),
652 self.v1_size(),
652 self.v1_size(),
653 self.v1_mtime(),
653 self.v1_mtime(),
654 )
654 )
655 }
655 }
656
656
657 pub(crate) fn is_from_other_parent(&self) -> bool {
657 pub(crate) fn is_from_other_parent(&self) -> bool {
658 self.state() == EntryState::Normal
658 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
659 && self.size() == SIZE_FROM_OTHER_PARENT
660 }
659 }
661
660
662 // TODO: other platforms
661 // TODO: other platforms
663 #[cfg(unix)]
662 #[cfg(unix)]
664 pub fn mode_changed(
663 pub fn mode_changed(
665 &self,
664 &self,
666 filesystem_metadata: &std::fs::Metadata,
665 filesystem_metadata: &std::fs::Metadata,
667 ) -> bool {
666 ) -> bool {
668 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
667 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
669 let fs_exec_bit = has_exec_bit(filesystem_metadata);
668 let fs_exec_bit = has_exec_bit(filesystem_metadata);
670 dirstate_exec_bit != fs_exec_bit
669 dirstate_exec_bit != fs_exec_bit
671 }
670 }
672
671
673 /// Returns a `(state, mode, size, mtime)` tuple as for
672 /// Returns a `(state, mode, size, mtime)` tuple as for
674 /// `DirstateMapMethods::debug_iter`.
673 /// `DirstateMapMethods::debug_iter`.
675 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
674 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
676 (self.state().into(), self.mode(), self.size(), self.mtime())
675 (self.state().into(), self.mode(), self.size(), self.mtime())
677 }
676 }
678 }
677 }
679
678
680 impl EntryState {
679 impl EntryState {
681 pub fn is_tracked(self) -> bool {
680 pub fn is_tracked(self) -> bool {
682 use EntryState::*;
681 use EntryState::*;
683 match self {
682 match self {
684 Normal | Added | Merged => true,
683 Normal | Added | Merged => true,
685 Removed => false,
684 Removed => false,
686 }
685 }
687 }
686 }
688 }
687 }
689
688
690 impl TryFrom<u8> for EntryState {
689 impl TryFrom<u8> for EntryState {
691 type Error = HgError;
690 type Error = HgError;
692
691
693 fn try_from(value: u8) -> Result<Self, Self::Error> {
692 fn try_from(value: u8) -> Result<Self, Self::Error> {
694 match value {
693 match value {
695 b'n' => Ok(EntryState::Normal),
694 b'n' => Ok(EntryState::Normal),
696 b'a' => Ok(EntryState::Added),
695 b'a' => Ok(EntryState::Added),
697 b'r' => Ok(EntryState::Removed),
696 b'r' => Ok(EntryState::Removed),
698 b'm' => Ok(EntryState::Merged),
697 b'm' => Ok(EntryState::Merged),
699 _ => Err(HgError::CorruptedRepository(format!(
698 _ => Err(HgError::CorruptedRepository(format!(
700 "Incorrect dirstate entry state {}",
699 "Incorrect dirstate entry state {}",
701 value
700 value
702 ))),
701 ))),
703 }
702 }
704 }
703 }
705 }
704 }
706
705
707 impl Into<u8> for EntryState {
706 impl Into<u8> for EntryState {
708 fn into(self) -> u8 {
707 fn into(self) -> u8 {
709 match self {
708 match self {
710 EntryState::Normal => b'n',
709 EntryState::Normal => b'n',
711 EntryState::Added => b'a',
710 EntryState::Added => b'a',
712 EntryState::Removed => b'r',
711 EntryState::Removed => b'r',
713 EntryState::Merged => b'm',
712 EntryState::Merged => b'm',
714 }
713 }
715 }
714 }
716 }
715 }
717
716
718 const EXEC_BIT_MASK: u32 = 0o100;
717 const EXEC_BIT_MASK: u32 = 0o100;
719
718
720 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
719 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
721 // TODO: How to handle executable permissions on Windows?
720 // TODO: How to handle executable permissions on Windows?
722 use std::os::unix::fs::MetadataExt;
721 use std::os::unix::fs::MetadataExt;
723 (metadata.mode() & EXEC_BIT_MASK) != 0
722 (metadata.mode() & EXEC_BIT_MASK) != 0
724 }
723 }
General Comments 0
You need to be logged in to leave comments. Login now