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