##// END OF EJS Templates
rust-status: refactor dispatch case for normal files...
Raphaël Gomès -
r44002:51cd8673 default
parent child Browse files
Show More
@@ -1,221 +1,223 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Rust implementation of dirstate.status (dirstate.py).
9 9 //! It is currently missing a lot of functionality compared to the Python one
10 10 //! and will only be triggered in narrow cases.
11 11
12 12 use crate::utils::files::HgMetadata;
13 13 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath};
14 14 use crate::{CopyMap, DirstateEntry, DirstateMap, EntryState};
15 15 use rayon::prelude::*;
16 16 use std::path::Path;
17 17
18 18 /// Marker enum used to dispatch new status entries into the right collections.
19 19 /// Is similar to `crate::EntryState`, but represents the transient state of
20 20 /// entries during the lifetime of a command.
21 21 enum Dispatch {
22 22 Unsure,
23 23 Modified,
24 24 Added,
25 25 Removed,
26 26 Deleted,
27 27 Clean,
28 28 Unknown,
29 29 }
30 30
31 /// Dates and times that are outside the 31-bit signed range are compared
32 /// modulo 2^31. This should prevent hg from behaving badly with very large
33 /// files or corrupt dates while still having a high probability of detecting
34 /// changes. (issue2608)
35 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
36 /// is not defined for `i32`, and there is no `As` trait. This forces the
37 /// caller to cast `b` as `i32`.
38 fn mod_compare(a: i32, b: i32) -> bool {
39 a & i32::max_value() != b & i32::max_value()
40 }
41
31 42 /// The file corresponding to the dirstate entry was found on the filesystem.
32 43 fn dispatch_found(
33 44 filename: impl AsRef<HgPath>,
34 45 entry: DirstateEntry,
35 46 metadata: HgMetadata,
36 47 copy_map: &CopyMap,
37 48 check_exec: bool,
38 49 list_clean: bool,
39 50 last_normal_time: i64,
40 51 ) -> Dispatch {
41 52 let DirstateEntry {
42 53 state,
43 54 mode,
44 55 mtime,
45 56 size,
46 57 } = entry;
47 58
48 59 let HgMetadata {
49 60 st_mode,
50 61 st_size,
51 62 st_mtime,
52 63 ..
53 64 } = metadata;
54 65
55 66 match state {
56 67 EntryState::Normal => {
57 // Dates and times that are outside the 31-bit signed
58 // range are compared modulo 2^31. This should prevent
59 // it from behaving badly with very large files or
60 // corrupt dates while still having a high probability
61 // of detecting changes. (issue2608)
62 let range_mask = 0x7fffffff;
63
64 let size_changed = (size != st_size as i32)
65 && size != (st_size as i32 & range_mask);
68 let size_changed = mod_compare(size, st_size as i32);
66 69 let mode_changed =
67 70 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
68 if size >= 0
69 && (size_changed || mode_changed)
70 || size == -2 // other parent
71 || copy_map.contains_key(filename.as_ref())
71 let metadata_changed = size >= 0 && (size_changed || mode_changed);
72 let other_parent = size == -2;
73 if metadata_changed
74 || other_parent
75 || copy_map.contains_key(filename.as_ref())
72 76 {
73 77 Dispatch::Modified
74 } else if mtime != st_mtime as i32
75 && mtime != (st_mtime as i32 & range_mask)
76 {
78 } else if mod_compare(mtime, st_mtime as i32) {
77 79 Dispatch::Unsure
78 80 } else if st_mtime == last_normal_time {
79 81 // the file may have just been marked as normal and
80 82 // it may have changed in the same second without
81 83 // changing its size. This can happen if we quickly
82 84 // do multiple commits. Force lookup, so we don't
83 85 // miss such a racy file change.
84 86 Dispatch::Unsure
85 87 } else if list_clean {
86 88 Dispatch::Clean
87 89 } else {
88 90 Dispatch::Unknown
89 91 }
90 92 }
91 93 EntryState::Merged => Dispatch::Modified,
92 94 EntryState::Added => Dispatch::Added,
93 95 EntryState::Removed => Dispatch::Removed,
94 96 EntryState::Unknown => Dispatch::Unknown,
95 97 }
96 98 }
97 99
98 100 /// The file corresponding to this Dirstate entry is missing.
99 101 fn dispatch_missing(state: EntryState) -> Dispatch {
100 102 match state {
101 103 // File was removed from the filesystem during commands
102 104 EntryState::Normal | EntryState::Merged | EntryState::Added => {
103 105 Dispatch::Deleted
104 106 }
105 107 // File was removed, everything is normal
106 108 EntryState::Removed => Dispatch::Removed,
107 109 // File is unknown to Mercurial, everything is normal
108 110 EntryState::Unknown => Dispatch::Unknown,
109 111 }
110 112 }
111 113
112 114 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
113 115 /// the relevant collections.
114 116 fn stat_dmap_entries(
115 117 dmap: &DirstateMap,
116 118 root_dir: impl AsRef<Path> + Sync + Send,
117 119 check_exec: bool,
118 120 list_clean: bool,
119 121 last_normal_time: i64,
120 122 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
121 123 dmap.par_iter().map(move |(filename, entry)| {
122 124 let filename: &HgPath = filename;
123 125 let filename_as_path = hg_path_to_path_buf(filename)?;
124 126 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
125 127
126 128 match meta {
127 129 Ok(ref m)
128 130 if !(m.file_type().is_file()
129 131 || m.file_type().is_symlink()) =>
130 132 {
131 133 Ok((filename, dispatch_missing(entry.state)))
132 134 }
133 135 Ok(m) => Ok((
134 136 filename,
135 137 dispatch_found(
136 138 filename,
137 139 *entry,
138 140 HgMetadata::from_metadata(m),
139 141 &dmap.copy_map,
140 142 check_exec,
141 143 list_clean,
142 144 last_normal_time,
143 145 ),
144 146 )),
145 147 Err(ref e)
146 148 if e.kind() == std::io::ErrorKind::NotFound
147 149 || e.raw_os_error() == Some(20) =>
148 150 {
149 151 // Rust does not yet have an `ErrorKind` for
150 152 // `NotADirectory` (errno 20)
151 153 // It happens if the dirstate contains `foo/bar` and
152 154 // foo is not a directory
153 155 Ok((filename, dispatch_missing(entry.state)))
154 156 }
155 157 Err(e) => Err(e),
156 158 }
157 159 })
158 160 }
159 161
160 162 pub struct StatusResult<'a> {
161 163 pub modified: Vec<&'a HgPath>,
162 164 pub added: Vec<&'a HgPath>,
163 165 pub removed: Vec<&'a HgPath>,
164 166 pub deleted: Vec<&'a HgPath>,
165 167 pub clean: Vec<&'a HgPath>,
166 168 // TODO ignored
167 169 // TODO unknown
168 170 }
169 171
170 172 fn build_response(
171 173 results: Vec<(&HgPath, Dispatch)>,
172 174 ) -> (Vec<&HgPath>, StatusResult) {
173 175 let mut lookup = vec![];
174 176 let mut modified = vec![];
175 177 let mut added = vec![];
176 178 let mut removed = vec![];
177 179 let mut deleted = vec![];
178 180 let mut clean = vec![];
179 181
180 182 for (filename, dispatch) in results.into_iter() {
181 183 match dispatch {
182 184 Dispatch::Unknown => {}
183 185 Dispatch::Unsure => lookup.push(filename),
184 186 Dispatch::Modified => modified.push(filename),
185 187 Dispatch::Added => added.push(filename),
186 188 Dispatch::Removed => removed.push(filename),
187 189 Dispatch::Deleted => deleted.push(filename),
188 190 Dispatch::Clean => clean.push(filename),
189 191 }
190 192 }
191 193
192 194 (
193 195 lookup,
194 196 StatusResult {
195 197 modified,
196 198 added,
197 199 removed,
198 200 deleted,
199 201 clean,
200 202 },
201 203 )
202 204 }
203 205
204 206 pub fn status(
205 207 dmap: &DirstateMap,
206 208 root_dir: impl AsRef<Path> + Sync + Send + Copy,
207 209 list_clean: bool,
208 210 last_normal_time: i64,
209 211 check_exec: bool,
210 212 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
211 213 let results: std::io::Result<_> = stat_dmap_entries(
212 214 &dmap,
213 215 root_dir,
214 216 check_exec,
215 217 list_clean,
216 218 last_normal_time,
217 219 )
218 220 .collect();
219 221
220 222 Ok(build_response(results?))
221 223 }
General Comments 0
You need to be logged in to leave comments. Login now