##// END OF EJS Templates
rhg: Rename cat_file_is_modified...
Simon Sapin -
r49167:b6d8eea9 default
parent child Browse files
Show More
@@ -1,325 +1,325 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2020, Georges Racinet <georges.racinets@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 use crate::error::CommandError;
9 9 use crate::ui::{Ui, UiError};
10 10 use crate::utils::path_utils::relativize_paths;
11 11 use clap::{Arg, SubCommand};
12 12 use hg;
13 13 use hg::config::Config;
14 14 use hg::dirstate::TruncatedTimestamp;
15 15 use hg::errors::HgError;
16 16 use hg::manifest::Manifest;
17 17 use hg::matchers::AlwaysMatcher;
18 18 use hg::repo::Repo;
19 19 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
20 20 use hg::{HgPathCow, StatusOptions};
21 21 use log::{info, warn};
22 22 use std::borrow::Cow;
23 23
24 24 pub const HELP_TEXT: &str = "
25 25 Show changed files in the working directory
26 26
27 27 This is a pure Rust version of `hg status`.
28 28
29 29 Some options might be missing, check the list below.
30 30 ";
31 31
32 32 pub fn args() -> clap::App<'static, 'static> {
33 33 SubCommand::with_name("status")
34 34 .alias("st")
35 35 .about(HELP_TEXT)
36 36 .arg(
37 37 Arg::with_name("all")
38 38 .help("show status of all files")
39 39 .short("-A")
40 40 .long("--all"),
41 41 )
42 42 .arg(
43 43 Arg::with_name("modified")
44 44 .help("show only modified files")
45 45 .short("-m")
46 46 .long("--modified"),
47 47 )
48 48 .arg(
49 49 Arg::with_name("added")
50 50 .help("show only added files")
51 51 .short("-a")
52 52 .long("--added"),
53 53 )
54 54 .arg(
55 55 Arg::with_name("removed")
56 56 .help("show only removed files")
57 57 .short("-r")
58 58 .long("--removed"),
59 59 )
60 60 .arg(
61 61 Arg::with_name("clean")
62 62 .help("show only clean files")
63 63 .short("-c")
64 64 .long("--clean"),
65 65 )
66 66 .arg(
67 67 Arg::with_name("deleted")
68 68 .help("show only deleted files")
69 69 .short("-d")
70 70 .long("--deleted"),
71 71 )
72 72 .arg(
73 73 Arg::with_name("unknown")
74 74 .help("show only unknown (not tracked) files")
75 75 .short("-u")
76 76 .long("--unknown"),
77 77 )
78 78 .arg(
79 79 Arg::with_name("ignored")
80 80 .help("show only ignored files")
81 81 .short("-i")
82 82 .long("--ignored"),
83 83 )
84 84 }
85 85
86 86 /// Pure data type allowing the caller to specify file states to display
87 87 #[derive(Copy, Clone, Debug)]
88 88 pub struct DisplayStates {
89 89 pub modified: bool,
90 90 pub added: bool,
91 91 pub removed: bool,
92 92 pub clean: bool,
93 93 pub deleted: bool,
94 94 pub unknown: bool,
95 95 pub ignored: bool,
96 96 }
97 97
98 98 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
99 99 modified: true,
100 100 added: true,
101 101 removed: true,
102 102 clean: false,
103 103 deleted: true,
104 104 unknown: true,
105 105 ignored: false,
106 106 };
107 107
108 108 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
109 109 modified: true,
110 110 added: true,
111 111 removed: true,
112 112 clean: true,
113 113 deleted: true,
114 114 unknown: true,
115 115 ignored: true,
116 116 };
117 117
118 118 impl DisplayStates {
119 119 pub fn is_empty(&self) -> bool {
120 120 !(self.modified
121 121 || self.added
122 122 || self.removed
123 123 || self.clean
124 124 || self.deleted
125 125 || self.unknown
126 126 || self.ignored)
127 127 }
128 128 }
129 129
130 130 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
131 131 let status_enabled_default = false;
132 132 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
133 133 if !status_enabled.unwrap_or(status_enabled_default) {
134 134 return Err(CommandError::unsupported(
135 135 "status is experimental in rhg (enable it with 'rhg.status = true' \
136 136 or enable fallback with 'rhg.on-unsupported = fallback')"
137 137 ));
138 138 }
139 139
140 140 // TODO: lift these limitations
141 141 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
142 142 return Err(CommandError::unsupported(
143 143 "ui.tweakdefaults is not yet supported with rhg status",
144 144 ));
145 145 }
146 146 if invocation.config.get_bool(b"ui", b"statuscopies")? {
147 147 return Err(CommandError::unsupported(
148 148 "ui.statuscopies is not yet supported with rhg status",
149 149 ));
150 150 }
151 151 if invocation
152 152 .config
153 153 .get(b"commands", b"status.terse")
154 154 .is_some()
155 155 {
156 156 return Err(CommandError::unsupported(
157 157 "status.terse is not yet supported with rhg status",
158 158 ));
159 159 }
160 160
161 161 let ui = invocation.ui;
162 162 let config = invocation.config;
163 163 let args = invocation.subcommand_args;
164 164 let display_states = if args.is_present("all") {
165 165 // TODO when implementing `--quiet`: it excludes clean files
166 166 // from `--all`
167 167 ALL_DISPLAY_STATES
168 168 } else {
169 169 let requested = DisplayStates {
170 170 modified: args.is_present("modified"),
171 171 added: args.is_present("added"),
172 172 removed: args.is_present("removed"),
173 173 clean: args.is_present("clean"),
174 174 deleted: args.is_present("deleted"),
175 175 unknown: args.is_present("unknown"),
176 176 ignored: args.is_present("ignored"),
177 177 };
178 178 if requested.is_empty() {
179 179 DEFAULT_DISPLAY_STATES
180 180 } else {
181 181 requested
182 182 }
183 183 };
184 184
185 185 let repo = invocation.repo?;
186 186 let mut dmap = repo.dirstate_map_mut()?;
187 187
188 188 let options = StatusOptions {
189 189 // TODO should be provided by the dirstate parsing and
190 190 // hence be stored on dmap. Using a value that assumes we aren't
191 191 // below the time resolution granularity of the FS and the
192 192 // dirstate.
193 193 last_normal_time: TruncatedTimestamp::new_truncate(0, 0),
194 194 // we're currently supporting file systems with exec flags only
195 195 // anyway
196 196 check_exec: true,
197 197 list_clean: display_states.clean,
198 198 list_unknown: display_states.unknown,
199 199 list_ignored: display_states.ignored,
200 200 collect_traversed_dirs: false,
201 201 };
202 202 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
203 203 let (mut ds_status, pattern_warnings) = dmap.status(
204 204 &AlwaysMatcher,
205 205 repo.working_directory_path().to_owned(),
206 206 vec![ignore_file],
207 207 options,
208 208 )?;
209 209 if !pattern_warnings.is_empty() {
210 210 warn!("Pattern warnings: {:?}", &pattern_warnings);
211 211 }
212 212
213 213 if !ds_status.bad.is_empty() {
214 214 warn!("Bad matches {:?}", &(ds_status.bad))
215 215 }
216 216 if !ds_status.unsure.is_empty() {
217 217 info!(
218 218 "Files to be rechecked by retrieval from filelog: {:?}",
219 219 &ds_status.unsure
220 220 );
221 221 }
222 222 if !ds_status.unsure.is_empty()
223 223 && (display_states.modified || display_states.clean)
224 224 {
225 225 let p1 = repo.dirstate_parents()?.p1;
226 226 let manifest = repo.manifest_for_node(p1).map_err(|e| {
227 227 CommandError::from((e, &*format!("{:x}", p1.short())))
228 228 })?;
229 229 for to_check in ds_status.unsure {
230 if cat_file_is_modified(repo, &manifest, &to_check)? {
230 if unsure_is_modified(repo, &manifest, &to_check)? {
231 231 if display_states.modified {
232 232 ds_status.modified.push(to_check);
233 233 }
234 234 } else {
235 235 if display_states.clean {
236 236 ds_status.clean.push(to_check);
237 237 }
238 238 }
239 239 }
240 240 }
241 241 if display_states.modified {
242 242 display_status_paths(ui, repo, config, &mut ds_status.modified, b"M")?;
243 243 }
244 244 if display_states.added {
245 245 display_status_paths(ui, repo, config, &mut ds_status.added, b"A")?;
246 246 }
247 247 if display_states.removed {
248 248 display_status_paths(ui, repo, config, &mut ds_status.removed, b"R")?;
249 249 }
250 250 if display_states.deleted {
251 251 display_status_paths(ui, repo, config, &mut ds_status.deleted, b"!")?;
252 252 }
253 253 if display_states.unknown {
254 254 display_status_paths(ui, repo, config, &mut ds_status.unknown, b"?")?;
255 255 }
256 256 if display_states.ignored {
257 257 display_status_paths(ui, repo, config, &mut ds_status.ignored, b"I")?;
258 258 }
259 259 if display_states.clean {
260 260 display_status_paths(ui, repo, config, &mut ds_status.clean, b"C")?;
261 261 }
262 262 Ok(())
263 263 }
264 264
265 265 // Probably more elegant to use a Deref or Borrow trait rather than
266 266 // harcode HgPathBuf, but probably not really useful at this point
267 267 fn display_status_paths(
268 268 ui: &Ui,
269 269 repo: &Repo,
270 270 config: &Config,
271 271 paths: &mut [HgPathCow],
272 272 status_prefix: &[u8],
273 273 ) -> Result<(), CommandError> {
274 274 paths.sort_unstable();
275 275 let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?;
276 276 relative = config
277 277 .get_option(b"commands", b"status.relative")?
278 278 .unwrap_or(relative);
279 279 if relative && !ui.plain() {
280 280 relativize_paths(
281 281 repo,
282 282 paths.iter().map(Ok),
283 283 |path: Cow<[u8]>| -> Result<(), UiError> {
284 284 ui.write_stdout(
285 285 &[status_prefix, b" ", path.as_ref(), b"\n"].concat(),
286 286 )
287 287 },
288 288 )?;
289 289 } else {
290 290 for path in paths {
291 291 // Same TODO as in commands::root
292 292 let bytes: &[u8] = path.as_bytes();
293 293 // TODO optim, probably lots of unneeded copies here, especially
294 294 // if out stream is buffered
295 295 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
296 296 }
297 297 }
298 298 Ok(())
299 299 }
300 300
301 301 /// Check if a file is modified by comparing actual repo store and file system.
302 302 ///
303 303 /// This meant to be used for those that the dirstate cannot resolve, due
304 304 /// to time resolution limits.
305 305 ///
306 306 /// TODO: detect permission bits and similar metadata modifications
307 fn cat_file_is_modified(
307 fn unsure_is_modified(
308 308 repo: &Repo,
309 309 manifest: &Manifest,
310 310 hg_path: &HgPath,
311 311 ) -> Result<bool, HgError> {
312 312 let entry = manifest
313 313 .find_file(hg_path)?
314 314 .expect("ambgious file not in p1");
315 315 let filelog = repo.filelog(hg_path)?;
316 316 let filelog_entry =
317 317 filelog.data_for_node(entry.node_id()?).map_err(|_| {
318 318 HgError::corrupted("filelog missing node from manifest")
319 319 })?;
320 320 let contents_in_p1 = filelog_entry.data()?;
321 321
322 322 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
323 323 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
324 324 return Ok(contents_in_p1 != &*fs_contents);
325 325 }
General Comments 0
You need to be logged in to leave comments. Login now