##// END OF EJS Templates
rhg: Add support for `rhg status -n`...
Simon Sapin -
r49171:c12ed335 default
parent child Browse files
Show More
@@ -1,343 +1,400
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 use crate::ui::{Ui, UiError};
9 use crate::ui::Ui;
10 10 use crate::utils::path_utils::relativize_paths;
11 11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 13 use hg;
13 14 use hg::config::Config;
14 15 use hg::dirstate::{has_exec_bit, TruncatedTimestamp};
15 16 use hg::errors::HgError;
16 17 use hg::manifest::Manifest;
17 18 use hg::matchers::AlwaysMatcher;
18 19 use hg::repo::Repo;
19 20 use hg::utils::files::get_bytes_from_os_string;
20 21 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
21 22 use hg::{HgPathCow, StatusOptions};
22 23 use log::{info, warn};
23 use std::borrow::Cow;
24 24
25 25 pub const HELP_TEXT: &str = "
26 26 Show changed files in the working directory
27 27
28 28 This is a pure Rust version of `hg status`.
29 29
30 30 Some options might be missing, check the list below.
31 31 ";
32 32
33 33 pub fn args() -> clap::App<'static, 'static> {
34 34 SubCommand::with_name("status")
35 35 .alias("st")
36 36 .about(HELP_TEXT)
37 37 .arg(
38 38 Arg::with_name("all")
39 39 .help("show status of all files")
40 40 .short("-A")
41 41 .long("--all"),
42 42 )
43 43 .arg(
44 44 Arg::with_name("modified")
45 45 .help("show only modified files")
46 46 .short("-m")
47 47 .long("--modified"),
48 48 )
49 49 .arg(
50 50 Arg::with_name("added")
51 51 .help("show only added files")
52 52 .short("-a")
53 53 .long("--added"),
54 54 )
55 55 .arg(
56 56 Arg::with_name("removed")
57 57 .help("show only removed files")
58 58 .short("-r")
59 59 .long("--removed"),
60 60 )
61 61 .arg(
62 62 Arg::with_name("clean")
63 63 .help("show only clean files")
64 64 .short("-c")
65 65 .long("--clean"),
66 66 )
67 67 .arg(
68 68 Arg::with_name("deleted")
69 69 .help("show only deleted files")
70 70 .short("-d")
71 71 .long("--deleted"),
72 72 )
73 73 .arg(
74 74 Arg::with_name("unknown")
75 75 .help("show only unknown (not tracked) files")
76 76 .short("-u")
77 77 .long("--unknown"),
78 78 )
79 79 .arg(
80 80 Arg::with_name("ignored")
81 81 .help("show only ignored files")
82 82 .short("-i")
83 83 .long("--ignored"),
84 84 )
85 .arg(
86 Arg::with_name("no-status")
87 .help("hide status prefix")
88 .short("-n")
89 .long("--no-status"),
90 )
85 91 }
86 92
87 93 /// Pure data type allowing the caller to specify file states to display
88 94 #[derive(Copy, Clone, Debug)]
89 95 pub struct DisplayStates {
90 96 pub modified: bool,
91 97 pub added: bool,
92 98 pub removed: bool,
93 99 pub clean: bool,
94 100 pub deleted: bool,
95 101 pub unknown: bool,
96 102 pub ignored: bool,
97 103 }
98 104
99 105 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
100 106 modified: true,
101 107 added: true,
102 108 removed: true,
103 109 clean: false,
104 110 deleted: true,
105 111 unknown: true,
106 112 ignored: false,
107 113 };
108 114
109 115 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
110 116 modified: true,
111 117 added: true,
112 118 removed: true,
113 119 clean: true,
114 120 deleted: true,
115 121 unknown: true,
116 122 ignored: true,
117 123 };
118 124
119 125 impl DisplayStates {
120 126 pub fn is_empty(&self) -> bool {
121 127 !(self.modified
122 128 || self.added
123 129 || self.removed
124 130 || self.clean
125 131 || self.deleted
126 132 || self.unknown
127 133 || self.ignored)
128 134 }
129 135 }
130 136
131 137 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
132 138 let status_enabled_default = false;
133 139 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
134 140 if !status_enabled.unwrap_or(status_enabled_default) {
135 141 return Err(CommandError::unsupported(
136 142 "status is experimental in rhg (enable it with 'rhg.status = true' \
137 143 or enable fallback with 'rhg.on-unsupported = fallback')"
138 144 ));
139 145 }
140 146
141 147 // TODO: lift these limitations
142 148 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
143 149 return Err(CommandError::unsupported(
144 150 "ui.tweakdefaults is not yet supported with rhg status",
145 151 ));
146 152 }
147 153 if invocation.config.get_bool(b"ui", b"statuscopies")? {
148 154 return Err(CommandError::unsupported(
149 155 "ui.statuscopies is not yet supported with rhg status",
150 156 ));
151 157 }
152 158 if invocation
153 159 .config
154 160 .get(b"commands", b"status.terse")
155 161 .is_some()
156 162 {
157 163 return Err(CommandError::unsupported(
158 164 "status.terse is not yet supported with rhg status",
159 165 ));
160 166 }
161 167
162 168 let ui = invocation.ui;
163 169 let config = invocation.config;
164 170 let args = invocation.subcommand_args;
165 171 let display_states = if args.is_present("all") {
166 172 // TODO when implementing `--quiet`: it excludes clean files
167 173 // from `--all`
168 174 ALL_DISPLAY_STATES
169 175 } else {
170 176 let requested = DisplayStates {
171 177 modified: args.is_present("modified"),
172 178 added: args.is_present("added"),
173 179 removed: args.is_present("removed"),
174 180 clean: args.is_present("clean"),
175 181 deleted: args.is_present("deleted"),
176 182 unknown: args.is_present("unknown"),
177 183 ignored: args.is_present("ignored"),
178 184 };
179 185 if requested.is_empty() {
180 186 DEFAULT_DISPLAY_STATES
181 187 } else {
182 188 requested
183 189 }
184 190 };
191 let no_status = args.is_present("no-status");
185 192
186 193 let repo = invocation.repo?;
187 194 let mut dmap = repo.dirstate_map_mut()?;
188 195
189 196 let options = StatusOptions {
190 197 // TODO should be provided by the dirstate parsing and
191 198 // hence be stored on dmap. Using a value that assumes we aren't
192 199 // below the time resolution granularity of the FS and the
193 200 // dirstate.
194 201 last_normal_time: TruncatedTimestamp::new_truncate(0, 0),
195 202 // we're currently supporting file systems with exec flags only
196 203 // anyway
197 204 check_exec: true,
198 205 list_clean: display_states.clean,
199 206 list_unknown: display_states.unknown,
200 207 list_ignored: display_states.ignored,
201 208 collect_traversed_dirs: false,
202 209 };
203 210 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
204 211 let (mut ds_status, pattern_warnings) = dmap.status(
205 212 &AlwaysMatcher,
206 213 repo.working_directory_path().to_owned(),
207 214 vec![ignore_file],
208 215 options,
209 216 )?;
210 217 if !pattern_warnings.is_empty() {
211 218 warn!("Pattern warnings: {:?}", &pattern_warnings);
212 219 }
213 220
214 221 if !ds_status.bad.is_empty() {
215 222 warn!("Bad matches {:?}", &(ds_status.bad))
216 223 }
217 224 if !ds_status.unsure.is_empty() {
218 225 info!(
219 226 "Files to be rechecked by retrieval from filelog: {:?}",
220 227 &ds_status.unsure
221 228 );
222 229 }
223 230 if !ds_status.unsure.is_empty()
224 231 && (display_states.modified || display_states.clean)
225 232 {
226 233 let p1 = repo.dirstate_parents()?.p1;
227 234 let manifest = repo.manifest_for_node(p1).map_err(|e| {
228 235 CommandError::from((e, &*format!("{:x}", p1.short())))
229 236 })?;
230 237 for to_check in ds_status.unsure {
231 238 if unsure_is_modified(repo, &manifest, &to_check)? {
232 239 if display_states.modified {
233 240 ds_status.modified.push(to_check);
234 241 }
235 242 } else {
236 243 if display_states.clean {
237 244 ds_status.clean.push(to_check);
238 245 }
239 246 }
240 247 }
241 248 }
242 249 if display_states.modified {
243 display_status_paths(ui, repo, config, &mut ds_status.modified, b"M")?;
250 display_status_paths(
251 ui,
252 repo,
253 config,
254 no_status,
255 &mut ds_status.modified,
256 b"M",
257 )?;
244 258 }
245 259 if display_states.added {
246 display_status_paths(ui, repo, config, &mut ds_status.added, b"A")?;
260 display_status_paths(
261 ui,
262 repo,
263 config,
264 no_status,
265 &mut ds_status.added,
266 b"A",
267 )?;
247 268 }
248 269 if display_states.removed {
249 display_status_paths(ui, repo, config, &mut ds_status.removed, b"R")?;
270 display_status_paths(
271 ui,
272 repo,
273 config,
274 no_status,
275 &mut ds_status.removed,
276 b"R",
277 )?;
250 278 }
251 279 if display_states.deleted {
252 display_status_paths(ui, repo, config, &mut ds_status.deleted, b"!")?;
280 display_status_paths(
281 ui,
282 repo,
283 config,
284 no_status,
285 &mut ds_status.deleted,
286 b"!",
287 )?;
253 288 }
254 289 if display_states.unknown {
255 display_status_paths(ui, repo, config, &mut ds_status.unknown, b"?")?;
290 display_status_paths(
291 ui,
292 repo,
293 config,
294 no_status,
295 &mut ds_status.unknown,
296 b"?",
297 )?;
256 298 }
257 299 if display_states.ignored {
258 display_status_paths(ui, repo, config, &mut ds_status.ignored, b"I")?;
300 display_status_paths(
301 ui,
302 repo,
303 config,
304 no_status,
305 &mut ds_status.ignored,
306 b"I",
307 )?;
259 308 }
260 309 if display_states.clean {
261 display_status_paths(ui, repo, config, &mut ds_status.clean, b"C")?;
310 display_status_paths(
311 ui,
312 repo,
313 config,
314 no_status,
315 &mut ds_status.clean,
316 b"C",
317 )?;
262 318 }
263 319 Ok(())
264 320 }
265 321
266 322 // Probably more elegant to use a Deref or Borrow trait rather than
267 323 // harcode HgPathBuf, but probably not really useful at this point
268 324 fn display_status_paths(
269 325 ui: &Ui,
270 326 repo: &Repo,
271 327 config: &Config,
328 no_status: bool,
272 329 paths: &mut [HgPathCow],
273 330 status_prefix: &[u8],
274 331 ) -> Result<(), CommandError> {
275 332 paths.sort_unstable();
276 333 let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?;
277 334 relative = config
278 335 .get_option(b"commands", b"status.relative")?
279 336 .unwrap_or(relative);
337 let print_path = |path: &[u8]| {
338 // TODO optim, probably lots of unneeded copies here, especially
339 // if out stream is buffered
340 if no_status {
341 ui.write_stdout(&format_bytes!(b"{}\n", path))
342 } else {
343 ui.write_stdout(&format_bytes!(b"{} {}\n", status_prefix, path))
344 }
345 };
346
280 347 if relative && !ui.plain() {
281 relativize_paths(
282 repo,
283 paths.iter().map(Ok),
284 |path: Cow<[u8]>| -> Result<(), UiError> {
285 ui.write_stdout(
286 &[status_prefix, b" ", path.as_ref(), b"\n"].concat(),
287 )
288 },
289 )?;
348 relativize_paths(repo, paths.iter().map(Ok), |path| {
349 print_path(&path)
350 })?;
290 351 } else {
291 352 for path in paths {
292 // Same TODO as in commands::root
293 let bytes: &[u8] = path.as_bytes();
294 // TODO optim, probably lots of unneeded copies here, especially
295 // if out stream is buffered
296 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
353 print_path(path.as_bytes())?
297 354 }
298 355 }
299 356 Ok(())
300 357 }
301 358
302 359 /// Check if a file is modified by comparing actual repo store and file system.
303 360 ///
304 361 /// This meant to be used for those that the dirstate cannot resolve, due
305 362 /// to time resolution limits.
306 363 fn unsure_is_modified(
307 364 repo: &Repo,
308 365 manifest: &Manifest,
309 366 hg_path: &HgPath,
310 367 ) -> Result<bool, HgError> {
311 368 let vfs = repo.working_directory_vfs();
312 369 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
313 370 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
314 371 let is_symlink = fs_metadata.file_type().is_symlink();
315 372 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the dirstate
316 373 let fs_flags = if is_symlink {
317 374 Some(b'l')
318 375 } else if has_exec_bit(&fs_metadata) {
319 376 Some(b'x')
320 377 } else {
321 378 None
322 379 };
323 380
324 381 let entry = manifest
325 382 .find_file(hg_path)?
326 383 .expect("ambgious file not in p1");
327 384 if entry.flags != fs_flags {
328 385 return Ok(true);
329 386 }
330 387 let filelog = repo.filelog(hg_path)?;
331 388 let filelog_entry =
332 389 filelog.data_for_node(entry.node_id()?).map_err(|_| {
333 390 HgError::corrupted("filelog missing node from manifest")
334 391 })?;
335 392 let contents_in_p1 = filelog_entry.data()?;
336 393
337 394 let fs_contents = if is_symlink {
338 395 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
339 396 } else {
340 397 vfs.read(fs_path)?
341 398 };
342 399 return Ok(contents_in_p1 != &*fs_contents);
343 400 }
@@ -1,969 +1,976
1 1 #testcases dirstate-v1 dirstate-v2
2 2
3 3 #if dirstate-v2
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [format]
6 6 > exp-rc-dirstate-v2=1
7 7 > [storage]
8 8 > dirstate-v2.slow-path=allow
9 9 > EOF
10 10 #endif
11 11
12 12 TODO: fix rhg bugs that make this test fail when status is enabled
13 13 $ unset RHG_STATUS
14 14
15 15
16 16 $ hg init repo1
17 17 $ cd repo1
18 18 $ mkdir a b a/1 b/1 b/2
19 19 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
20 20
21 21 hg status in repo root:
22 22
23 23 $ hg status
24 24 ? a/1/in_a_1
25 25 ? a/in_a
26 26 ? b/1/in_b_1
27 27 ? b/2/in_b_2
28 28 ? b/in_b
29 29 ? in_root
30 30
31 31 hg status . in repo root:
32 32
33 33 $ hg status .
34 34 ? a/1/in_a_1
35 35 ? a/in_a
36 36 ? b/1/in_b_1
37 37 ? b/2/in_b_2
38 38 ? b/in_b
39 39 ? in_root
40 40
41 41 $ hg status --cwd a
42 42 ? a/1/in_a_1
43 43 ? a/in_a
44 44 ? b/1/in_b_1
45 45 ? b/2/in_b_2
46 46 ? b/in_b
47 47 ? in_root
48 48 $ hg status --cwd a .
49 49 ? 1/in_a_1
50 50 ? in_a
51 51 $ hg status --cwd a ..
52 52 ? 1/in_a_1
53 53 ? in_a
54 54 ? ../b/1/in_b_1
55 55 ? ../b/2/in_b_2
56 56 ? ../b/in_b
57 57 ? ../in_root
58 58
59 59 $ hg status --cwd b
60 60 ? a/1/in_a_1
61 61 ? a/in_a
62 62 ? b/1/in_b_1
63 63 ? b/2/in_b_2
64 64 ? b/in_b
65 65 ? in_root
66 66 $ hg status --cwd b .
67 67 ? 1/in_b_1
68 68 ? 2/in_b_2
69 69 ? in_b
70 70 $ hg status --cwd b ..
71 71 ? ../a/1/in_a_1
72 72 ? ../a/in_a
73 73 ? 1/in_b_1
74 74 ? 2/in_b_2
75 75 ? in_b
76 76 ? ../in_root
77 77
78 78 $ hg status --cwd a/1
79 79 ? a/1/in_a_1
80 80 ? a/in_a
81 81 ? b/1/in_b_1
82 82 ? b/2/in_b_2
83 83 ? b/in_b
84 84 ? in_root
85 85 $ hg status --cwd a/1 .
86 86 ? in_a_1
87 87 $ hg status --cwd a/1 ..
88 88 ? in_a_1
89 89 ? ../in_a
90 90
91 91 $ hg status --cwd b/1
92 92 ? a/1/in_a_1
93 93 ? a/in_a
94 94 ? b/1/in_b_1
95 95 ? b/2/in_b_2
96 96 ? b/in_b
97 97 ? in_root
98 98 $ hg status --cwd b/1 .
99 99 ? in_b_1
100 100 $ hg status --cwd b/1 ..
101 101 ? in_b_1
102 102 ? ../2/in_b_2
103 103 ? ../in_b
104 104
105 105 $ hg status --cwd b/2
106 106 ? a/1/in_a_1
107 107 ? a/in_a
108 108 ? b/1/in_b_1
109 109 ? b/2/in_b_2
110 110 ? b/in_b
111 111 ? in_root
112 112 $ hg status --cwd b/2 .
113 113 ? in_b_2
114 114 $ hg status --cwd b/2 ..
115 115 ? ../1/in_b_1
116 116 ? in_b_2
117 117 ? ../in_b
118 118
119 119 combining patterns with root and patterns without a root works
120 120
121 121 $ hg st a/in_a re:.*b$
122 122 ? a/in_a
123 123 ? b/in_b
124 124
125 125 tweaking defaults works
126 126 $ hg status --cwd a --config ui.tweakdefaults=yes
127 127 ? 1/in_a_1
128 128 ? in_a
129 129 ? ../b/1/in_b_1
130 130 ? ../b/2/in_b_2
131 131 ? ../b/in_b
132 132 ? ../in_root
133 133 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
134 134 ? a/1/in_a_1 (glob)
135 135 ? a/in_a (glob)
136 136 ? b/1/in_b_1 (glob)
137 137 ? b/2/in_b_2 (glob)
138 138 ? b/in_b (glob)
139 139 ? in_root
140 140 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
141 141 ? 1/in_a_1
142 142 ? in_a
143 143 ? ../b/1/in_b_1
144 144 ? ../b/2/in_b_2
145 145 ? ../b/in_b
146 146 ? ../in_root (glob)
147 147
148 148 relative paths can be requested
149 149
150 150 $ hg status --cwd a --config ui.relative-paths=yes
151 151 ? 1/in_a_1
152 152 ? in_a
153 153 ? ../b/1/in_b_1
154 154 ? ../b/2/in_b_2
155 155 ? ../b/in_b
156 156 ? ../in_root
157 157
158 158 $ hg status --cwd a . --config ui.relative-paths=legacy
159 159 ? 1/in_a_1
160 160 ? in_a
161 161 $ hg status --cwd a . --config ui.relative-paths=no
162 162 ? a/1/in_a_1
163 163 ? a/in_a
164 164
165 165 commands.status.relative overrides ui.relative-paths
166 166
167 167 $ cat >> $HGRCPATH <<EOF
168 168 > [ui]
169 169 > relative-paths = False
170 170 > [commands]
171 171 > status.relative = True
172 172 > EOF
173 173 $ hg status --cwd a
174 174 ? 1/in_a_1
175 175 ? in_a
176 176 ? ../b/1/in_b_1
177 177 ? ../b/2/in_b_2
178 178 ? ../b/in_b
179 179 ? ../in_root
180 180 $ HGPLAIN=1 hg status --cwd a
181 181 ? a/1/in_a_1 (glob)
182 182 ? a/in_a (glob)
183 183 ? b/1/in_b_1 (glob)
184 184 ? b/2/in_b_2 (glob)
185 185 ? b/in_b (glob)
186 186 ? in_root
187 187
188 188 if relative paths are explicitly off, tweakdefaults doesn't change it
189 189 $ cat >> $HGRCPATH <<EOF
190 190 > [commands]
191 191 > status.relative = False
192 192 > EOF
193 193 $ hg status --cwd a --config ui.tweakdefaults=yes
194 194 ? a/1/in_a_1
195 195 ? a/in_a
196 196 ? b/1/in_b_1
197 197 ? b/2/in_b_2
198 198 ? b/in_b
199 199 ? in_root
200 200
201 201 $ cd ..
202 202
203 203 $ hg init repo2
204 204 $ cd repo2
205 205 $ touch modified removed deleted ignored
206 206 $ echo "^ignored$" > .hgignore
207 207 $ hg ci -A -m 'initial checkin'
208 208 adding .hgignore
209 209 adding deleted
210 210 adding modified
211 211 adding removed
212 212 $ touch modified added unknown ignored
213 213 $ hg add added
214 214 $ hg remove removed
215 215 $ rm deleted
216 216
217 217 hg status:
218 218
219 219 $ hg status
220 220 A added
221 221 R removed
222 222 ! deleted
223 223 ? unknown
224 224
225 hg status -n:
226 $ env RHG_STATUS=1 RHG_ON_UNSUPPORTED=abort hg status -n
227 added
228 removed
229 deleted
230 unknown
231
225 232 hg status modified added removed deleted unknown never-existed ignored:
226 233
227 234 $ hg status modified added removed deleted unknown never-existed ignored
228 235 never-existed: * (glob)
229 236 A added
230 237 R removed
231 238 ! deleted
232 239 ? unknown
233 240
234 241 $ hg copy modified copied
235 242
236 243 hg status -C:
237 244
238 245 $ hg status -C
239 246 A added
240 247 A copied
241 248 modified
242 249 R removed
243 250 ! deleted
244 251 ? unknown
245 252
246 253 hg status -A:
247 254
248 255 $ hg status -A
249 256 A added
250 257 A copied
251 258 modified
252 259 R removed
253 260 ! deleted
254 261 ? unknown
255 262 I ignored
256 263 C .hgignore
257 264 C modified
258 265
259 266 $ hg status -A -T '{status} {path} {node|shortest}\n'
260 267 A added ffff
261 268 A copied ffff
262 269 R removed ffff
263 270 ! deleted ffff
264 271 ? unknown ffff
265 272 I ignored ffff
266 273 C .hgignore ffff
267 274 C modified ffff
268 275
269 276 $ hg status -A -Tjson
270 277 [
271 278 {
272 279 "itemtype": "file",
273 280 "path": "added",
274 281 "status": "A"
275 282 },
276 283 {
277 284 "itemtype": "file",
278 285 "path": "copied",
279 286 "source": "modified",
280 287 "status": "A"
281 288 },
282 289 {
283 290 "itemtype": "file",
284 291 "path": "removed",
285 292 "status": "R"
286 293 },
287 294 {
288 295 "itemtype": "file",
289 296 "path": "deleted",
290 297 "status": "!"
291 298 },
292 299 {
293 300 "itemtype": "file",
294 301 "path": "unknown",
295 302 "status": "?"
296 303 },
297 304 {
298 305 "itemtype": "file",
299 306 "path": "ignored",
300 307 "status": "I"
301 308 },
302 309 {
303 310 "itemtype": "file",
304 311 "path": ".hgignore",
305 312 "status": "C"
306 313 },
307 314 {
308 315 "itemtype": "file",
309 316 "path": "modified",
310 317 "status": "C"
311 318 }
312 319 ]
313 320
314 321 $ hg status -A -Tpickle > pickle
315 322 >>> from __future__ import print_function
316 323 >>> from mercurial import util
317 324 >>> pickle = util.pickle
318 325 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
319 326 >>> for s, p in data: print("%s %s" % (s, p))
320 327 ! deleted
321 328 ? pickle
322 329 ? unknown
323 330 A added
324 331 A copied
325 332 C .hgignore
326 333 C modified
327 334 I ignored
328 335 R removed
329 336 $ rm pickle
330 337
331 338 $ echo "^ignoreddir$" > .hgignore
332 339 $ mkdir ignoreddir
333 340 $ touch ignoreddir/file
334 341
335 342 Test templater support:
336 343
337 344 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
338 345 [M] .hgignore
339 346 [A] added
340 347 [A] modified -> copied
341 348 [R] removed
342 349 [!] deleted
343 350 [?] ignored
344 351 [?] unknown
345 352 [I] ignoreddir/file
346 353 [C] modified
347 354 $ hg status -AT default
348 355 M .hgignore
349 356 A added
350 357 A copied
351 358 modified
352 359 R removed
353 360 ! deleted
354 361 ? ignored
355 362 ? unknown
356 363 I ignoreddir/file
357 364 C modified
358 365 $ hg status -T compact
359 366 abort: "status" not in template map
360 367 [255]
361 368
362 369 hg status ignoreddir/file:
363 370
364 371 $ hg status ignoreddir/file
365 372
366 373 hg status -i ignoreddir/file:
367 374
368 375 $ hg status -i ignoreddir/file
369 376 I ignoreddir/file
370 377 $ cd ..
371 378
372 379 Check 'status -q' and some combinations
373 380
374 381 $ hg init repo3
375 382 $ cd repo3
376 383 $ touch modified removed deleted ignored
377 384 $ echo "^ignored$" > .hgignore
378 385 $ hg commit -A -m 'initial checkin'
379 386 adding .hgignore
380 387 adding deleted
381 388 adding modified
382 389 adding removed
383 390 $ touch added unknown ignored
384 391 $ hg add added
385 392 $ echo "test" >> modified
386 393 $ hg remove removed
387 394 $ rm deleted
388 395 $ hg copy modified copied
389 396
390 397 Specify working directory revision explicitly, that should be the same as
391 398 "hg status"
392 399
393 400 $ hg status --change "wdir()"
394 401 M modified
395 402 A added
396 403 A copied
397 404 R removed
398 405 ! deleted
399 406 ? unknown
400 407
401 408 Run status with 2 different flags.
402 409 Check if result is the same or different.
403 410 If result is not as expected, raise error
404 411
405 412 $ assert() {
406 413 > hg status $1 > ../a
407 414 > hg status $2 > ../b
408 415 > if diff ../a ../b > /dev/null; then
409 416 > out=0
410 417 > else
411 418 > out=1
412 419 > fi
413 420 > if [ $3 -eq 0 ]; then
414 421 > df="same"
415 422 > else
416 423 > df="different"
417 424 > fi
418 425 > if [ $out -ne $3 ]; then
419 426 > echo "Error on $1 and $2, should be $df."
420 427 > fi
421 428 > }
422 429
423 430 Assert flag1 flag2 [0-same | 1-different]
424 431
425 432 $ assert "-q" "-mard" 0
426 433 $ assert "-A" "-marduicC" 0
427 434 $ assert "-qA" "-mardcC" 0
428 435 $ assert "-qAui" "-A" 0
429 436 $ assert "-qAu" "-marducC" 0
430 437 $ assert "-qAi" "-mardicC" 0
431 438 $ assert "-qu" "-u" 0
432 439 $ assert "-q" "-u" 1
433 440 $ assert "-m" "-a" 1
434 441 $ assert "-r" "-d" 1
435 442 $ cd ..
436 443
437 444 $ hg init repo4
438 445 $ cd repo4
439 446 $ touch modified removed deleted
440 447 $ hg ci -q -A -m 'initial checkin'
441 448 $ touch added unknown
442 449 $ hg add added
443 450 $ hg remove removed
444 451 $ rm deleted
445 452 $ echo x > modified
446 453 $ hg copy modified copied
447 454 $ hg ci -m 'test checkin' -d "1000001 0"
448 455 $ rm *
449 456 $ touch unrelated
450 457 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
451 458
452 459 hg status --change 1:
453 460
454 461 $ hg status --change 1
455 462 M modified
456 463 A added
457 464 A copied
458 465 R removed
459 466
460 467 hg status --change 1 unrelated:
461 468
462 469 $ hg status --change 1 unrelated
463 470
464 471 hg status -C --change 1 added modified copied removed deleted:
465 472
466 473 $ hg status -C --change 1 added modified copied removed deleted
467 474 M modified
468 475 A added
469 476 A copied
470 477 modified
471 478 R removed
472 479
473 480 hg status -A --change 1 and revset:
474 481
475 482 $ hg status -A --change '1|1'
476 483 M modified
477 484 A added
478 485 A copied
479 486 modified
480 487 R removed
481 488 C deleted
482 489
483 490 $ cd ..
484 491
485 492 hg status with --rev and reverted changes:
486 493
487 494 $ hg init reverted-changes-repo
488 495 $ cd reverted-changes-repo
489 496 $ echo a > file
490 497 $ hg add file
491 498 $ hg ci -m a
492 499 $ echo b > file
493 500 $ hg ci -m b
494 501
495 502 reverted file should appear clean
496 503
497 504 $ hg revert -r 0 .
498 505 reverting file
499 506 $ hg status -A --rev 0
500 507 C file
501 508
502 509 #if execbit
503 510 reverted file with changed flag should appear modified
504 511
505 512 $ chmod +x file
506 513 $ hg status -A --rev 0
507 514 M file
508 515
509 516 $ hg revert -r 0 .
510 517 reverting file
511 518
512 519 reverted and committed file with changed flag should appear modified
513 520
514 521 $ hg co -C .
515 522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
516 523 $ chmod +x file
517 524 $ hg ci -m 'change flag'
518 525 $ hg status -A --rev 1 --rev 2
519 526 M file
520 527 $ hg diff -r 1 -r 2
521 528
522 529 #endif
523 530
524 531 $ cd ..
525 532
526 533 hg status of binary file starting with '\1\n', a separator for metadata:
527 534
528 535 $ hg init repo5
529 536 $ cd repo5
530 537 >>> open("010a", r"wb").write(b"\1\nfoo") and None
531 538 $ hg ci -q -A -m 'initial checkin'
532 539 $ hg status -A
533 540 C 010a
534 541
535 542 >>> open("010a", r"wb").write(b"\1\nbar") and None
536 543 $ hg status -A
537 544 M 010a
538 545 $ hg ci -q -m 'modify 010a'
539 546 $ hg status -A --rev 0:1
540 547 M 010a
541 548
542 549 $ touch empty
543 550 $ hg ci -q -A -m 'add another file'
544 551 $ hg status -A --rev 1:2 010a
545 552 C 010a
546 553
547 554 $ cd ..
548 555
549 556 test "hg status" with "directory pattern" which matches against files
550 557 only known on target revision.
551 558
552 559 $ hg init repo6
553 560 $ cd repo6
554 561
555 562 $ echo a > a.txt
556 563 $ hg add a.txt
557 564 $ hg commit -m '#0'
558 565 $ mkdir -p 1/2/3/4/5
559 566 $ echo b > 1/2/3/4/5/b.txt
560 567 $ hg add 1/2/3/4/5/b.txt
561 568 $ hg commit -m '#1'
562 569
563 570 $ hg update -C 0 > /dev/null
564 571 $ hg status -A
565 572 C a.txt
566 573
567 574 the directory matching against specified pattern should be removed,
568 575 because directory existence prevents 'dirstate.walk()' from showing
569 576 warning message about such pattern.
570 577
571 578 $ test ! -d 1
572 579 $ hg status -A --rev 1 1/2/3/4/5/b.txt
573 580 R 1/2/3/4/5/b.txt
574 581 $ hg status -A --rev 1 1/2/3/4/5
575 582 R 1/2/3/4/5/b.txt
576 583 $ hg status -A --rev 1 1/2/3
577 584 R 1/2/3/4/5/b.txt
578 585 $ hg status -A --rev 1 1
579 586 R 1/2/3/4/5/b.txt
580 587
581 588 $ hg status --config ui.formatdebug=True --rev 1 1
582 589 status = [
583 590 {
584 591 'itemtype': 'file',
585 592 'path': '1/2/3/4/5/b.txt',
586 593 'status': 'R'
587 594 },
588 595 ]
589 596
590 597 #if windows
591 598 $ hg --config ui.slash=false status -A --rev 1 1
592 599 R 1\2\3\4\5\b.txt
593 600 #endif
594 601
595 602 $ cd ..
596 603
597 604 Status after move overwriting a file (issue4458)
598 605 =================================================
599 606
600 607
601 608 $ hg init issue4458
602 609 $ cd issue4458
603 610 $ echo a > a
604 611 $ echo b > b
605 612 $ hg commit -Am base
606 613 adding a
607 614 adding b
608 615
609 616
610 617 with --force
611 618
612 619 $ hg mv b --force a
613 620 $ hg st --copies
614 621 M a
615 622 b
616 623 R b
617 624 $ hg revert --all
618 625 reverting a
619 626 undeleting b
620 627 $ rm *.orig
621 628
622 629 without force
623 630
624 631 $ hg rm a
625 632 $ hg st --copies
626 633 R a
627 634 $ hg mv b a
628 635 $ hg st --copies
629 636 M a
630 637 b
631 638 R b
632 639
633 640 using ui.statuscopies setting
634 641 $ hg st --config ui.statuscopies=true
635 642 M a
636 643 b
637 644 R b
638 645 $ hg st --config ui.statuscopies=false
639 646 M a
640 647 R b
641 648 $ hg st --config ui.tweakdefaults=yes
642 649 M a
643 650 b
644 651 R b
645 652
646 653 using log status template (issue5155)
647 654 $ hg log -Tstatus -r 'wdir()' -C
648 655 changeset: 2147483647:ffffffffffff
649 656 parent: 0:8c55c58b4c0e
650 657 user: test
651 658 date: * (glob)
652 659 files:
653 660 M a
654 661 b
655 662 R b
656 663
657 664 $ hg log -GTstatus -r 'wdir()' -C
658 665 o changeset: 2147483647:ffffffffffff
659 666 | parent: 0:8c55c58b4c0e
660 667 ~ user: test
661 668 date: * (glob)
662 669 files:
663 670 M a
664 671 b
665 672 R b
666 673
667 674
668 675 Other "bug" highlight, the revision status does not report the copy information.
669 676 This is buggy behavior.
670 677
671 678 $ hg commit -m 'blah'
672 679 $ hg st --copies --change .
673 680 M a
674 681 R b
675 682
676 683 using log status template, the copy information is displayed correctly.
677 684 $ hg log -Tstatus -r. -C
678 685 changeset: 1:6685fde43d21
679 686 tag: tip
680 687 user: test
681 688 date: * (glob)
682 689 summary: blah
683 690 files:
684 691 M a
685 692 b
686 693 R b
687 694
688 695
689 696 $ cd ..
690 697
691 698 Make sure .hg doesn't show up even as a symlink
692 699
693 700 $ hg init repo0
694 701 $ mkdir symlink-repo0
695 702 $ cd symlink-repo0
696 703 $ ln -s ../repo0/.hg
697 704 $ hg status
698 705
699 706 If the size hasnt changed but mtime has, status needs to read the contents
700 707 of the file to check whether it has changed
701 708
702 709 $ echo 1 > a
703 710 $ echo 1 > b
704 711 $ touch -t 200102030000 a b
705 712 $ hg commit -Aqm '#0'
706 713 $ echo 2 > a
707 714 $ touch -t 200102040000 a b
708 715 $ hg status
709 716 M a
710 717
711 718 Asking specifically for the status of a deleted/removed file
712 719
713 720 $ rm a
714 721 $ rm b
715 722 $ hg status a
716 723 ! a
717 724 $ hg rm a
718 725 $ hg rm b
719 726 $ hg status a
720 727 R a
721 728 $ hg commit -qm '#1'
722 729 $ hg status a
723 730 a: $ENOENT$
724 731
725 732 Check using include flag with pattern when status does not need to traverse
726 733 the working directory (issue6483)
727 734
728 735 $ cd ..
729 736 $ hg init issue6483
730 737 $ cd issue6483
731 738 $ touch a.py b.rs
732 739 $ hg add a.py b.rs
733 740 $ hg st -aI "*.py"
734 741 A a.py
735 742
736 743 Also check exclude pattern
737 744
738 745 $ hg st -aX "*.rs"
739 746 A a.py
740 747
741 748 issue6335
742 749 When a directory containing a tracked file gets symlinked, as of 5.8
743 750 `hg st` only gives the correct answer about clean (or deleted) files
744 751 if also listing unknowns.
745 752 The tree-based dirstate and status algorithm fix this:
746 753
747 754 #if symlink no-dirstate-v1 rust
748 755
749 756 $ cd ..
750 757 $ hg init issue6335
751 758 $ cd issue6335
752 759 $ mkdir foo
753 760 $ touch foo/a
754 761 $ hg ci -Ama
755 762 adding foo/a
756 763 $ mv foo bar
757 764 $ ln -s bar foo
758 765 $ hg status
759 766 ! foo/a
760 767 ? bar/a
761 768 ? foo
762 769
763 770 $ hg status -c # incorrect output without the Rust implementation
764 771 $ hg status -cu
765 772 ? bar/a
766 773 ? foo
767 774 $ hg status -d # incorrect output without the Rust implementation
768 775 ! foo/a
769 776 $ hg status -du
770 777 ! foo/a
771 778 ? bar/a
772 779 ? foo
773 780
774 781 #endif
775 782
776 783
777 784 Create a repo with files in each possible status
778 785
779 786 $ cd ..
780 787 $ hg init repo7
781 788 $ cd repo7
782 789 $ mkdir subdir
783 790 $ touch clean modified deleted removed
784 791 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
785 792 $ echo ignored > .hgignore
786 793 $ hg ci -Aqm '#0'
787 794 $ echo 1 > modified
788 795 $ echo 1 > subdir/modified
789 796 $ rm deleted
790 797 $ rm subdir/deleted
791 798 $ hg rm removed
792 799 $ hg rm subdir/removed
793 800 $ touch unknown ignored
794 801 $ touch subdir/unknown subdir/ignored
795 802
796 803 Check the output
797 804
798 805 $ hg status
799 806 M modified
800 807 M subdir/modified
801 808 R removed
802 809 R subdir/removed
803 810 ! deleted
804 811 ! subdir/deleted
805 812 ? subdir/unknown
806 813 ? unknown
807 814
808 815 $ hg status -mard
809 816 M modified
810 817 M subdir/modified
811 818 R removed
812 819 R subdir/removed
813 820 ! deleted
814 821 ! subdir/deleted
815 822
816 823 $ hg status -A
817 824 M modified
818 825 M subdir/modified
819 826 R removed
820 827 R subdir/removed
821 828 ! deleted
822 829 ! subdir/deleted
823 830 ? subdir/unknown
824 831 ? unknown
825 832 I ignored
826 833 I subdir/ignored
827 834 C .hgignore
828 835 C clean
829 836 C subdir/clean
830 837
831 838 Note: `hg status some-name` creates a patternmatcher which is not supported
832 839 yet by the Rust implementation of status, but includematcher is supported.
833 840 --include is used below for that reason
834 841
835 842 #if unix-permissions
836 843
837 844 Not having permission to read a directory that contains tracked files makes
838 845 status emit a warning then behave as if the directory was empty or removed
839 846 entirely:
840 847
841 848 $ chmod 0 subdir
842 849 $ hg status --include subdir
843 850 subdir: Permission denied
844 851 R subdir/removed
845 852 ! subdir/clean
846 853 ! subdir/deleted
847 854 ! subdir/modified
848 855 $ chmod 755 subdir
849 856
850 857 #endif
851 858
852 859 Remove a directory that contains tracked files
853 860
854 861 $ rm -r subdir
855 862 $ hg status --include subdir
856 863 R subdir/removed
857 864 ! subdir/clean
858 865 ! subdir/deleted
859 866 ! subdir/modified
860 867
861 868 and replace it by a file
862 869
863 870 $ touch subdir
864 871 $ hg status --include subdir
865 872 R subdir/removed
866 873 ! subdir/clean
867 874 ! subdir/deleted
868 875 ! subdir/modified
869 876 ? subdir
870 877
871 878 Replaced a deleted or removed file with a directory
872 879
873 880 $ mkdir deleted removed
874 881 $ touch deleted/1 removed/1
875 882 $ hg status --include deleted --include removed
876 883 R removed
877 884 ! deleted
878 885 ? deleted/1
879 886 ? removed/1
880 887 $ hg add removed/1
881 888 $ hg status --include deleted --include removed
882 889 A removed/1
883 890 R removed
884 891 ! deleted
885 892 ? deleted/1
886 893
887 894 Deeply nested files in an ignored directory are still listed on request
888 895
889 896 $ echo ignored-dir >> .hgignore
890 897 $ mkdir ignored-dir
891 898 $ mkdir ignored-dir/subdir
892 899 $ touch ignored-dir/subdir/1
893 900 $ hg status --ignored
894 901 I ignored
895 902 I ignored-dir/subdir/1
896 903
897 904 Check using include flag while listing ignored composes correctly (issue6514)
898 905
899 906 $ cd ..
900 907 $ hg init issue6514
901 908 $ cd issue6514
902 909 $ mkdir ignored-folder
903 910 $ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
904 911 $ cat >.hgignore <<EOF
905 912 > A.hs
906 913 > B.hs
907 914 > ignored-folder/
908 915 > EOF
909 916 $ hg st -i -I 're:.*\.hs$'
910 917 I A.hs
911 918 I B.hs
912 919 I ignored-folder/ctest.hs
913 920
914 921 #if rust dirstate-v2
915 922
916 923 Check read_dir caching
917 924
918 925 $ cd ..
919 926 $ hg init repo8
920 927 $ cd repo8
921 928 $ mkdir subdir
922 929 $ touch subdir/a subdir/b
923 930 $ hg ci -Aqm '#0'
924 931
925 932 The cached mtime is initially unset
926 933
927 934 $ hg debugdirstate --all --no-dates | grep '^ '
928 935 0 -1 unset subdir
929 936
930 937 It is still not set when there are unknown files
931 938
932 939 $ touch subdir/unknown
933 940 $ hg status
934 941 ? subdir/unknown
935 942 $ hg debugdirstate --all --no-dates | grep '^ '
936 943 0 -1 unset subdir
937 944
938 945 Now the directory is eligible for caching, so its mtime is save in the dirstate
939 946
940 947 $ rm subdir/unknown
941 948 $ hg status
942 949 $ hg debugdirstate --all --no-dates | grep '^ '
943 950 0 -1 set subdir
944 951
945 952 This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
946 953
947 954 $ hg status
948 955
949 956 Creating a new file changes the directorys mtime, invalidating the cache
950 957
951 958 $ touch subdir/unknown
952 959 $ hg status
953 960 ? subdir/unknown
954 961
955 962 $ rm subdir/unknown
956 963 $ hg status
957 964
958 965 Removing a node from the dirstate resets the cache for its parent directory
959 966
960 967 $ hg forget subdir/a
961 968 $ hg debugdirstate --all --no-dates | grep '^ '
962 969 0 -1 set subdir
963 970 $ hg ci -qm '#1'
964 971 $ hg debugdirstate --all --no-dates | grep '^ '
965 972 0 -1 unset subdir
966 973 $ hg status
967 974 ? subdir/a
968 975
969 976 #endif
General Comments 0
You need to be logged in to leave comments. Login now