##// END OF EJS Templates
rhg: Colorize `rhg status` output when appropriate...
Simon Sapin -
r49585:3e2b4bb2 default
parent child Browse files
Show More
@@ -1,539 +1,539 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::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::dirstate::RANGE_MASK_31BIT;
18 use hg::dirstate::RANGE_MASK_31BIT;
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;
22 use hg::matchers::AlwaysMatcher;
23 use hg::repo::Repo;
23 use hg::repo::Repo;
24 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_os_string;
25 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_bytes_from_path;
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::StatusOptions;
28 use hg::StatusOptions;
29 use log::info;
29 use log::info;
30 use std::io;
30 use std::io;
31 use std::path::PathBuf;
31 use std::path::PathBuf;
32
32
33 pub const HELP_TEXT: &str = "
33 pub const HELP_TEXT: &str = "
34 Show changed files in the working directory
34 Show changed files in the working directory
35
35
36 This is a pure Rust version of `hg status`.
36 This is a pure Rust version of `hg status`.
37
37
38 Some options might be missing, check the list below.
38 Some options might be missing, check the list below.
39 ";
39 ";
40
40
41 pub fn args() -> clap::App<'static, 'static> {
41 pub fn args() -> clap::App<'static, 'static> {
42 SubCommand::with_name("status")
42 SubCommand::with_name("status")
43 .alias("st")
43 .alias("st")
44 .about(HELP_TEXT)
44 .about(HELP_TEXT)
45 .arg(
45 .arg(
46 Arg::with_name("all")
46 Arg::with_name("all")
47 .help("show status of all files")
47 .help("show status of all files")
48 .short("-A")
48 .short("-A")
49 .long("--all"),
49 .long("--all"),
50 )
50 )
51 .arg(
51 .arg(
52 Arg::with_name("modified")
52 Arg::with_name("modified")
53 .help("show only modified files")
53 .help("show only modified files")
54 .short("-m")
54 .short("-m")
55 .long("--modified"),
55 .long("--modified"),
56 )
56 )
57 .arg(
57 .arg(
58 Arg::with_name("added")
58 Arg::with_name("added")
59 .help("show only added files")
59 .help("show only added files")
60 .short("-a")
60 .short("-a")
61 .long("--added"),
61 .long("--added"),
62 )
62 )
63 .arg(
63 .arg(
64 Arg::with_name("removed")
64 Arg::with_name("removed")
65 .help("show only removed files")
65 .help("show only removed files")
66 .short("-r")
66 .short("-r")
67 .long("--removed"),
67 .long("--removed"),
68 )
68 )
69 .arg(
69 .arg(
70 Arg::with_name("clean")
70 Arg::with_name("clean")
71 .help("show only clean files")
71 .help("show only clean files")
72 .short("-c")
72 .short("-c")
73 .long("--clean"),
73 .long("--clean"),
74 )
74 )
75 .arg(
75 .arg(
76 Arg::with_name("deleted")
76 Arg::with_name("deleted")
77 .help("show only deleted files")
77 .help("show only deleted files")
78 .short("-d")
78 .short("-d")
79 .long("--deleted"),
79 .long("--deleted"),
80 )
80 )
81 .arg(
81 .arg(
82 Arg::with_name("unknown")
82 Arg::with_name("unknown")
83 .help("show only unknown (not tracked) files")
83 .help("show only unknown (not tracked) files")
84 .short("-u")
84 .short("-u")
85 .long("--unknown"),
85 .long("--unknown"),
86 )
86 )
87 .arg(
87 .arg(
88 Arg::with_name("ignored")
88 Arg::with_name("ignored")
89 .help("show only ignored files")
89 .help("show only ignored files")
90 .short("-i")
90 .short("-i")
91 .long("--ignored"),
91 .long("--ignored"),
92 )
92 )
93 .arg(
93 .arg(
94 Arg::with_name("copies")
94 Arg::with_name("copies")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 .short("-C")
96 .short("-C")
97 .long("--copies"),
97 .long("--copies"),
98 )
98 )
99 .arg(
99 .arg(
100 Arg::with_name("no-status")
100 Arg::with_name("no-status")
101 .help("hide status prefix")
101 .help("hide status prefix")
102 .short("-n")
102 .short("-n")
103 .long("--no-status"),
103 .long("--no-status"),
104 )
104 )
105 }
105 }
106
106
107 /// Pure data type allowing the caller to specify file states to display
107 /// Pure data type allowing the caller to specify file states to display
108 #[derive(Copy, Clone, Debug)]
108 #[derive(Copy, Clone, Debug)]
109 pub struct DisplayStates {
109 pub struct DisplayStates {
110 pub modified: bool,
110 pub modified: bool,
111 pub added: bool,
111 pub added: bool,
112 pub removed: bool,
112 pub removed: bool,
113 pub clean: bool,
113 pub clean: bool,
114 pub deleted: bool,
114 pub deleted: bool,
115 pub unknown: bool,
115 pub unknown: bool,
116 pub ignored: bool,
116 pub ignored: bool,
117 }
117 }
118
118
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 modified: true,
120 modified: true,
121 added: true,
121 added: true,
122 removed: true,
122 removed: true,
123 clean: false,
123 clean: false,
124 deleted: true,
124 deleted: true,
125 unknown: true,
125 unknown: true,
126 ignored: false,
126 ignored: false,
127 };
127 };
128
128
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 modified: true,
130 modified: true,
131 added: true,
131 added: true,
132 removed: true,
132 removed: true,
133 clean: true,
133 clean: true,
134 deleted: true,
134 deleted: true,
135 unknown: true,
135 unknown: true,
136 ignored: true,
136 ignored: true,
137 };
137 };
138
138
139 impl DisplayStates {
139 impl DisplayStates {
140 pub fn is_empty(&self) -> bool {
140 pub fn is_empty(&self) -> bool {
141 !(self.modified
141 !(self.modified
142 || self.added
142 || self.added
143 || self.removed
143 || self.removed
144 || self.clean
144 || self.clean
145 || self.deleted
145 || self.deleted
146 || self.unknown
146 || self.unknown
147 || self.ignored)
147 || self.ignored)
148 }
148 }
149 }
149 }
150
150
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 let status_enabled_default = false;
152 let status_enabled_default = false;
153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
154 if !status_enabled.unwrap_or(status_enabled_default) {
154 if !status_enabled.unwrap_or(status_enabled_default) {
155 return Err(CommandError::unsupported(
155 return Err(CommandError::unsupported(
156 "status is experimental in rhg (enable it with 'rhg.status = true' \
156 "status is experimental in rhg (enable it with 'rhg.status = true' \
157 or enable fallback with 'rhg.on-unsupported = fallback')"
157 or enable fallback with 'rhg.on-unsupported = fallback')"
158 ));
158 ));
159 }
159 }
160
160
161 // TODO: lift these limitations
161 // TODO: lift these limitations
162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
163 return Err(CommandError::unsupported(
163 return Err(CommandError::unsupported(
164 "ui.tweakdefaults is not yet supported with rhg status",
164 "ui.tweakdefaults is not yet supported with rhg status",
165 ));
165 ));
166 }
166 }
167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
168 return Err(CommandError::unsupported(
168 return Err(CommandError::unsupported(
169 "ui.statuscopies is not yet supported with rhg status",
169 "ui.statuscopies is not yet supported with rhg status",
170 ));
170 ));
171 }
171 }
172 if invocation
172 if invocation
173 .config
173 .config
174 .get(b"commands", b"status.terse")
174 .get(b"commands", b"status.terse")
175 .is_some()
175 .is_some()
176 {
176 {
177 return Err(CommandError::unsupported(
177 return Err(CommandError::unsupported(
178 "status.terse is not yet supported with rhg status",
178 "status.terse is not yet supported with rhg status",
179 ));
179 ));
180 }
180 }
181
181
182 let ui = invocation.ui;
182 let ui = invocation.ui;
183 let config = invocation.config;
183 let config = invocation.config;
184 let args = invocation.subcommand_args;
184 let args = invocation.subcommand_args;
185
185
186 let verbose = !ui.plain(None)
186 let verbose = !ui.plain(None)
187 && !args.is_present("print0")
187 && !args.is_present("print0")
188 && (config.get_bool(b"ui", b"verbose")?
188 && (config.get_bool(b"ui", b"verbose")?
189 || config.get_bool(b"commands", b"status.verbose")?);
189 || config.get_bool(b"commands", b"status.verbose")?);
190 if verbose {
190 if verbose {
191 return Err(CommandError::unsupported(
191 return Err(CommandError::unsupported(
192 "verbose status is not supported yet",
192 "verbose status is not supported yet",
193 ));
193 ));
194 }
194 }
195
195
196 let all = args.is_present("all");
196 let all = args.is_present("all");
197 let display_states = if all {
197 let display_states = if all {
198 // TODO when implementing `--quiet`: it excludes clean files
198 // TODO when implementing `--quiet`: it excludes clean files
199 // from `--all`
199 // from `--all`
200 ALL_DISPLAY_STATES
200 ALL_DISPLAY_STATES
201 } else {
201 } else {
202 let requested = DisplayStates {
202 let requested = DisplayStates {
203 modified: args.is_present("modified"),
203 modified: args.is_present("modified"),
204 added: args.is_present("added"),
204 added: args.is_present("added"),
205 removed: args.is_present("removed"),
205 removed: args.is_present("removed"),
206 clean: args.is_present("clean"),
206 clean: args.is_present("clean"),
207 deleted: args.is_present("deleted"),
207 deleted: args.is_present("deleted"),
208 unknown: args.is_present("unknown"),
208 unknown: args.is_present("unknown"),
209 ignored: args.is_present("ignored"),
209 ignored: args.is_present("ignored"),
210 };
210 };
211 if requested.is_empty() {
211 if requested.is_empty() {
212 DEFAULT_DISPLAY_STATES
212 DEFAULT_DISPLAY_STATES
213 } else {
213 } else {
214 requested
214 requested
215 }
215 }
216 };
216 };
217 let no_status = args.is_present("no-status");
217 let no_status = args.is_present("no-status");
218 let list_copies = all
218 let list_copies = all
219 || args.is_present("copies")
219 || args.is_present("copies")
220 || config.get_bool(b"ui", b"statuscopies")?;
220 || config.get_bool(b"ui", b"statuscopies")?;
221
221
222 let repo = invocation.repo?;
222 let repo = invocation.repo?;
223
223
224 if repo.has_sparse() || repo.has_narrow() {
224 if repo.has_sparse() || repo.has_narrow() {
225 return Err(CommandError::unsupported(
225 return Err(CommandError::unsupported(
226 "rhg status is not supported for sparse checkouts or narrow clones yet"
226 "rhg status is not supported for sparse checkouts or narrow clones yet"
227 ));
227 ));
228 }
228 }
229
229
230 let mut dmap = repo.dirstate_map_mut()?;
230 let mut dmap = repo.dirstate_map_mut()?;
231
231
232 let options = StatusOptions {
232 let options = StatusOptions {
233 // we're currently supporting file systems with exec flags only
233 // we're currently supporting file systems with exec flags only
234 // anyway
234 // anyway
235 check_exec: true,
235 check_exec: true,
236 list_clean: display_states.clean,
236 list_clean: display_states.clean,
237 list_unknown: display_states.unknown,
237 list_unknown: display_states.unknown,
238 list_ignored: display_states.ignored,
238 list_ignored: display_states.ignored,
239 list_copies,
239 list_copies,
240 collect_traversed_dirs: false,
240 collect_traversed_dirs: false,
241 };
241 };
242 let (mut ds_status, pattern_warnings) = dmap.status(
242 let (mut ds_status, pattern_warnings) = dmap.status(
243 &AlwaysMatcher,
243 &AlwaysMatcher,
244 repo.working_directory_path().to_owned(),
244 repo.working_directory_path().to_owned(),
245 ignore_files(repo, config),
245 ignore_files(repo, config),
246 options,
246 options,
247 )?;
247 )?;
248 for warning in pattern_warnings {
248 for warning in pattern_warnings {
249 match warning {
249 match warning {
250 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
250 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
251 .write_stderr(&format_bytes!(
251 .write_stderr(&format_bytes!(
252 b"{}: ignoring invalid syntax '{}'\n",
252 b"{}: ignoring invalid syntax '{}'\n",
253 get_bytes_from_path(path),
253 get_bytes_from_path(path),
254 &*syntax
254 &*syntax
255 ))?,
255 ))?,
256 hg::PatternFileWarning::NoSuchFile(path) => {
256 hg::PatternFileWarning::NoSuchFile(path) => {
257 let path = if let Ok(relative) =
257 let path = if let Ok(relative) =
258 path.strip_prefix(repo.working_directory_path())
258 path.strip_prefix(repo.working_directory_path())
259 {
259 {
260 relative
260 relative
261 } else {
261 } else {
262 &*path
262 &*path
263 };
263 };
264 ui.write_stderr(&format_bytes!(
264 ui.write_stderr(&format_bytes!(
265 b"skipping unreadable pattern file '{}': \
265 b"skipping unreadable pattern file '{}': \
266 No such file or directory\n",
266 No such file or directory\n",
267 get_bytes_from_path(path),
267 get_bytes_from_path(path),
268 ))?
268 ))?
269 }
269 }
270 }
270 }
271 }
271 }
272
272
273 for (path, error) in ds_status.bad {
273 for (path, error) in ds_status.bad {
274 let error = match error {
274 let error = match error {
275 hg::BadMatch::OsError(code) => {
275 hg::BadMatch::OsError(code) => {
276 std::io::Error::from_raw_os_error(code).to_string()
276 std::io::Error::from_raw_os_error(code).to_string()
277 }
277 }
278 hg::BadMatch::BadType(ty) => {
278 hg::BadMatch::BadType(ty) => {
279 format!("unsupported file type (type is {})", ty)
279 format!("unsupported file type (type is {})", ty)
280 }
280 }
281 };
281 };
282 ui.write_stderr(&format_bytes!(
282 ui.write_stderr(&format_bytes!(
283 b"{}: {}\n",
283 b"{}: {}\n",
284 path.as_bytes(),
284 path.as_bytes(),
285 error.as_bytes()
285 error.as_bytes()
286 ))?
286 ))?
287 }
287 }
288 if !ds_status.unsure.is_empty() {
288 if !ds_status.unsure.is_empty() {
289 info!(
289 info!(
290 "Files to be rechecked by retrieval from filelog: {:?}",
290 "Files to be rechecked by retrieval from filelog: {:?}",
291 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
291 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
292 );
292 );
293 }
293 }
294 let mut fixup = Vec::new();
294 let mut fixup = Vec::new();
295 if !ds_status.unsure.is_empty()
295 if !ds_status.unsure.is_empty()
296 && (display_states.modified || display_states.clean)
296 && (display_states.modified || display_states.clean)
297 {
297 {
298 let p1 = repo.dirstate_parents()?.p1;
298 let p1 = repo.dirstate_parents()?.p1;
299 let manifest = repo.manifest_for_node(p1).map_err(|e| {
299 let manifest = repo.manifest_for_node(p1).map_err(|e| {
300 CommandError::from((e, &*format!("{:x}", p1.short())))
300 CommandError::from((e, &*format!("{:x}", p1.short())))
301 })?;
301 })?;
302 for to_check in ds_status.unsure {
302 for to_check in ds_status.unsure {
303 if unsure_is_modified(repo, &manifest, &to_check.path)? {
303 if unsure_is_modified(repo, &manifest, &to_check.path)? {
304 if display_states.modified {
304 if display_states.modified {
305 ds_status.modified.push(to_check);
305 ds_status.modified.push(to_check);
306 }
306 }
307 } else {
307 } else {
308 if display_states.clean {
308 if display_states.clean {
309 ds_status.clean.push(to_check.clone());
309 ds_status.clean.push(to_check.clone());
310 }
310 }
311 fixup.push(to_check.path.into_owned())
311 fixup.push(to_check.path.into_owned())
312 }
312 }
313 }
313 }
314 }
314 }
315 let relative_paths = (!ui.plain(None))
315 let relative_paths = (!ui.plain(None))
316 && config
316 && config
317 .get_option(b"commands", b"status.relative")?
317 .get_option(b"commands", b"status.relative")?
318 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
318 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
319 let output = DisplayStatusPaths {
319 let output = DisplayStatusPaths {
320 ui,
320 ui,
321 no_status,
321 no_status,
322 relativize: if relative_paths {
322 relativize: if relative_paths {
323 Some(RelativizePaths::new(repo)?)
323 Some(RelativizePaths::new(repo)?)
324 } else {
324 } else {
325 None
325 None
326 },
326 },
327 };
327 };
328 if display_states.modified {
328 if display_states.modified {
329 output.display(b"M", ds_status.modified)?;
329 output.display(b"M ", "status.modified", ds_status.modified)?;
330 }
330 }
331 if display_states.added {
331 if display_states.added {
332 output.display(b"A", ds_status.added)?;
332 output.display(b"A ", "status.added", ds_status.added)?;
333 }
333 }
334 if display_states.removed {
334 if display_states.removed {
335 output.display(b"R", ds_status.removed)?;
335 output.display(b"R ", "status.removed", ds_status.removed)?;
336 }
336 }
337 if display_states.deleted {
337 if display_states.deleted {
338 output.display(b"!", ds_status.deleted)?;
338 output.display(b"! ", "status.deleted", ds_status.deleted)?;
339 }
339 }
340 if display_states.unknown {
340 if display_states.unknown {
341 output.display(b"?", ds_status.unknown)?;
341 output.display(b"? ", "status.unknown", ds_status.unknown)?;
342 }
342 }
343 if display_states.ignored {
343 if display_states.ignored {
344 output.display(b"I", ds_status.ignored)?;
344 output.display(b"I ", "status.ignored", ds_status.ignored)?;
345 }
345 }
346 if display_states.clean {
346 if display_states.clean {
347 output.display(b"C", ds_status.clean)?;
347 output.display(b"C ", "status.clean", ds_status.clean)?;
348 }
348 }
349
349
350 let mut dirstate_write_needed = ds_status.dirty;
350 let mut dirstate_write_needed = ds_status.dirty;
351 let filesystem_time_at_status_start =
351 let filesystem_time_at_status_start =
352 ds_status.filesystem_time_at_status_start;
352 ds_status.filesystem_time_at_status_start;
353
353
354 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
354 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
355 && !dirstate_write_needed
355 && !dirstate_write_needed
356 {
356 {
357 // Nothing to update
357 // Nothing to update
358 return Ok(());
358 return Ok(());
359 }
359 }
360
360
361 // Update the dirstate on disk if we can
361 // Update the dirstate on disk if we can
362 let with_lock_result =
362 let with_lock_result =
363 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
363 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
364 if let Some(mtime_boundary) = filesystem_time_at_status_start {
364 if let Some(mtime_boundary) = filesystem_time_at_status_start {
365 for hg_path in fixup {
365 for hg_path in fixup {
366 use std::os::unix::fs::MetadataExt;
366 use std::os::unix::fs::MetadataExt;
367 let fs_path = hg_path_to_path_buf(&hg_path)
367 let fs_path = hg_path_to_path_buf(&hg_path)
368 .expect("HgPath conversion");
368 .expect("HgPath conversion");
369 // Specifically do not reuse `fs_metadata` from
369 // Specifically do not reuse `fs_metadata` from
370 // `unsure_is_clean` which was needed before reading
370 // `unsure_is_clean` which was needed before reading
371 // contents. Here we access metadata again after reading
371 // contents. Here we access metadata again after reading
372 // content, in case it changed in the meantime.
372 // content, in case it changed in the meantime.
373 let fs_metadata = repo
373 let fs_metadata = repo
374 .working_directory_vfs()
374 .working_directory_vfs()
375 .symlink_metadata(&fs_path)?;
375 .symlink_metadata(&fs_path)?;
376 if let Some(mtime) =
376 if let Some(mtime) =
377 TruncatedTimestamp::for_reliable_mtime_of(
377 TruncatedTimestamp::for_reliable_mtime_of(
378 &fs_metadata,
378 &fs_metadata,
379 &mtime_boundary,
379 &mtime_boundary,
380 )
380 )
381 .when_reading_file(&fs_path)?
381 .when_reading_file(&fs_path)?
382 {
382 {
383 let mode = fs_metadata.mode();
383 let mode = fs_metadata.mode();
384 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
384 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
385 let mut entry = dmap
385 let mut entry = dmap
386 .get(&hg_path)?
386 .get(&hg_path)?
387 .expect("ambiguous file not in dirstate");
387 .expect("ambiguous file not in dirstate");
388 entry.set_clean(mode, size, mtime);
388 entry.set_clean(mode, size, mtime);
389 dmap.add_file(&hg_path, entry)?;
389 dmap.add_file(&hg_path, entry)?;
390 dirstate_write_needed = true
390 dirstate_write_needed = true
391 }
391 }
392 }
392 }
393 }
393 }
394 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
394 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
395 if dirstate_write_needed {
395 if dirstate_write_needed {
396 repo.write_dirstate()?
396 repo.write_dirstate()?
397 }
397 }
398 Ok(())
398 Ok(())
399 });
399 });
400 match with_lock_result {
400 match with_lock_result {
401 Ok(closure_result) => closure_result?,
401 Ok(closure_result) => closure_result?,
402 Err(LockError::AlreadyHeld) => {
402 Err(LockError::AlreadyHeld) => {
403 // Not updating the dirstate is not ideal but not critical:
403 // Not updating the dirstate is not ideal but not critical:
404 // don’t keep our caller waiting until some other Mercurial
404 // don’t keep our caller waiting until some other Mercurial
405 // process releases the lock.
405 // process releases the lock.
406 }
406 }
407 Err(LockError::Other(HgError::IoError { error, .. }))
407 Err(LockError::Other(HgError::IoError { error, .. }))
408 if error.kind() == io::ErrorKind::PermissionDenied =>
408 if error.kind() == io::ErrorKind::PermissionDenied =>
409 {
409 {
410 // `hg status` on a read-only repository is fine
410 // `hg status` on a read-only repository is fine
411 }
411 }
412 Err(LockError::Other(error)) => {
412 Err(LockError::Other(error)) => {
413 // Report other I/O errors
413 // Report other I/O errors
414 Err(error)?
414 Err(error)?
415 }
415 }
416 }
416 }
417 Ok(())
417 Ok(())
418 }
418 }
419
419
420 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
420 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
421 let mut ignore_files = Vec::new();
421 let mut ignore_files = Vec::new();
422 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
422 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
423 if repo_ignore.exists() {
423 if repo_ignore.exists() {
424 ignore_files.push(repo_ignore)
424 ignore_files.push(repo_ignore)
425 }
425 }
426 for (key, value) in config.iter_section(b"ui") {
426 for (key, value) in config.iter_section(b"ui") {
427 if key == b"ignore" || key.starts_with(b"ignore.") {
427 if key == b"ignore" || key.starts_with(b"ignore.") {
428 let path = get_path_from_bytes(value);
428 let path = get_path_from_bytes(value);
429 // TODO:Β expand "~/" and environment variable here, like Python
429 // TODO:Β expand "~/" and environment variable here, like Python
430 // does with `os.path.expanduser` and `os.path.expandvars`
430 // does with `os.path.expanduser` and `os.path.expandvars`
431
431
432 let joined = repo.working_directory_path().join(path);
432 let joined = repo.working_directory_path().join(path);
433 ignore_files.push(joined);
433 ignore_files.push(joined);
434 }
434 }
435 }
435 }
436 ignore_files
436 ignore_files
437 }
437 }
438
438
439 struct DisplayStatusPaths<'a> {
439 struct DisplayStatusPaths<'a> {
440 ui: &'a Ui,
440 ui: &'a Ui,
441 no_status: bool,
441 no_status: bool,
442 relativize: Option<RelativizePaths>,
442 relativize: Option<RelativizePaths>,
443 }
443 }
444
444
445 impl DisplayStatusPaths<'_> {
445 impl DisplayStatusPaths<'_> {
446 // Probably more elegant to use a Deref or Borrow trait rather than
446 // Probably more elegant to use a Deref or Borrow trait rather than
447 // harcode HgPathBuf, but probably not really useful at this point
447 // harcode HgPathBuf, but probably not really useful at this point
448 fn display(
448 fn display(
449 &self,
449 &self,
450 status_prefix: &[u8],
450 status_prefix: &[u8],
451 label: &'static str,
451 mut paths: Vec<StatusPath<'_>>,
452 mut paths: Vec<StatusPath<'_>>,
452 ) -> Result<(), CommandError> {
453 ) -> Result<(), CommandError> {
453 paths.sort_unstable();
454 paths.sort_unstable();
455 // TODO:Β get the stdout lock once for the whole loop instead of in each write
454 for StatusPath { path, copy_source } in paths {
456 for StatusPath { path, copy_source } in paths {
455 let relative;
457 let relative;
456 let path = if let Some(relativize) = &self.relativize {
458 let path = if let Some(relativize) = &self.relativize {
457 relative = relativize.relativize(&path);
459 relative = relativize.relativize(&path);
458 &*relative
460 &*relative
459 } else {
461 } else {
460 path.as_bytes()
462 path.as_bytes()
461 };
463 };
462 // TODO optim, probably lots of unneeded copies here, especially
464 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
463 // if out stream is buffered
465 // in order to stream to stdout instead of allocating an
464 if self.no_status {
466 // itermediate `Vec<u8>`.
465 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
467 if !self.no_status {
466 } else {
468 self.ui.write_stdout_labelled(status_prefix, label)?
467 self.ui.write_stdout(&format_bytes!(
468 b"{} {}\n",
469 status_prefix,
470 path
471 ))?
472 }
469 }
470 self.ui
471 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
473 if let Some(source) = copy_source {
472 if let Some(source) = copy_source {
474 self.ui.write_stdout(&format_bytes!(
473 let label = "status.copied";
475 b" {}\n",
474 self.ui.write_stdout_labelled(
476 source.as_bytes()
475 &format_bytes!(b" {}\n", source.as_bytes()),
477 ))?
476 label,
477 )?
478 }
478 }
479 }
479 }
480 Ok(())
480 Ok(())
481 }
481 }
482 }
482 }
483
483
484 /// Check if a file is modified by comparing actual repo store and file system.
484 /// Check if a file is modified by comparing actual repo store and file system.
485 ///
485 ///
486 /// This meant to be used for those that the dirstate cannot resolve, due
486 /// This meant to be used for those that the dirstate cannot resolve, due
487 /// to time resolution limits.
487 /// to time resolution limits.
488 fn unsure_is_modified(
488 fn unsure_is_modified(
489 repo: &Repo,
489 repo: &Repo,
490 manifest: &Manifest,
490 manifest: &Manifest,
491 hg_path: &HgPath,
491 hg_path: &HgPath,
492 ) -> Result<bool, HgError> {
492 ) -> Result<bool, HgError> {
493 let vfs = repo.working_directory_vfs();
493 let vfs = repo.working_directory_vfs();
494 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
494 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
495 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
495 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
496 let is_symlink = fs_metadata.file_type().is_symlink();
496 let is_symlink = fs_metadata.file_type().is_symlink();
497 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
497 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
498 // dirstate
498 // dirstate
499 let fs_flags = if is_symlink {
499 let fs_flags = if is_symlink {
500 Some(b'l')
500 Some(b'l')
501 } else if has_exec_bit(&fs_metadata) {
501 } else if has_exec_bit(&fs_metadata) {
502 Some(b'x')
502 Some(b'x')
503 } else {
503 } else {
504 None
504 None
505 };
505 };
506
506
507 let entry = manifest
507 let entry = manifest
508 .find_by_path(hg_path)?
508 .find_by_path(hg_path)?
509 .expect("ambgious file not in p1");
509 .expect("ambgious file not in p1");
510 if entry.flags != fs_flags {
510 if entry.flags != fs_flags {
511 return Ok(true);
511 return Ok(true);
512 }
512 }
513 let filelog = repo.filelog(hg_path)?;
513 let filelog = repo.filelog(hg_path)?;
514 let fs_len = fs_metadata.len();
514 let fs_len = fs_metadata.len();
515 let filelog_entry =
515 let filelog_entry =
516 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
516 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
517 HgError::corrupted("filelog missing node from manifest")
517 HgError::corrupted("filelog missing node from manifest")
518 })?;
518 })?;
519 if filelog_entry.file_data_len_not_equal_to(fs_len) {
519 if filelog_entry.file_data_len_not_equal_to(fs_len) {
520 // No need to read file contents:
520 // No need to read file contents:
521 // it cannot be equal if it has a different length.
521 // it cannot be equal if it has a different length.
522 return Ok(true);
522 return Ok(true);
523 }
523 }
524
524
525 let p1_filelog_data = filelog_entry.data()?;
525 let p1_filelog_data = filelog_entry.data()?;
526 let p1_contents = p1_filelog_data.file_data()?;
526 let p1_contents = p1_filelog_data.file_data()?;
527 if p1_contents.len() as u64 != fs_len {
527 if p1_contents.len() as u64 != fs_len {
528 // No need to read file contents:
528 // No need to read file contents:
529 // it cannot be equal if it has a different length.
529 // it cannot be equal if it has a different length.
530 return Ok(true);
530 return Ok(true);
531 }
531 }
532
532
533 let fs_contents = if is_symlink {
533 let fs_contents = if is_symlink {
534 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
534 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
535 } else {
535 } else {
536 vfs.read(fs_path)?
536 vfs.read(fs_path)?
537 };
537 };
538 Ok(p1_contents != &*fs_contents)
538 Ok(p1_contents != &*fs_contents)
539 }
539 }
@@ -1,715 +1,706 b''
1 extern crate log;
1 extern crate log;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::Ui;
3 use crate::ui::Ui;
4 use clap::App;
4 use clap::App;
5 use clap::AppSettings;
5 use clap::AppSettings;
6 use clap::Arg;
6 use clap::Arg;
7 use clap::ArgMatches;
7 use clap::ArgMatches;
8 use format_bytes::{format_bytes, join};
8 use format_bytes::{format_bytes, join};
9 use hg::config::{Config, ConfigSource};
9 use hg::config::{Config, ConfigSource};
10 use hg::exit_codes;
10 use hg::exit_codes;
11 use hg::repo::{Repo, RepoError};
11 use hg::repo::{Repo, RepoError};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
13 use hg::utils::SliceExt;
13 use hg::utils::SliceExt;
14 use std::collections::HashSet;
14 use std::collections::HashSet;
15 use std::ffi::OsString;
15 use std::ffi::OsString;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17 use std::process::Command;
17 use std::process::Command;
18
18
19 mod blackbox;
19 mod blackbox;
20 mod color;
20 mod color;
21 mod error;
21 mod error;
22 mod ui;
22 mod ui;
23 pub mod utils {
23 pub mod utils {
24 pub mod path_utils;
24 pub mod path_utils;
25 }
25 }
26
26
27 fn main_with_result(
27 fn main_with_result(
28 process_start_time: &blackbox::ProcessStartTime,
28 process_start_time: &blackbox::ProcessStartTime,
29 ui: &ui::Ui,
29 ui: &ui::Ui,
30 repo: Result<&Repo, &NoRepoInCwdError>,
30 repo: Result<&Repo, &NoRepoInCwdError>,
31 config: &Config,
31 config: &Config,
32 ) -> Result<(), CommandError> {
32 ) -> Result<(), CommandError> {
33 check_unsupported(config, repo, ui)?;
33 check_unsupported(config, repo)?;
34
34
35 let app = App::new("rhg")
35 let app = App::new("rhg")
36 .global_setting(AppSettings::AllowInvalidUtf8)
36 .global_setting(AppSettings::AllowInvalidUtf8)
37 .global_setting(AppSettings::DisableVersion)
37 .global_setting(AppSettings::DisableVersion)
38 .setting(AppSettings::SubcommandRequired)
38 .setting(AppSettings::SubcommandRequired)
39 .setting(AppSettings::VersionlessSubcommands)
39 .setting(AppSettings::VersionlessSubcommands)
40 .arg(
40 .arg(
41 Arg::with_name("repository")
41 Arg::with_name("repository")
42 .help("repository root directory")
42 .help("repository root directory")
43 .short("-R")
43 .short("-R")
44 .long("--repository")
44 .long("--repository")
45 .value_name("REPO")
45 .value_name("REPO")
46 .takes_value(true)
46 .takes_value(true)
47 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
47 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
48 .global(true),
48 .global(true),
49 )
49 )
50 .arg(
50 .arg(
51 Arg::with_name("config")
51 Arg::with_name("config")
52 .help("set/override config option (use 'section.name=value')")
52 .help("set/override config option (use 'section.name=value')")
53 .long("--config")
53 .long("--config")
54 .value_name("CONFIG")
54 .value_name("CONFIG")
55 .takes_value(true)
55 .takes_value(true)
56 .global(true)
56 .global(true)
57 // Ok: `--config section.key1=val --config section.key2=val2`
57 // Ok: `--config section.key1=val --config section.key2=val2`
58 .multiple(true)
58 .multiple(true)
59 // Not ok: `--config section.key1=val section.key2=val2`
59 // Not ok: `--config section.key1=val section.key2=val2`
60 .number_of_values(1),
60 .number_of_values(1),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::with_name("cwd")
63 Arg::with_name("cwd")
64 .help("change working directory")
64 .help("change working directory")
65 .long("--cwd")
65 .long("--cwd")
66 .value_name("DIR")
66 .value_name("DIR")
67 .takes_value(true)
67 .takes_value(true)
68 .global(true),
68 .global(true),
69 )
69 )
70 .arg(
70 .arg(
71 Arg::with_name("color")
71 Arg::with_name("color")
72 .help("when to colorize (boolean, always, auto, never, or debug)")
72 .help("when to colorize (boolean, always, auto, never, or debug)")
73 .long("--color")
73 .long("--color")
74 .value_name("TYPE")
74 .value_name("TYPE")
75 .takes_value(true)
75 .takes_value(true)
76 .global(true),
76 .global(true),
77 )
77 )
78 .version("0.0.1");
78 .version("0.0.1");
79 let app = add_subcommand_args(app);
79 let app = add_subcommand_args(app);
80
80
81 let matches = app.clone().get_matches_safe()?;
81 let matches = app.clone().get_matches_safe()?;
82
82
83 let (subcommand_name, subcommand_matches) = matches.subcommand();
83 let (subcommand_name, subcommand_matches) = matches.subcommand();
84
84
85 // Mercurial allows users to define "defaults" for commands, fallback
85 // Mercurial allows users to define "defaults" for commands, fallback
86 // if a default is detected for the current command
86 // if a default is detected for the current command
87 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
87 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
88 if defaults?.is_some() {
88 if defaults?.is_some() {
89 let msg = "`defaults` config set";
89 let msg = "`defaults` config set";
90 return Err(CommandError::unsupported(msg));
90 return Err(CommandError::unsupported(msg));
91 }
91 }
92
92
93 for prefix in ["pre", "post", "fail"].iter() {
93 for prefix in ["pre", "post", "fail"].iter() {
94 // Mercurial allows users to define generic hooks for commands,
94 // Mercurial allows users to define generic hooks for commands,
95 // fallback if any are detected
95 // fallback if any are detected
96 let item = format!("{}-{}", prefix, subcommand_name);
96 let item = format!("{}-{}", prefix, subcommand_name);
97 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
97 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
98 if hook_for_command.is_some() {
98 if hook_for_command.is_some() {
99 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
99 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
100 return Err(CommandError::unsupported(msg));
100 return Err(CommandError::unsupported(msg));
101 }
101 }
102 }
102 }
103 let run = subcommand_run_fn(subcommand_name)
103 let run = subcommand_run_fn(subcommand_name)
104 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
104 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
105 let subcommand_args = subcommand_matches
105 let subcommand_args = subcommand_matches
106 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
106 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
107
107
108 let invocation = CliInvocation {
108 let invocation = CliInvocation {
109 ui,
109 ui,
110 subcommand_args,
110 subcommand_args,
111 config,
111 config,
112 repo,
112 repo,
113 };
113 };
114
114
115 if let Ok(repo) = repo {
115 if let Ok(repo) = repo {
116 // We don't support subrepos, fallback if the subrepos file is present
116 // We don't support subrepos, fallback if the subrepos file is present
117 if repo.working_directory_vfs().join(".hgsub").exists() {
117 if repo.working_directory_vfs().join(".hgsub").exists() {
118 let msg = "subrepos (.hgsub is present)";
118 let msg = "subrepos (.hgsub is present)";
119 return Err(CommandError::unsupported(msg));
119 return Err(CommandError::unsupported(msg));
120 }
120 }
121 }
121 }
122
122
123 if config.is_extension_enabled(b"blackbox") {
123 if config.is_extension_enabled(b"blackbox") {
124 let blackbox =
124 let blackbox =
125 blackbox::Blackbox::new(&invocation, process_start_time)?;
125 blackbox::Blackbox::new(&invocation, process_start_time)?;
126 blackbox.log_command_start();
126 blackbox.log_command_start();
127 let result = run(&invocation);
127 let result = run(&invocation);
128 blackbox.log_command_end(exit_code(
128 blackbox.log_command_end(exit_code(
129 &result,
129 &result,
130 // TODO: show a warning or combine with original error if
130 // TODO: show a warning or combine with original error if
131 // `get_bool` returns an error
131 // `get_bool` returns an error
132 config
132 config
133 .get_bool(b"ui", b"detailed-exit-code")
133 .get_bool(b"ui", b"detailed-exit-code")
134 .unwrap_or(false),
134 .unwrap_or(false),
135 ));
135 ));
136 result
136 result
137 } else {
137 } else {
138 run(&invocation)
138 run(&invocation)
139 }
139 }
140 }
140 }
141
141
142 fn main() {
142 fn main() {
143 // Run this first, before we find out if the blackbox extension is even
143 // Run this first, before we find out if the blackbox extension is even
144 // enabled, in order to include everything in-between in the duration
144 // enabled, in order to include everything in-between in the duration
145 // measurements. Reading config files can be slow if they’re on NFS.
145 // measurements. Reading config files can be slow if they’re on NFS.
146 let process_start_time = blackbox::ProcessStartTime::now();
146 let process_start_time = blackbox::ProcessStartTime::now();
147
147
148 env_logger::init();
148 env_logger::init();
149
149
150 let early_args = EarlyArgs::parse(std::env::args_os());
150 let early_args = EarlyArgs::parse(std::env::args_os());
151
151
152 let initial_current_dir = early_args.cwd.map(|cwd| {
152 let initial_current_dir = early_args.cwd.map(|cwd| {
153 let cwd = get_path_from_bytes(&cwd);
153 let cwd = get_path_from_bytes(&cwd);
154 std::env::current_dir()
154 std::env::current_dir()
155 .and_then(|initial| {
155 .and_then(|initial| {
156 std::env::set_current_dir(cwd)?;
156 std::env::set_current_dir(cwd)?;
157 Ok(initial)
157 Ok(initial)
158 })
158 })
159 .unwrap_or_else(|error| {
159 .unwrap_or_else(|error| {
160 exit(
160 exit(
161 &None,
161 &None,
162 &Ui::new_infallible(&Config::empty()),
162 &Ui::new_infallible(&Config::empty()),
163 OnUnsupported::Abort,
163 OnUnsupported::Abort,
164 Err(CommandError::abort(format!(
164 Err(CommandError::abort(format!(
165 "abort: {}: '{}'",
165 "abort: {}: '{}'",
166 error,
166 error,
167 cwd.display()
167 cwd.display()
168 ))),
168 ))),
169 false,
169 false,
170 )
170 )
171 })
171 })
172 });
172 });
173
173
174 let mut non_repo_config =
174 let mut non_repo_config =
175 Config::load_non_repo().unwrap_or_else(|error| {
175 Config::load_non_repo().unwrap_or_else(|error| {
176 // Normally this is decided based on config, but we don’t have that
176 // Normally this is decided based on config, but we don’t have that
177 // available. As of this writing config loading never returns an
177 // available. As of this writing config loading never returns an
178 // "unsupported" error but that is not enforced by the type system.
178 // "unsupported" error but that is not enforced by the type system.
179 let on_unsupported = OnUnsupported::Abort;
179 let on_unsupported = OnUnsupported::Abort;
180
180
181 exit(
181 exit(
182 &initial_current_dir,
182 &initial_current_dir,
183 &Ui::new_infallible(&Config::empty()),
183 &Ui::new_infallible(&Config::empty()),
184 on_unsupported,
184 on_unsupported,
185 Err(error.into()),
185 Err(error.into()),
186 false,
186 false,
187 )
187 )
188 });
188 });
189
189
190 non_repo_config
190 non_repo_config
191 .load_cli_args(early_args.config, early_args.color)
191 .load_cli_args(early_args.config, early_args.color)
192 .unwrap_or_else(|error| {
192 .unwrap_or_else(|error| {
193 exit(
193 exit(
194 &initial_current_dir,
194 &initial_current_dir,
195 &Ui::new_infallible(&non_repo_config),
195 &Ui::new_infallible(&non_repo_config),
196 OnUnsupported::from_config(&non_repo_config),
196 OnUnsupported::from_config(&non_repo_config),
197 Err(error.into()),
197 Err(error.into()),
198 non_repo_config
198 non_repo_config
199 .get_bool(b"ui", b"detailed-exit-code")
199 .get_bool(b"ui", b"detailed-exit-code")
200 .unwrap_or(false),
200 .unwrap_or(false),
201 )
201 )
202 });
202 });
203
203
204 if let Some(repo_path_bytes) = &early_args.repo {
204 if let Some(repo_path_bytes) = &early_args.repo {
205 lazy_static::lazy_static! {
205 lazy_static::lazy_static! {
206 static ref SCHEME_RE: regex::bytes::Regex =
206 static ref SCHEME_RE: regex::bytes::Regex =
207 // Same as `_matchscheme` in `mercurial/util.py`
207 // Same as `_matchscheme` in `mercurial/util.py`
208 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
208 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
209 }
209 }
210 if SCHEME_RE.is_match(&repo_path_bytes) {
210 if SCHEME_RE.is_match(&repo_path_bytes) {
211 exit(
211 exit(
212 &initial_current_dir,
212 &initial_current_dir,
213 &Ui::new_infallible(&non_repo_config),
213 &Ui::new_infallible(&non_repo_config),
214 OnUnsupported::from_config(&non_repo_config),
214 OnUnsupported::from_config(&non_repo_config),
215 Err(CommandError::UnsupportedFeature {
215 Err(CommandError::UnsupportedFeature {
216 message: format_bytes!(
216 message: format_bytes!(
217 b"URL-like --repository {}",
217 b"URL-like --repository {}",
218 repo_path_bytes
218 repo_path_bytes
219 ),
219 ),
220 }),
220 }),
221 // TODO: show a warning or combine with original error if
221 // TODO: show a warning or combine with original error if
222 // `get_bool` returns an error
222 // `get_bool` returns an error
223 non_repo_config
223 non_repo_config
224 .get_bool(b"ui", b"detailed-exit-code")
224 .get_bool(b"ui", b"detailed-exit-code")
225 .unwrap_or(false),
225 .unwrap_or(false),
226 )
226 )
227 }
227 }
228 }
228 }
229 let repo_arg = early_args.repo.unwrap_or(Vec::new());
229 let repo_arg = early_args.repo.unwrap_or(Vec::new());
230 let repo_path: Option<PathBuf> = {
230 let repo_path: Option<PathBuf> = {
231 if repo_arg.is_empty() {
231 if repo_arg.is_empty() {
232 None
232 None
233 } else {
233 } else {
234 let local_config = {
234 let local_config = {
235 if std::env::var_os("HGRCSKIPREPO").is_none() {
235 if std::env::var_os("HGRCSKIPREPO").is_none() {
236 // TODO: handle errors from find_repo_root
236 // TODO: handle errors from find_repo_root
237 if let Ok(current_dir_path) = Repo::find_repo_root() {
237 if let Ok(current_dir_path) = Repo::find_repo_root() {
238 let config_files = vec![
238 let config_files = vec![
239 ConfigSource::AbsPath(
239 ConfigSource::AbsPath(
240 current_dir_path.join(".hg/hgrc"),
240 current_dir_path.join(".hg/hgrc"),
241 ),
241 ),
242 ConfigSource::AbsPath(
242 ConfigSource::AbsPath(
243 current_dir_path.join(".hg/hgrc-not-shared"),
243 current_dir_path.join(".hg/hgrc-not-shared"),
244 ),
244 ),
245 ];
245 ];
246 // TODO: handle errors from
246 // TODO: handle errors from
247 // `load_from_explicit_sources`
247 // `load_from_explicit_sources`
248 Config::load_from_explicit_sources(config_files).ok()
248 Config::load_from_explicit_sources(config_files).ok()
249 } else {
249 } else {
250 None
250 None
251 }
251 }
252 } else {
252 } else {
253 None
253 None
254 }
254 }
255 };
255 };
256
256
257 let non_repo_config_val = {
257 let non_repo_config_val = {
258 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
258 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
259 match &non_repo_val {
259 match &non_repo_val {
260 Some(val) if val.len() > 0 => home::home_dir()
260 Some(val) if val.len() > 0 => home::home_dir()
261 .unwrap_or_else(|| PathBuf::from("~"))
261 .unwrap_or_else(|| PathBuf::from("~"))
262 .join(get_path_from_bytes(val))
262 .join(get_path_from_bytes(val))
263 .canonicalize()
263 .canonicalize()
264 // TODO: handle error and make it similar to python
264 // TODO: handle error and make it similar to python
265 // implementation maybe?
265 // implementation maybe?
266 .ok(),
266 .ok(),
267 _ => None,
267 _ => None,
268 }
268 }
269 };
269 };
270
270
271 let config_val = match &local_config {
271 let config_val = match &local_config {
272 None => non_repo_config_val,
272 None => non_repo_config_val,
273 Some(val) => {
273 Some(val) => {
274 let local_config_val = val.get(b"paths", &repo_arg);
274 let local_config_val = val.get(b"paths", &repo_arg);
275 match &local_config_val {
275 match &local_config_val {
276 Some(val) if val.len() > 0 => {
276 Some(val) if val.len() > 0 => {
277 // presence of a local_config assures that
277 // presence of a local_config assures that
278 // current_dir
278 // current_dir
279 // wont result in an Error
279 // wont result in an Error
280 let canpath = hg::utils::current_dir()
280 let canpath = hg::utils::current_dir()
281 .unwrap()
281 .unwrap()
282 .join(get_path_from_bytes(val))
282 .join(get_path_from_bytes(val))
283 .canonicalize();
283 .canonicalize();
284 canpath.ok().or(non_repo_config_val)
284 canpath.ok().or(non_repo_config_val)
285 }
285 }
286 _ => non_repo_config_val,
286 _ => non_repo_config_val,
287 }
287 }
288 }
288 }
289 };
289 };
290 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
290 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
291 }
291 }
292 };
292 };
293
293
294 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
294 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
295 {
295 {
296 Ok(repo) => Ok(repo),
296 Ok(repo) => Ok(repo),
297 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
297 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
298 // Not finding a repo is not fatal yet, if `-R` was not given
298 // Not finding a repo is not fatal yet, if `-R` was not given
299 Err(NoRepoInCwdError { cwd: at })
299 Err(NoRepoInCwdError { cwd: at })
300 }
300 }
301 Err(error) => exit(
301 Err(error) => exit(
302 &initial_current_dir,
302 &initial_current_dir,
303 &Ui::new_infallible(&non_repo_config),
303 &Ui::new_infallible(&non_repo_config),
304 OnUnsupported::from_config(&non_repo_config),
304 OnUnsupported::from_config(&non_repo_config),
305 Err(error.into()),
305 Err(error.into()),
306 // TODO: show a warning or combine with original error if
306 // TODO: show a warning or combine with original error if
307 // `get_bool` returns an error
307 // `get_bool` returns an error
308 non_repo_config
308 non_repo_config
309 .get_bool(b"ui", b"detailed-exit-code")
309 .get_bool(b"ui", b"detailed-exit-code")
310 .unwrap_or(false),
310 .unwrap_or(false),
311 ),
311 ),
312 };
312 };
313
313
314 let config = if let Ok(repo) = &repo_result {
314 let config = if let Ok(repo) = &repo_result {
315 repo.config()
315 repo.config()
316 } else {
316 } else {
317 &non_repo_config
317 &non_repo_config
318 };
318 };
319 let ui = Ui::new(&config).unwrap_or_else(|error| {
319 let ui = Ui::new(&config).unwrap_or_else(|error| {
320 exit(
320 exit(
321 &initial_current_dir,
321 &initial_current_dir,
322 &Ui::new_infallible(&config),
322 &Ui::new_infallible(&config),
323 OnUnsupported::from_config(&config),
323 OnUnsupported::from_config(&config),
324 Err(error.into()),
324 Err(error.into()),
325 config
325 config
326 .get_bool(b"ui", b"detailed-exit-code")
326 .get_bool(b"ui", b"detailed-exit-code")
327 .unwrap_or(false),
327 .unwrap_or(false),
328 )
328 )
329 });
329 });
330 let on_unsupported = OnUnsupported::from_config(config);
330 let on_unsupported = OnUnsupported::from_config(config);
331
331
332 let result = main_with_result(
332 let result = main_with_result(
333 &process_start_time,
333 &process_start_time,
334 &ui,
334 &ui,
335 repo_result.as_ref(),
335 repo_result.as_ref(),
336 config,
336 config,
337 );
337 );
338 exit(
338 exit(
339 &initial_current_dir,
339 &initial_current_dir,
340 &ui,
340 &ui,
341 on_unsupported,
341 on_unsupported,
342 result,
342 result,
343 // TODO: show a warning or combine with original error if `get_bool`
343 // TODO: show a warning or combine with original error if `get_bool`
344 // returns an error
344 // returns an error
345 config
345 config
346 .get_bool(b"ui", b"detailed-exit-code")
346 .get_bool(b"ui", b"detailed-exit-code")
347 .unwrap_or(false),
347 .unwrap_or(false),
348 )
348 )
349 }
349 }
350
350
351 fn exit_code(
351 fn exit_code(
352 result: &Result<(), CommandError>,
352 result: &Result<(), CommandError>,
353 use_detailed_exit_code: bool,
353 use_detailed_exit_code: bool,
354 ) -> i32 {
354 ) -> i32 {
355 match result {
355 match result {
356 Ok(()) => exit_codes::OK,
356 Ok(()) => exit_codes::OK,
357 Err(CommandError::Abort {
357 Err(CommandError::Abort {
358 message: _,
358 message: _,
359 detailed_exit_code,
359 detailed_exit_code,
360 }) => {
360 }) => {
361 if use_detailed_exit_code {
361 if use_detailed_exit_code {
362 *detailed_exit_code
362 *detailed_exit_code
363 } else {
363 } else {
364 exit_codes::ABORT
364 exit_codes::ABORT
365 }
365 }
366 }
366 }
367 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
367 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
368
368
369 // Exit with a specific code and no error message to let a potential
369 // Exit with a specific code and no error message to let a potential
370 // wrapper script fallback to Python-based Mercurial.
370 // wrapper script fallback to Python-based Mercurial.
371 Err(CommandError::UnsupportedFeature { .. }) => {
371 Err(CommandError::UnsupportedFeature { .. }) => {
372 exit_codes::UNIMPLEMENTED
372 exit_codes::UNIMPLEMENTED
373 }
373 }
374 }
374 }
375 }
375 }
376
376
377 fn exit(
377 fn exit(
378 initial_current_dir: &Option<PathBuf>,
378 initial_current_dir: &Option<PathBuf>,
379 ui: &Ui,
379 ui: &Ui,
380 mut on_unsupported: OnUnsupported,
380 mut on_unsupported: OnUnsupported,
381 result: Result<(), CommandError>,
381 result: Result<(), CommandError>,
382 use_detailed_exit_code: bool,
382 use_detailed_exit_code: bool,
383 ) -> ! {
383 ) -> ! {
384 if let (
384 if let (
385 OnUnsupported::Fallback { executable },
385 OnUnsupported::Fallback { executable },
386 Err(CommandError::UnsupportedFeature { .. }),
386 Err(CommandError::UnsupportedFeature { .. }),
387 ) = (&on_unsupported, &result)
387 ) = (&on_unsupported, &result)
388 {
388 {
389 let mut args = std::env::args_os();
389 let mut args = std::env::args_os();
390 let executable = match executable {
390 let executable = match executable {
391 None => {
391 None => {
392 exit_no_fallback(
392 exit_no_fallback(
393 ui,
393 ui,
394 OnUnsupported::Abort,
394 OnUnsupported::Abort,
395 Err(CommandError::abort(
395 Err(CommandError::abort(
396 "abort: 'rhg.on-unsupported=fallback' without \
396 "abort: 'rhg.on-unsupported=fallback' without \
397 'rhg.fallback-executable' set.",
397 'rhg.fallback-executable' set.",
398 )),
398 )),
399 false,
399 false,
400 );
400 );
401 }
401 }
402 Some(executable) => executable,
402 Some(executable) => executable,
403 };
403 };
404 let executable_path = get_path_from_bytes(&executable);
404 let executable_path = get_path_from_bytes(&executable);
405 let this_executable = args.next().expect("exepcted argv[0] to exist");
405 let this_executable = args.next().expect("exepcted argv[0] to exist");
406 if executable_path == &PathBuf::from(this_executable) {
406 if executable_path == &PathBuf::from(this_executable) {
407 // Avoid spawning infinitely many processes until resource
407 // Avoid spawning infinitely many processes until resource
408 // exhaustion.
408 // exhaustion.
409 let _ = ui.write_stderr(&format_bytes!(
409 let _ = ui.write_stderr(&format_bytes!(
410 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
410 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
411 points to `rhg` itself.\n",
411 points to `rhg` itself.\n",
412 executable
412 executable
413 ));
413 ));
414 on_unsupported = OnUnsupported::Abort
414 on_unsupported = OnUnsupported::Abort
415 } else {
415 } else {
416 // `args` is now `argv[1..]` since we’ve already consumed
416 // `args` is now `argv[1..]` since we’ve already consumed
417 // `argv[0]`
417 // `argv[0]`
418 let mut command = Command::new(executable_path);
418 let mut command = Command::new(executable_path);
419 command.args(args);
419 command.args(args);
420 if let Some(initial) = initial_current_dir {
420 if let Some(initial) = initial_current_dir {
421 command.current_dir(initial);
421 command.current_dir(initial);
422 }
422 }
423 let result = command.status();
423 let result = command.status();
424 match result {
424 match result {
425 Ok(status) => std::process::exit(
425 Ok(status) => std::process::exit(
426 status.code().unwrap_or(exit_codes::ABORT),
426 status.code().unwrap_or(exit_codes::ABORT),
427 ),
427 ),
428 Err(error) => {
428 Err(error) => {
429 let _ = ui.write_stderr(&format_bytes!(
429 let _ = ui.write_stderr(&format_bytes!(
430 b"tried to fall back to a '{}' sub-process but got error {}\n",
430 b"tried to fall back to a '{}' sub-process but got error {}\n",
431 executable, format_bytes::Utf8(error)
431 executable, format_bytes::Utf8(error)
432 ));
432 ));
433 on_unsupported = OnUnsupported::Abort
433 on_unsupported = OnUnsupported::Abort
434 }
434 }
435 }
435 }
436 }
436 }
437 }
437 }
438 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
438 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
439 }
439 }
440
440
441 fn exit_no_fallback(
441 fn exit_no_fallback(
442 ui: &Ui,
442 ui: &Ui,
443 on_unsupported: OnUnsupported,
443 on_unsupported: OnUnsupported,
444 result: Result<(), CommandError>,
444 result: Result<(), CommandError>,
445 use_detailed_exit_code: bool,
445 use_detailed_exit_code: bool,
446 ) -> ! {
446 ) -> ! {
447 match &result {
447 match &result {
448 Ok(_) => {}
448 Ok(_) => {}
449 Err(CommandError::Unsuccessful) => {}
449 Err(CommandError::Unsuccessful) => {}
450 Err(CommandError::Abort {
450 Err(CommandError::Abort {
451 message,
451 message,
452 detailed_exit_code: _,
452 detailed_exit_code: _,
453 }) => {
453 }) => {
454 if !message.is_empty() {
454 if !message.is_empty() {
455 // Ignore errors when writing to stderr, we’re already exiting
455 // Ignore errors when writing to stderr, we’re already exiting
456 // with failure code so there’s not much more we can do.
456 // with failure code so there’s not much more we can do.
457 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
457 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
458 }
458 }
459 }
459 }
460 Err(CommandError::UnsupportedFeature { message }) => {
460 Err(CommandError::UnsupportedFeature { message }) => {
461 match on_unsupported {
461 match on_unsupported {
462 OnUnsupported::Abort => {
462 OnUnsupported::Abort => {
463 let _ = ui.write_stderr(&format_bytes!(
463 let _ = ui.write_stderr(&format_bytes!(
464 b"unsupported feature: {}\n",
464 b"unsupported feature: {}\n",
465 message
465 message
466 ));
466 ));
467 }
467 }
468 OnUnsupported::AbortSilent => {}
468 OnUnsupported::AbortSilent => {}
469 OnUnsupported::Fallback { .. } => unreachable!(),
469 OnUnsupported::Fallback { .. } => unreachable!(),
470 }
470 }
471 }
471 }
472 }
472 }
473 std::process::exit(exit_code(&result, use_detailed_exit_code))
473 std::process::exit(exit_code(&result, use_detailed_exit_code))
474 }
474 }
475
475
476 macro_rules! subcommands {
476 macro_rules! subcommands {
477 ($( $command: ident )+) => {
477 ($( $command: ident )+) => {
478 mod commands {
478 mod commands {
479 $(
479 $(
480 pub mod $command;
480 pub mod $command;
481 )+
481 )+
482 }
482 }
483
483
484 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
484 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
485 app
485 app
486 $(
486 $(
487 .subcommand(commands::$command::args())
487 .subcommand(commands::$command::args())
488 )+
488 )+
489 }
489 }
490
490
491 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
491 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
492
492
493 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
493 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
494 match name {
494 match name {
495 $(
495 $(
496 stringify!($command) => Some(commands::$command::run),
496 stringify!($command) => Some(commands::$command::run),
497 )+
497 )+
498 _ => None,
498 _ => None,
499 }
499 }
500 }
500 }
501 };
501 };
502 }
502 }
503
503
504 subcommands! {
504 subcommands! {
505 cat
505 cat
506 debugdata
506 debugdata
507 debugrequirements
507 debugrequirements
508 debugignorerhg
508 debugignorerhg
509 files
509 files
510 root
510 root
511 config
511 config
512 status
512 status
513 }
513 }
514
514
515 pub struct CliInvocation<'a> {
515 pub struct CliInvocation<'a> {
516 ui: &'a Ui,
516 ui: &'a Ui,
517 subcommand_args: &'a ArgMatches<'a>,
517 subcommand_args: &'a ArgMatches<'a>,
518 config: &'a Config,
518 config: &'a Config,
519 /// References inside `Result` is a bit peculiar but allow
519 /// References inside `Result` is a bit peculiar but allow
520 /// `invocation.repo?` to work out with `&CliInvocation` since this
520 /// `invocation.repo?` to work out with `&CliInvocation` since this
521 /// `Result` type is `Copy`.
521 /// `Result` type is `Copy`.
522 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
522 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
523 }
523 }
524
524
525 struct NoRepoInCwdError {
525 struct NoRepoInCwdError {
526 cwd: PathBuf,
526 cwd: PathBuf,
527 }
527 }
528
528
529 /// CLI arguments to be parsed "early" in order to be able to read
529 /// CLI arguments to be parsed "early" in order to be able to read
530 /// configuration before using Clap. Ideally we would also use Clap for this,
530 /// configuration before using Clap. Ideally we would also use Clap for this,
531 /// see <https://github.com/clap-rs/clap/discussions/2366>.
531 /// see <https://github.com/clap-rs/clap/discussions/2366>.
532 ///
532 ///
533 /// These arguments are still declared when we do use Clap later, so that Clap
533 /// These arguments are still declared when we do use Clap later, so that Clap
534 /// does not return an error for their presence.
534 /// does not return an error for their presence.
535 struct EarlyArgs {
535 struct EarlyArgs {
536 /// Values of all `--config` arguments. (Possibly none)
536 /// Values of all `--config` arguments. (Possibly none)
537 config: Vec<Vec<u8>>,
537 config: Vec<Vec<u8>>,
538 /// Value of all the `--color` argument, if any.
538 /// Value of all the `--color` argument, if any.
539 color: Option<Vec<u8>>,
539 color: Option<Vec<u8>>,
540 /// Value of the `-R` or `--repository` argument, if any.
540 /// Value of the `-R` or `--repository` argument, if any.
541 repo: Option<Vec<u8>>,
541 repo: Option<Vec<u8>>,
542 /// Value of the `--cwd` argument, if any.
542 /// Value of the `--cwd` argument, if any.
543 cwd: Option<Vec<u8>>,
543 cwd: Option<Vec<u8>>,
544 }
544 }
545
545
546 impl EarlyArgs {
546 impl EarlyArgs {
547 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
547 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
548 let mut args = args.into_iter().map(get_bytes_from_os_str);
548 let mut args = args.into_iter().map(get_bytes_from_os_str);
549 let mut config = Vec::new();
549 let mut config = Vec::new();
550 let mut color = None;
550 let mut color = None;
551 let mut repo = None;
551 let mut repo = None;
552 let mut cwd = None;
552 let mut cwd = None;
553 // Use `while let` instead of `for` so that we can also call
553 // Use `while let` instead of `for` so that we can also call
554 // `args.next()` inside the loop.
554 // `args.next()` inside the loop.
555 while let Some(arg) = args.next() {
555 while let Some(arg) = args.next() {
556 if arg == b"--config" {
556 if arg == b"--config" {
557 if let Some(value) = args.next() {
557 if let Some(value) = args.next() {
558 config.push(value)
558 config.push(value)
559 }
559 }
560 } else if let Some(value) = arg.drop_prefix(b"--config=") {
560 } else if let Some(value) = arg.drop_prefix(b"--config=") {
561 config.push(value.to_owned())
561 config.push(value.to_owned())
562 }
562 }
563
563
564 if arg == b"--color" {
564 if arg == b"--color" {
565 if let Some(value) = args.next() {
565 if let Some(value) = args.next() {
566 color = Some(value)
566 color = Some(value)
567 }
567 }
568 } else if let Some(value) = arg.drop_prefix(b"--color=") {
568 } else if let Some(value) = arg.drop_prefix(b"--color=") {
569 color = Some(value.to_owned())
569 color = Some(value.to_owned())
570 }
570 }
571
571
572 if arg == b"--cwd" {
572 if arg == b"--cwd" {
573 if let Some(value) = args.next() {
573 if let Some(value) = args.next() {
574 cwd = Some(value)
574 cwd = Some(value)
575 }
575 }
576 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
576 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
577 cwd = Some(value.to_owned())
577 cwd = Some(value.to_owned())
578 }
578 }
579
579
580 if arg == b"--repository" || arg == b"-R" {
580 if arg == b"--repository" || arg == b"-R" {
581 if let Some(value) = args.next() {
581 if let Some(value) = args.next() {
582 repo = Some(value)
582 repo = Some(value)
583 }
583 }
584 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
584 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
585 repo = Some(value.to_owned())
585 repo = Some(value.to_owned())
586 } else if let Some(value) = arg.drop_prefix(b"-R") {
586 } else if let Some(value) = arg.drop_prefix(b"-R") {
587 repo = Some(value.to_owned())
587 repo = Some(value.to_owned())
588 }
588 }
589 }
589 }
590 Self {
590 Self {
591 config,
591 config,
592 color,
592 color,
593 repo,
593 repo,
594 cwd,
594 cwd,
595 }
595 }
596 }
596 }
597 }
597 }
598
598
599 /// What to do when encountering some unsupported feature.
599 /// What to do when encountering some unsupported feature.
600 ///
600 ///
601 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
601 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
602 enum OnUnsupported {
602 enum OnUnsupported {
603 /// Print an error message describing what feature is not supported,
603 /// Print an error message describing what feature is not supported,
604 /// and exit with code 252.
604 /// and exit with code 252.
605 Abort,
605 Abort,
606 /// Silently exit with code 252.
606 /// Silently exit with code 252.
607 AbortSilent,
607 AbortSilent,
608 /// Try running a Python implementation
608 /// Try running a Python implementation
609 Fallback { executable: Option<Vec<u8>> },
609 Fallback { executable: Option<Vec<u8>> },
610 }
610 }
611
611
612 impl OnUnsupported {
612 impl OnUnsupported {
613 const DEFAULT: Self = OnUnsupported::Abort;
613 const DEFAULT: Self = OnUnsupported::Abort;
614
614
615 fn from_config(config: &Config) -> Self {
615 fn from_config(config: &Config) -> Self {
616 match config
616 match config
617 .get(b"rhg", b"on-unsupported")
617 .get(b"rhg", b"on-unsupported")
618 .map(|value| value.to_ascii_lowercase())
618 .map(|value| value.to_ascii_lowercase())
619 .as_deref()
619 .as_deref()
620 {
620 {
621 Some(b"abort") => OnUnsupported::Abort,
621 Some(b"abort") => OnUnsupported::Abort,
622 Some(b"abort-silent") => OnUnsupported::AbortSilent,
622 Some(b"abort-silent") => OnUnsupported::AbortSilent,
623 Some(b"fallback") => OnUnsupported::Fallback {
623 Some(b"fallback") => OnUnsupported::Fallback {
624 executable: config
624 executable: config
625 .get(b"rhg", b"fallback-executable")
625 .get(b"rhg", b"fallback-executable")
626 .map(|x| x.to_owned()),
626 .map(|x| x.to_owned()),
627 },
627 },
628 None => Self::DEFAULT,
628 None => Self::DEFAULT,
629 Some(_) => {
629 Some(_) => {
630 // TODO: warn about unknown config value
630 // TODO: warn about unknown config value
631 Self::DEFAULT
631 Self::DEFAULT
632 }
632 }
633 }
633 }
634 }
634 }
635 }
635 }
636
636
637 /// The `*` extension is an edge-case for config sub-options that apply to all
637 /// The `*` extension is an edge-case for config sub-options that apply to all
638 /// extensions. For now, only `:required` exists, but that may change in the
638 /// extensions. For now, only `:required` exists, but that may change in the
639 /// future.
639 /// future.
640 const SUPPORTED_EXTENSIONS: &[&[u8]] =
640 const SUPPORTED_EXTENSIONS: &[&[u8]] =
641 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
641 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
642
642
643 fn check_extensions(config: &Config) -> Result<(), CommandError> {
643 fn check_extensions(config: &Config) -> Result<(), CommandError> {
644 let enabled: HashSet<&[u8]> = config
644 let enabled: HashSet<&[u8]> = config
645 .get_section_keys(b"extensions")
645 .get_section_keys(b"extensions")
646 .into_iter()
646 .into_iter()
647 .map(|extension| {
647 .map(|extension| {
648 // Ignore extension suboptions. Only `required` exists for now.
648 // Ignore extension suboptions. Only `required` exists for now.
649 // `rhg` either supports an extension or doesn't, so it doesn't
649 // `rhg` either supports an extension or doesn't, so it doesn't
650 // make sense to consider the loading of an extension.
650 // make sense to consider the loading of an extension.
651 extension.split_2(b':').unwrap_or((extension, b"")).0
651 extension.split_2(b':').unwrap_or((extension, b"")).0
652 })
652 })
653 .collect();
653 .collect();
654
654
655 let mut unsupported = enabled;
655 let mut unsupported = enabled;
656 for supported in SUPPORTED_EXTENSIONS {
656 for supported in SUPPORTED_EXTENSIONS {
657 unsupported.remove(supported);
657 unsupported.remove(supported);
658 }
658 }
659
659
660 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
660 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
661 {
661 {
662 for ignored in ignored_list {
662 for ignored in ignored_list {
663 unsupported.remove(ignored.as_slice());
663 unsupported.remove(ignored.as_slice());
664 }
664 }
665 }
665 }
666
666
667 if unsupported.is_empty() {
667 if unsupported.is_empty() {
668 Ok(())
668 Ok(())
669 } else {
669 } else {
670 Err(CommandError::UnsupportedFeature {
670 Err(CommandError::UnsupportedFeature {
671 message: format_bytes!(
671 message: format_bytes!(
672 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
672 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
673 join(unsupported, b", ")
673 join(unsupported, b", ")
674 ),
674 ),
675 })
675 })
676 }
676 }
677 }
677 }
678
678
679 fn check_unsupported(
679 fn check_unsupported(
680 config: &Config,
680 config: &Config,
681 repo: Result<&Repo, &NoRepoInCwdError>,
681 repo: Result<&Repo, &NoRepoInCwdError>,
682 ui: &ui::Ui,
683 ) -> Result<(), CommandError> {
682 ) -> Result<(), CommandError> {
684 check_extensions(config)?;
683 check_extensions(config)?;
685
684
686 if std::env::var_os("HG_PENDING").is_some() {
685 if std::env::var_os("HG_PENDING").is_some() {
687 // TODO: only if the value is `== repo.working_directory`?
686 // TODO: only if the value is `== repo.working_directory`?
688 // What about relative v.s. absolute paths?
687 // What about relative v.s. absolute paths?
689 Err(CommandError::unsupported("$HG_PENDING"))?
688 Err(CommandError::unsupported("$HG_PENDING"))?
690 }
689 }
691
690
692 if let Ok(repo) = repo {
691 if let Ok(repo) = repo {
693 if repo.has_subrepos()? {
692 if repo.has_subrepos()? {
694 Err(CommandError::unsupported("sub-repositories"))?
693 Err(CommandError::unsupported("sub-repositories"))?
695 }
694 }
696 }
695 }
697
696
698 if config.has_non_empty_section(b"encode") {
697 if config.has_non_empty_section(b"encode") {
699 Err(CommandError::unsupported("[encode] config"))?
698 Err(CommandError::unsupported("[encode] config"))?
700 }
699 }
701
700
702 if config.has_non_empty_section(b"decode") {
701 if config.has_non_empty_section(b"decode") {
703 Err(CommandError::unsupported("[decode] config"))?
702 Err(CommandError::unsupported("[decode] config"))?
704 }
703 }
705
704
706 if let Some(color) = config.get(b"ui", b"color") {
707 if (color == b"always" || color == b"debug")
708 && !ui.plain(Some("color"))
709 {
710 Err(CommandError::unsupported("colored output"))?
711 }
712 }
713
714 Ok(())
705 Ok(())
715 }
706 }
@@ -1,245 +1,240 b''
1 use crate::color::ColorConfig;
1 use crate::color::ColorConfig;
2 use crate::color::Effect;
2 use crate::color::Effect;
3 use format_bytes::format_bytes;
3 use format_bytes::format_bytes;
4 use format_bytes::write_bytes;
4 use format_bytes::write_bytes;
5 use hg::config::Config;
5 use hg::config::Config;
6 use hg::errors::HgError;
6 use hg::errors::HgError;
7 use hg::utils::files::get_bytes_from_os_string;
7 use hg::utils::files::get_bytes_from_os_string;
8 use std::borrow::Cow;
8 use std::borrow::Cow;
9 use std::env;
9 use std::env;
10 use std::io;
10 use std::io;
11 use std::io::{ErrorKind, Write};
11 use std::io::{ErrorKind, Write};
12
12
13 pub struct Ui {
13 pub struct Ui {
14 stdout: std::io::Stdout,
14 stdout: std::io::Stdout,
15 stderr: std::io::Stderr,
15 stderr: std::io::Stderr,
16 colors: Option<ColorConfig>,
16 colors: Option<ColorConfig>,
17 }
17 }
18
18
19 /// The kind of user interface error
19 /// The kind of user interface error
20 pub enum UiError {
20 pub enum UiError {
21 /// The standard output stream cannot be written to
21 /// The standard output stream cannot be written to
22 StdoutError(io::Error),
22 StdoutError(io::Error),
23 /// The standard error stream cannot be written to
23 /// The standard error stream cannot be written to
24 StderrError(io::Error),
24 StderrError(io::Error),
25 }
25 }
26
26
27 /// The commandline user interface
27 /// The commandline user interface
28 impl Ui {
28 impl Ui {
29 pub fn new(config: &Config) -> Result<Self, HgError> {
29 pub fn new(config: &Config) -> Result<Self, HgError> {
30 Ok(Ui {
30 Ok(Ui {
31 // If using something else, also adapt `isatty()` below.
31 // If using something else, also adapt `isatty()` below.
32 stdout: std::io::stdout(),
32 stdout: std::io::stdout(),
33
33
34 stderr: std::io::stderr(),
34 stderr: std::io::stderr(),
35 colors: ColorConfig::new(config)?,
35 colors: ColorConfig::new(config)?,
36 })
36 })
37 }
37 }
38
38
39 /// Default to no color if color configuration errors.
39 /// Default to no color if color configuration errors.
40 ///
40 ///
41 /// Useful when we’re already handling another error.
41 /// Useful when we’re already handling another error.
42 pub fn new_infallible(config: &Config) -> Self {
42 pub fn new_infallible(config: &Config) -> Self {
43 Ui {
43 Ui {
44 // If using something else, also adapt `isatty()` below.
44 // If using something else, also adapt `isatty()` below.
45 stdout: std::io::stdout(),
45 stdout: std::io::stdout(),
46
46
47 stderr: std::io::stderr(),
47 stderr: std::io::stderr(),
48 colors: ColorConfig::new(config).unwrap_or(None),
48 colors: ColorConfig::new(config).unwrap_or(None),
49 }
49 }
50 }
50 }
51
51
52 /// Returns a buffered handle on stdout for faster batch printing
52 /// Returns a buffered handle on stdout for faster batch printing
53 /// operations.
53 /// operations.
54 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
54 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
55 StdoutBuffer::new(self.stdout.lock())
55 StdoutBuffer::new(self.stdout.lock())
56 }
56 }
57
57
58 /// Write bytes to stdout
58 /// Write bytes to stdout
59 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
59 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
60 // Hack to silence "unused" warnings
61 if false {
62 return self.write_stdout_labelled(bytes, "");
63 }
64
65 let mut stdout = self.stdout.lock();
60 let mut stdout = self.stdout.lock();
66
61
67 stdout.write_all(bytes).or_else(handle_stdout_error)?;
62 stdout.write_all(bytes).or_else(handle_stdout_error)?;
68
63
69 stdout.flush().or_else(handle_stdout_error)
64 stdout.flush().or_else(handle_stdout_error)
70 }
65 }
71
66
72 /// Write bytes to stderr
67 /// Write bytes to stderr
73 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
68 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
74 let mut stderr = self.stderr.lock();
69 let mut stderr = self.stderr.lock();
75
70
76 stderr.write_all(bytes).or_else(handle_stderr_error)?;
71 stderr.write_all(bytes).or_else(handle_stderr_error)?;
77
72
78 stderr.flush().or_else(handle_stderr_error)
73 stderr.flush().or_else(handle_stderr_error)
79 }
74 }
80
75
81 /// Write bytes to stdout with the given label
76 /// Write bytes to stdout with the given label
82 ///
77 ///
83 /// Like the optional `label` parameter in `mercurial/ui.py`,
78 /// Like the optional `label` parameter in `mercurial/ui.py`,
84 /// this label influences the color used for this output.
79 /// this label influences the color used for this output.
85 pub fn write_stdout_labelled(
80 pub fn write_stdout_labelled(
86 &self,
81 &self,
87 bytes: &[u8],
82 bytes: &[u8],
88 label: &str,
83 label: &str,
89 ) -> Result<(), UiError> {
84 ) -> Result<(), UiError> {
90 if let Some(colors) = &self.colors {
85 if let Some(colors) = &self.colors {
91 if let Some(effects) = colors.styles.get(label.as_bytes()) {
86 if let Some(effects) = colors.styles.get(label.as_bytes()) {
92 if !effects.is_empty() {
87 if !effects.is_empty() {
93 return self
88 return self
94 .write_stdout_with_effects(bytes, effects)
89 .write_stdout_with_effects(bytes, effects)
95 .or_else(handle_stdout_error);
90 .or_else(handle_stdout_error);
96 }
91 }
97 }
92 }
98 }
93 }
99 self.write_stdout(bytes)
94 self.write_stdout(bytes)
100 }
95 }
101
96
102 fn write_stdout_with_effects(
97 fn write_stdout_with_effects(
103 &self,
98 &self,
104 bytes: &[u8],
99 bytes: &[u8],
105 effects: &[Effect],
100 effects: &[Effect],
106 ) -> io::Result<()> {
101 ) -> io::Result<()> {
107 let stdout = &mut self.stdout.lock();
102 let stdout = &mut self.stdout.lock();
108 let mut write_line = |line: &[u8], first: bool| {
103 let mut write_line = |line: &[u8], first: bool| {
109 // `line` does not include the newline delimiter
104 // `line` does not include the newline delimiter
110 if !first {
105 if !first {
111 stdout.write_all(b"\n")?;
106 stdout.write_all(b"\n")?;
112 }
107 }
113 if line.is_empty() {
108 if line.is_empty() {
114 return Ok(());
109 return Ok(());
115 }
110 }
116 /// 0x1B == 27 == 0o33
111 /// 0x1B == 27 == 0o33
117 const ASCII_ESCAPE: &[u8] = b"\x1b";
112 const ASCII_ESCAPE: &[u8] = b"\x1b";
118 write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?;
113 write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?;
119 for effect in effects {
114 for effect in effects {
120 write_bytes!(stdout, b";{}", effect)?;
115 write_bytes!(stdout, b";{}", effect)?;
121 }
116 }
122 write_bytes!(stdout, b"m")?;
117 write_bytes!(stdout, b"m")?;
123 stdout.write_all(line)?;
118 stdout.write_all(line)?;
124 write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE)
119 write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE)
125 };
120 };
126 let mut lines = bytes.split(|&byte| byte == b'\n');
121 let mut lines = bytes.split(|&byte| byte == b'\n');
127 if let Some(first) = lines.next() {
122 if let Some(first) = lines.next() {
128 write_line(first, true)?;
123 write_line(first, true)?;
129 for line in lines {
124 for line in lines {
130 write_line(line, false)?
125 write_line(line, false)?
131 }
126 }
132 }
127 }
133 stdout.flush()
128 stdout.flush()
134 }
129 }
135
130
136 /// Return whether plain mode is active.
131 /// Return whether plain mode is active.
137 ///
132 ///
138 /// Plain mode means that all configuration variables which affect
133 /// Plain mode means that all configuration variables which affect
139 /// the behavior and output of Mercurial should be
134 /// the behavior and output of Mercurial should be
140 /// ignored. Additionally, the output should be stable,
135 /// ignored. Additionally, the output should be stable,
141 /// reproducible and suitable for use in scripts or applications.
136 /// reproducible and suitable for use in scripts or applications.
142 ///
137 ///
143 /// The only way to trigger plain mode is by setting either the
138 /// The only way to trigger plain mode is by setting either the
144 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
139 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
145 ///
140 ///
146 /// The return value can either be
141 /// The return value can either be
147 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
142 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
148 /// - False if feature is disabled by default and not included in HGPLAIN
143 /// - False if feature is disabled by default and not included in HGPLAIN
149 /// - True otherwise
144 /// - True otherwise
150 pub fn plain(&self, feature: Option<&str>) -> bool {
145 pub fn plain(&self, feature: Option<&str>) -> bool {
151 plain(feature)
146 plain(feature)
152 }
147 }
153 }
148 }
154
149
155 pub fn plain(opt_feature: Option<&str>) -> bool {
150 pub fn plain(opt_feature: Option<&str>) -> bool {
156 if let Some(except) = env::var_os("HGPLAINEXCEPT") {
151 if let Some(except) = env::var_os("HGPLAINEXCEPT") {
157 opt_feature.map_or(true, |feature| {
152 opt_feature.map_or(true, |feature| {
158 get_bytes_from_os_string(except)
153 get_bytes_from_os_string(except)
159 .split(|&byte| byte == b',')
154 .split(|&byte| byte == b',')
160 .all(|exception| exception != feature.as_bytes())
155 .all(|exception| exception != feature.as_bytes())
161 })
156 })
162 } else {
157 } else {
163 env::var_os("HGPLAIN").is_some()
158 env::var_os("HGPLAIN").is_some()
164 }
159 }
165 }
160 }
166
161
167 /// A buffered stdout writer for faster batch printing operations.
162 /// A buffered stdout writer for faster batch printing operations.
168 pub struct StdoutBuffer<W: Write> {
163 pub struct StdoutBuffer<W: Write> {
169 buf: io::BufWriter<W>,
164 buf: io::BufWriter<W>,
170 }
165 }
171
166
172 impl<W: Write> StdoutBuffer<W> {
167 impl<W: Write> StdoutBuffer<W> {
173 pub fn new(writer: W) -> Self {
168 pub fn new(writer: W) -> Self {
174 let buf = io::BufWriter::new(writer);
169 let buf = io::BufWriter::new(writer);
175 Self { buf }
170 Self { buf }
176 }
171 }
177
172
178 /// Write bytes to stdout buffer
173 /// Write bytes to stdout buffer
179 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
174 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
180 self.buf.write_all(bytes).or_else(handle_stdout_error)
175 self.buf.write_all(bytes).or_else(handle_stdout_error)
181 }
176 }
182
177
183 /// Flush bytes to stdout
178 /// Flush bytes to stdout
184 pub fn flush(&mut self) -> Result<(), UiError> {
179 pub fn flush(&mut self) -> Result<(), UiError> {
185 self.buf.flush().or_else(handle_stdout_error)
180 self.buf.flush().or_else(handle_stdout_error)
186 }
181 }
187 }
182 }
188
183
189 /// Sometimes writing to stdout is not possible, try writing to stderr to
184 /// Sometimes writing to stdout is not possible, try writing to stderr to
190 /// signal that failure, otherwise just bail.
185 /// signal that failure, otherwise just bail.
191 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
186 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
192 if let ErrorKind::BrokenPipe = error.kind() {
187 if let ErrorKind::BrokenPipe = error.kind() {
193 // This makes `| head` work for example
188 // This makes `| head` work for example
194 return Ok(());
189 return Ok(());
195 }
190 }
196 let mut stderr = io::stderr();
191 let mut stderr = io::stderr();
197
192
198 stderr
193 stderr
199 .write_all(&format_bytes!(
194 .write_all(&format_bytes!(
200 b"abort: {}\n",
195 b"abort: {}\n",
201 error.to_string().as_bytes()
196 error.to_string().as_bytes()
202 ))
197 ))
203 .map_err(UiError::StderrError)?;
198 .map_err(UiError::StderrError)?;
204
199
205 stderr.flush().map_err(UiError::StderrError)?;
200 stderr.flush().map_err(UiError::StderrError)?;
206
201
207 Err(UiError::StdoutError(error))
202 Err(UiError::StdoutError(error))
208 }
203 }
209
204
210 /// Sometimes writing to stderr is not possible.
205 /// Sometimes writing to stderr is not possible.
211 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
206 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
212 // A broken pipe should not result in a error
207 // A broken pipe should not result in a error
213 // like with `| head` for example
208 // like with `| head` for example
214 if let ErrorKind::BrokenPipe = error.kind() {
209 if let ErrorKind::BrokenPipe = error.kind() {
215 return Ok(());
210 return Ok(());
216 }
211 }
217 Err(UiError::StdoutError(error))
212 Err(UiError::StdoutError(error))
218 }
213 }
219
214
220 /// Encode rust strings according to the user system.
215 /// Encode rust strings according to the user system.
221 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
216 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
222 // TODO encode for the user's system //
217 // TODO encode for the user's system //
223 let bytes = s.as_bytes();
218 let bytes = s.as_bytes();
224 Cow::Borrowed(bytes)
219 Cow::Borrowed(bytes)
225 }
220 }
226
221
227 /// Should formatted output be used?
222 /// Should formatted output be used?
228 ///
223 ///
229 /// Note: rhg does not have the formatter mechanism yet,
224 /// Note: rhg does not have the formatter mechanism yet,
230 /// but this is also used when deciding whether to use color.
225 /// but this is also used when deciding whether to use color.
231 pub fn formatted(config: &Config) -> Result<bool, HgError> {
226 pub fn formatted(config: &Config) -> Result<bool, HgError> {
232 if let Some(formatted) = config.get_option(b"ui", b"formatted")? {
227 if let Some(formatted) = config.get_option(b"ui", b"formatted")? {
233 Ok(formatted)
228 Ok(formatted)
234 } else {
229 } else {
235 isatty(config)
230 isatty(config)
236 }
231 }
237 }
232 }
238
233
239 fn isatty(config: &Config) -> Result<bool, HgError> {
234 fn isatty(config: &Config) -> Result<bool, HgError> {
240 Ok(if config.get_bool(b"ui", b"nontty")? {
235 Ok(if config.get_bool(b"ui", b"nontty")? {
241 false
236 false
242 } else {
237 } else {
243 atty::is(atty::Stream::Stdout)
238 atty::is(atty::Stream::Stdout)
244 })
239 })
245 }
240 }
@@ -1,408 +1,407 b''
1 $ cat <<EOF >> $HGRCPATH
1 $ cat <<EOF >> $HGRCPATH
2 > [ui]
2 > [ui]
3 > color = always
3 > color = always
4 > [color]
4 > [color]
5 > mode = ansi
5 > mode = ansi
6 > EOF
6 > EOF
7 Terminfo codes compatibility fix
7 Terminfo codes compatibility fix
8 $ echo "color.none=0" >> $HGRCPATH
8 $ echo "color.none=0" >> $HGRCPATH
9
9
10 $ hg init repo1
10 $ hg init repo1
11 $ cd repo1
11 $ cd repo1
12 $ mkdir a b a/1 b/1 b/2
12 $ mkdir a b a/1 b/1 b/2
13 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
13 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
14
14
15 hg status in repo root:
15 hg status in repo root:
16
16
17 $ hg status
17 $ hg status
18 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
18 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
19 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
19 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
20 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
20 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
21 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
21 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
22 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
22 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
23 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
23 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
24
24
25 $ hg status --color=debug
25 $ hg status --color=debug
26 [status.unknown|? ][status.unknown|a/1/in_a_1]
26 [status.unknown|? ][status.unknown|a/1/in_a_1]
27 [status.unknown|? ][status.unknown|a/in_a]
27 [status.unknown|? ][status.unknown|a/in_a]
28 [status.unknown|? ][status.unknown|b/1/in_b_1]
28 [status.unknown|? ][status.unknown|b/1/in_b_1]
29 [status.unknown|? ][status.unknown|b/2/in_b_2]
29 [status.unknown|? ][status.unknown|b/2/in_b_2]
30 [status.unknown|? ][status.unknown|b/in_b]
30 [status.unknown|? ][status.unknown|b/in_b]
31 [status.unknown|? ][status.unknown|in_root]
31 [status.unknown|? ][status.unknown|in_root]
32 HGPLAIN disables color
32 HGPLAIN disables color
33 $ HGPLAIN=1 hg status --color=debug
33 $ HGPLAIN=1 hg status --color=debug
34 ? a/1/in_a_1 (glob)
34 ? a/1/in_a_1 (glob)
35 ? a/in_a (glob)
35 ? a/in_a (glob)
36 ? b/1/in_b_1 (glob)
36 ? b/1/in_b_1 (glob)
37 ? b/2/in_b_2 (glob)
37 ? b/2/in_b_2 (glob)
38 ? b/in_b (glob)
38 ? b/in_b (glob)
39 ? in_root
39 ? in_root
40 HGPLAINEXCEPT=color does not disable color
40 HGPLAINEXCEPT=color does not disable color
41 $ HGPLAINEXCEPT=color hg status --color=debug
41 $ HGPLAINEXCEPT=color hg status --color=debug
42 [status.unknown|? ][status.unknown|a/1/in_a_1] (glob)
42 [status.unknown|? ][status.unknown|a/1/in_a_1] (glob)
43 [status.unknown|? ][status.unknown|a/in_a] (glob)
43 [status.unknown|? ][status.unknown|a/in_a] (glob)
44 [status.unknown|? ][status.unknown|b/1/in_b_1] (glob)
44 [status.unknown|? ][status.unknown|b/1/in_b_1] (glob)
45 [status.unknown|? ][status.unknown|b/2/in_b_2] (glob)
45 [status.unknown|? ][status.unknown|b/2/in_b_2] (glob)
46 [status.unknown|? ][status.unknown|b/in_b] (glob)
46 [status.unknown|? ][status.unknown|b/in_b] (glob)
47 [status.unknown|? ][status.unknown|in_root]
47 [status.unknown|? ][status.unknown|in_root]
48
48
49 hg status with template
49 hg status with template
50 $ hg status -T "{label('red', path)}\n" --color=debug
50 $ hg status -T "{label('red', path)}\n" --color=debug
51 [red|a/1/in_a_1]
51 [red|a/1/in_a_1]
52 [red|a/in_a]
52 [red|a/in_a]
53 [red|b/1/in_b_1]
53 [red|b/1/in_b_1]
54 [red|b/2/in_b_2]
54 [red|b/2/in_b_2]
55 [red|b/in_b]
55 [red|b/in_b]
56 [red|in_root]
56 [red|in_root]
57
57
58 hg status . in repo root:
58 hg status . in repo root:
59
59
60 $ hg status .
60 $ hg status .
61 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
61 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
62 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
62 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
63 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
63 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
64 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
64 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
65 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
65 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
66 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
66 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
67
67
68 $ hg status --cwd a
68 $ hg status --cwd a
69 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
69 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
70 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
70 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
71 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
71 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
72 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
72 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
73 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
73 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
74 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
74 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
75 $ hg status --cwd a .
75 $ hg status --cwd a .
76 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
76 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
77 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
77 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
78 $ hg status --cwd a ..
78 $ hg status --cwd a ..
79 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
79 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
80 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
80 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
81 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/1/in_b_1\x1b[0m (esc)
81 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/1/in_b_1\x1b[0m (esc)
82 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/2/in_b_2\x1b[0m (esc)
82 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/2/in_b_2\x1b[0m (esc)
83 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/in_b\x1b[0m (esc)
83 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/in_b\x1b[0m (esc)
84 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
84 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
85
85
86 $ hg status --cwd b
86 $ hg status --cwd b
87 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
87 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
88 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
88 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
89 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
89 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
90 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
90 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
91 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
91 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
92 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
92 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
93 $ hg status --cwd b .
93 $ hg status --cwd b .
94 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
94 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
95 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
95 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
96 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
96 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
97 $ hg status --cwd b ..
97 $ hg status --cwd b ..
98 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/1/in_a_1\x1b[0m (esc)
98 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/1/in_a_1\x1b[0m (esc)
99 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/in_a\x1b[0m (esc)
99 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/in_a\x1b[0m (esc)
100 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
100 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
101 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
101 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
102 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
102 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
103 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
103 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
104
104
105 $ hg status --cwd a/1
105 $ hg status --cwd a/1
106 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
106 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
107 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
107 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
108 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
108 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
109 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
109 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
110 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
110 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
111 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
111 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
112 $ hg status --cwd a/1 .
112 $ hg status --cwd a/1 .
113 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
113 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
114 $ hg status --cwd a/1 ..
114 $ hg status --cwd a/1 ..
115 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
115 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
116 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_a\x1b[0m (esc)
116 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_a\x1b[0m (esc)
117
117
118 $ hg status --cwd b/1
118 $ hg status --cwd b/1
119 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
119 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
120 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
120 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
121 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
121 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
122 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
122 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
123 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
123 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
124 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
124 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
125 $ hg status --cwd b/1 .
125 $ hg status --cwd b/1 .
126 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
126 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
127 $ hg status --cwd b/1 ..
127 $ hg status --cwd b/1 ..
128 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
128 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
129 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../2/in_b_2\x1b[0m (esc)
129 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../2/in_b_2\x1b[0m (esc)
130 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
130 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
131
131
132 $ hg status --cwd b/2
132 $ hg status --cwd b/2
133 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
133 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
134 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
134 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
135 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
135 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
136 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
136 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
137 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
137 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
138 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
138 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
139 $ hg status --cwd b/2 .
139 $ hg status --cwd b/2 .
140 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
140 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
141 $ hg status --cwd b/2 ..
141 $ hg status --cwd b/2 ..
142 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../1/in_b_1\x1b[0m (esc)
142 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../1/in_b_1\x1b[0m (esc)
143 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
143 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
144 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
144 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
145
145
146 Make sure --color=never works
146 Make sure --color=never works
147 $ hg status --color=never
147 $ hg status --color=never
148 ? a/1/in_a_1
148 ? a/1/in_a_1
149 ? a/in_a
149 ? a/in_a
150 ? b/1/in_b_1
150 ? b/1/in_b_1
151 ? b/2/in_b_2
151 ? b/2/in_b_2
152 ? b/in_b
152 ? b/in_b
153 ? in_root
153 ? in_root
154
154
155 Make sure ui.formatted=False works
155 Make sure ui.formatted=False works
156 $ hg status --color=auto --config ui.formatted=False
156 $ hg status --color=auto --config ui.formatted=False
157 ? a/1/in_a_1
157 ? a/1/in_a_1
158 ? a/in_a
158 ? a/in_a
159 ? b/1/in_b_1
159 ? b/1/in_b_1
160 ? b/2/in_b_2
160 ? b/2/in_b_2
161 ? b/in_b
161 ? b/in_b
162 ? in_root
162 ? in_root
163
163
164 $ cd ..
164 $ cd ..
165
165
166 $ hg init repo2
166 $ hg init repo2
167 $ cd repo2
167 $ cd repo2
168 $ touch modified removed deleted ignored
168 $ touch modified removed deleted ignored
169 $ echo "^ignored$" > .hgignore
169 $ echo "^ignored$" > .hgignore
170 $ hg ci -A -m 'initial checkin'
170 $ hg ci -A -m 'initial checkin'
171 \x1b[0;32madding .hgignore\x1b[0m (esc)
171 \x1b[0;32madding .hgignore\x1b[0m (esc)
172 \x1b[0;32madding deleted\x1b[0m (esc)
172 \x1b[0;32madding deleted\x1b[0m (esc)
173 \x1b[0;32madding modified\x1b[0m (esc)
173 \x1b[0;32madding modified\x1b[0m (esc)
174 \x1b[0;32madding removed\x1b[0m (esc)
174 \x1b[0;32madding removed\x1b[0m (esc)
175 $ hg log --color=debug
175 $ hg log --color=debug
176 [log.changeset changeset.draft|changeset: 0:389aef86a55e]
176 [log.changeset changeset.draft|changeset: 0:389aef86a55e]
177 [log.tag|tag: tip]
177 [log.tag|tag: tip]
178 [log.user|user: test]
178 [log.user|user: test]
179 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
179 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
180 [log.summary|summary: initial checkin]
180 [log.summary|summary: initial checkin]
181
181
182 $ hg log -Tcompact --color=debug
182 $ hg log -Tcompact --color=debug
183 [log.changeset changeset.draft|0][tip] [log.node|389aef86a55e] [log.date|1970-01-01 00:00 +0000] [log.user|test]
183 [log.changeset changeset.draft|0][tip] [log.node|389aef86a55e] [log.date|1970-01-01 00:00 +0000] [log.user|test]
184 [ui.note log.description|initial checkin]
184 [ui.note log.description|initial checkin]
185
185
186 Labels on empty strings should not be displayed, labels on custom
186 Labels on empty strings should not be displayed, labels on custom
187 templates should be.
187 templates should be.
188
188
189 $ hg log --color=debug -T '{label("my.label",author)}\n{label("skipped.label","")}'
189 $ hg log --color=debug -T '{label("my.label",author)}\n{label("skipped.label","")}'
190 [my.label|test]
190 [my.label|test]
191 $ touch modified added unknown ignored
191 $ touch modified added unknown ignored
192 $ hg add added
192 $ hg add added
193 $ hg remove removed
193 $ hg remove removed
194 $ rm deleted
194 $ rm deleted
195
195
196 hg status:
196 hg status:
197
197
198 $ hg status
198 $ hg status
199 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
199 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
200 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
200 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
201 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
201 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
202 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
202 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
203
203
204 hg status modified added removed deleted unknown never-existed ignored:
204 hg status modified added removed deleted unknown never-existed ignored:
205
205
206 $ hg status modified added removed deleted unknown never-existed ignored
206 $ hg status modified added removed deleted unknown never-existed ignored
207 never-existed: * (glob)
207 never-existed: * (glob)
208 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
208 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
209 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
209 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
210 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
210 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
211 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
211 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
212
212
213 $ hg copy modified copied
213 $ hg copy modified copied
214
214
215 hg status -C:
215 hg status -C:
216
216
217 $ hg status -C
217 $ hg status -C
218 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
218 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
219 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
219 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
220 \x1b[0;0m modified\x1b[0m (esc)
220 \x1b[0;0m modified\x1b[0m (esc)
221 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
221 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
222 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
222 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
223 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
223 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
224
224
225 hg status -A:
225 hg status -A:
226
226
227 $ hg status -A
227 $ hg status -A
228 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
228 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
229 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
229 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
230 \x1b[0;0m modified\x1b[0m (esc)
230 \x1b[0;0m modified\x1b[0m (esc)
231 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
231 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
232 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
232 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
233 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
233 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
234 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignored\x1b[0m (esc)
234 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignored\x1b[0m (esc)
235 \x1b[0;0mC \x1b[0m\x1b[0;0m.hgignore\x1b[0m (esc)
235 \x1b[0;0mC \x1b[0m\x1b[0;0m.hgignore\x1b[0m (esc)
236 \x1b[0;0mC \x1b[0m\x1b[0;0mmodified\x1b[0m (esc)
236 \x1b[0;0mC \x1b[0m\x1b[0;0mmodified\x1b[0m (esc)
237
237
238
238
239 hg status -A (with terminfo color):
239 hg status -A (with terminfo color):
240
240
241 #if tic
241 #if tic
242
242
243 $ tic -o "$TESTTMP/terminfo" "$TESTDIR/hgterm.ti"
243 $ tic -o "$TESTTMP/terminfo" "$TESTDIR/hgterm.ti"
244 $ ln -s "$TESTTMP/terminfo" "$TESTTMP/terminfo.cdb"
244 $ ln -s "$TESTTMP/terminfo" "$TESTTMP/terminfo.cdb"
245 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo -A
245 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo -A
246 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1madded\x1b[30m (esc)
246 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1madded\x1b[30m (esc)
247 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1mcopied\x1b[30m (esc)
247 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1mcopied\x1b[30m (esc)
248 \x1b[30m\x1b[30m modified\x1b[30m (esc)
248 \x1b[30m\x1b[30m modified\x1b[30m (esc)
249 \x1b[30m\x1b[31m\x1b[1mR \x1b[30m\x1b[30m\x1b[31m\x1b[1mremoved\x1b[30m (esc)
249 \x1b[30m\x1b[31m\x1b[1mR \x1b[30m\x1b[30m\x1b[31m\x1b[1mremoved\x1b[30m (esc)
250 \x1b[30m\x1b[36m\x1b[1m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[1m\x1b[4mdeleted\x1b[30m (esc)
250 \x1b[30m\x1b[36m\x1b[1m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[1m\x1b[4mdeleted\x1b[30m (esc)
251 \x1b[30m\x1b[35m\x1b[1m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[1m\x1b[4munknown\x1b[30m (esc)
251 \x1b[30m\x1b[35m\x1b[1m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[1m\x1b[4munknown\x1b[30m (esc)
252 \x1b[30m\x1b[30m\x1b[1mI \x1b[30m\x1b[30m\x1b[30m\x1b[1mignored\x1b[30m (esc)
252 \x1b[30m\x1b[30m\x1b[1mI \x1b[30m\x1b[30m\x1b[30m\x1b[1mignored\x1b[30m (esc)
253 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30m.hgignore\x1b[30m (esc)
253 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30m.hgignore\x1b[30m (esc)
254 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30mmodified\x1b[30m (esc)
254 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30mmodified\x1b[30m (esc)
255
255
256 The user can define effects with raw terminfo codes:
256 The user can define effects with raw terminfo codes:
257
257
258 $ cat <<EOF >> $HGRCPATH
258 $ cat <<EOF >> $HGRCPATH
259 > # Completely bogus code for dim
259 > # Completely bogus code for dim
260 > terminfo.dim = \E[88m
260 > terminfo.dim = \E[88m
261 > # We can override what's in the terminfo database, too
261 > # We can override what's in the terminfo database, too
262 > terminfo.bold = \E[2m
262 > terminfo.bold = \E[2m
263 > EOF
263 > EOF
264 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo --config color.status.clean=dim -A
264 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo --config color.status.clean=dim -A
265 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2madded\x1b[30m (esc)
265 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2madded\x1b[30m (esc)
266 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2mcopied\x1b[30m (esc)
266 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2mcopied\x1b[30m (esc)
267 \x1b[30m\x1b[30m modified\x1b[30m (esc)
267 \x1b[30m\x1b[30m modified\x1b[30m (esc)
268 \x1b[30m\x1b[31m\x1b[2mR \x1b[30m\x1b[30m\x1b[31m\x1b[2mremoved\x1b[30m (esc)
268 \x1b[30m\x1b[31m\x1b[2mR \x1b[30m\x1b[30m\x1b[31m\x1b[2mremoved\x1b[30m (esc)
269 \x1b[30m\x1b[36m\x1b[2m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[2m\x1b[4mdeleted\x1b[30m (esc)
269 \x1b[30m\x1b[36m\x1b[2m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[2m\x1b[4mdeleted\x1b[30m (esc)
270 \x1b[30m\x1b[35m\x1b[2m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[2m\x1b[4munknown\x1b[30m (esc)
270 \x1b[30m\x1b[35m\x1b[2m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[2m\x1b[4munknown\x1b[30m (esc)
271 \x1b[30m\x1b[30m\x1b[2mI \x1b[30m\x1b[30m\x1b[30m\x1b[2mignored\x1b[30m (esc)
271 \x1b[30m\x1b[30m\x1b[2mI \x1b[30m\x1b[30m\x1b[30m\x1b[2mignored\x1b[30m (esc)
272 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88m.hgignore\x1b[30m (esc)
272 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88m.hgignore\x1b[30m (esc)
273 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88mmodified\x1b[30m (esc)
273 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88mmodified\x1b[30m (esc)
274
274
275 #endif
275 #endif
276
276
277
277
278 $ echo "^ignoreddir$" > .hgignore
278 $ echo "^ignoreddir$" > .hgignore
279 $ mkdir ignoreddir
279 $ mkdir ignoreddir
280 $ touch ignoreddir/file
280 $ touch ignoreddir/file
281
281
282 hg status ignoreddir/file:
282 hg status ignoreddir/file:
283
283
284 $ hg status ignoreddir/file
284 $ hg status ignoreddir/file
285
285
286 hg status -i ignoreddir/file:
286 hg status -i ignoreddir/file:
287
287
288 $ hg status -i ignoreddir/file
288 $ hg status -i ignoreddir/file
289 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignoreddir/file\x1b[0m (esc)
289 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignoreddir/file\x1b[0m (esc)
290 $ cd ..
290 $ cd ..
291
291
292 check 'status -q' and some combinations
292 check 'status -q' and some combinations
293
293
294 $ hg init repo3
294 $ hg init repo3
295 $ cd repo3
295 $ cd repo3
296 $ touch modified removed deleted ignored
296 $ touch modified removed deleted ignored
297 $ echo "^ignored$" > .hgignore
297 $ echo "^ignored$" > .hgignore
298 $ hg commit -A -m 'initial checkin'
298 $ hg commit -A -m 'initial checkin'
299 \x1b[0;32madding .hgignore\x1b[0m (esc)
299 \x1b[0;32madding .hgignore\x1b[0m (esc)
300 \x1b[0;32madding deleted\x1b[0m (esc)
300 \x1b[0;32madding deleted\x1b[0m (esc)
301 \x1b[0;32madding modified\x1b[0m (esc)
301 \x1b[0;32madding modified\x1b[0m (esc)
302 \x1b[0;32madding removed\x1b[0m (esc)
302 \x1b[0;32madding removed\x1b[0m (esc)
303 $ touch added unknown ignored
303 $ touch added unknown ignored
304 $ hg add added
304 $ hg add added
305 $ echo "test" >> modified
305 $ echo "test" >> modified
306 $ hg remove removed
306 $ hg remove removed
307 $ rm deleted
307 $ rm deleted
308 $ hg copy modified copied
308 $ hg copy modified copied
309
309
310 test unknown color
310 test unknown color
311
311
312 $ hg --config color.status.modified=periwinkle status
312 $ hg --config color.status.modified=periwinkle status
313 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
313 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
314 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
314 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (no-rhg !)
315 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
315 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (no-rhg !)
316 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (rhg !)
317 M modified
316 M modified
318 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
317 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
319 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
318 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
320 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
319 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
321 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
320 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
322 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
321 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
323
322
324 Run status with 2 different flags.
323 Run status with 2 different flags.
325 Check if result is the same or different.
324 Check if result is the same or different.
326 If result is not as expected, raise error
325 If result is not as expected, raise error
327
326
328 $ assert() {
327 $ assert() {
329 > hg status $1 > ../a
328 > hg status $1 > ../a
330 > hg status $2 > ../b
329 > hg status $2 > ../b
331 > if diff ../a ../b > /dev/null; then
330 > if diff ../a ../b > /dev/null; then
332 > out=0
331 > out=0
333 > else
332 > else
334 > out=1
333 > out=1
335 > fi
334 > fi
336 > if [ $3 -eq 0 ]; then
335 > if [ $3 -eq 0 ]; then
337 > df="same"
336 > df="same"
338 > else
337 > else
339 > df="different"
338 > df="different"
340 > fi
339 > fi
341 > if [ $out -ne $3 ]; then
340 > if [ $out -ne $3 ]; then
342 > echo "Error on $1 and $2, should be $df."
341 > echo "Error on $1 and $2, should be $df."
343 > fi
342 > fi
344 > }
343 > }
345
344
346 assert flag1 flag2 [0-same | 1-different]
345 assert flag1 flag2 [0-same | 1-different]
347
346
348 $ assert "-q" "-mard" 0
347 $ assert "-q" "-mard" 0
349 $ assert "-A" "-marduicC" 0
348 $ assert "-A" "-marduicC" 0
350 $ assert "-qA" "-mardcC" 0
349 $ assert "-qA" "-mardcC" 0
351 $ assert "-qAui" "-A" 0
350 $ assert "-qAui" "-A" 0
352 $ assert "-qAu" "-marducC" 0
351 $ assert "-qAu" "-marducC" 0
353 $ assert "-qAi" "-mardicC" 0
352 $ assert "-qAi" "-mardicC" 0
354 $ assert "-qu" "-u" 0
353 $ assert "-qu" "-u" 0
355 $ assert "-q" "-u" 1
354 $ assert "-q" "-u" 1
356 $ assert "-m" "-a" 1
355 $ assert "-m" "-a" 1
357 $ assert "-r" "-d" 1
356 $ assert "-r" "-d" 1
358 $ cd ..
357 $ cd ..
359
358
360 test 'resolve -l'
359 test 'resolve -l'
361
360
362 $ hg init repo4
361 $ hg init repo4
363 $ cd repo4
362 $ cd repo4
364 $ echo "file a" > a
363 $ echo "file a" > a
365 $ echo "file b" > b
364 $ echo "file b" > b
366 $ hg add a b
365 $ hg add a b
367 $ hg commit -m "initial"
366 $ hg commit -m "initial"
368 $ echo "file a change 1" > a
367 $ echo "file a change 1" > a
369 $ echo "file b change 1" > b
368 $ echo "file b change 1" > b
370 $ hg commit -m "head 1"
369 $ hg commit -m "head 1"
371 $ hg update 0
370 $ hg update 0
372 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 $ echo "file a change 2" > a
372 $ echo "file a change 2" > a
374 $ echo "file b change 2" > b
373 $ echo "file b change 2" > b
375 $ hg commit -m "head 2"
374 $ hg commit -m "head 2"
376 created new head
375 created new head
377 $ hg merge
376 $ hg merge
378 merging a
377 merging a
379 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
378 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
380 merging b
379 merging b
381 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
380 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
382 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
381 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
383 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
382 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
384 [1]
383 [1]
385 $ hg resolve -m b
384 $ hg resolve -m b
386
385
387 hg resolve with one unresolved, one resolved:
386 hg resolve with one unresolved, one resolved:
388
387
389 $ hg resolve -l
388 $ hg resolve -l
390 \x1b[0;31;1mU \x1b[0m\x1b[0;31;1ma\x1b[0m (esc)
389 \x1b[0;31;1mU \x1b[0m\x1b[0;31;1ma\x1b[0m (esc)
391 \x1b[0;32;1mR \x1b[0m\x1b[0;32;1mb\x1b[0m (esc)
390 \x1b[0;32;1mR \x1b[0m\x1b[0;32;1mb\x1b[0m (esc)
392
391
393 color coding of error message with current availability of curses
392 color coding of error message with current availability of curses
394
393
395 $ hg unknowncommand > /dev/null
394 $ hg unknowncommand > /dev/null
396 hg: unknown command 'unknowncommand'
395 hg: unknown command 'unknowncommand'
397 (use 'hg help' for a list of commands)
396 (use 'hg help' for a list of commands)
398 [10]
397 [10]
399
398
400 color coding of error message without curses
399 color coding of error message without curses
401
400
402 $ echo 'raise ImportError' > curses.py
401 $ echo 'raise ImportError' > curses.py
403 $ PYTHONPATH=`pwd`:$PYTHONPATH hg unknowncommand > /dev/null
402 $ PYTHONPATH=`pwd`:$PYTHONPATH hg unknowncommand > /dev/null
404 hg: unknown command 'unknowncommand'
403 hg: unknown command 'unknowncommand'
405 (use 'hg help' for a list of commands)
404 (use 'hg help' for a list of commands)
406 [10]
405 [10]
407
406
408 $ cd ..
407 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now