##// END OF EJS Templates
rhg: Fall back to Python if verbose status is requested by config...
Simon Sapin -
r49344:47f2a82a default
parent child Browse files
Show More
@@ -1,527 +1,538 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::error::CommandError;
9 9 use crate::ui::Ui;
10 10 use crate::utils::path_utils::RelativizePaths;
11 11 use clap::{Arg, SubCommand};
12 12 use format_bytes::format_bytes;
13 13 use hg;
14 14 use hg::config::Config;
15 15 use hg::dirstate::has_exec_bit;
16 16 use hg::dirstate::status::StatusPath;
17 17 use hg::dirstate::TruncatedTimestamp;
18 18 use hg::dirstate::RANGE_MASK_31BIT;
19 19 use hg::errors::{HgError, IoResultExt};
20 20 use hg::lock::LockError;
21 21 use hg::manifest::Manifest;
22 22 use hg::matchers::AlwaysMatcher;
23 23 use hg::repo::Repo;
24 24 use hg::utils::files::get_bytes_from_os_string;
25 25 use hg::utils::files::get_bytes_from_path;
26 26 use hg::utils::files::get_path_from_bytes;
27 27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 28 use hg::StatusOptions;
29 29 use log::info;
30 30 use std::io;
31 31 use std::path::PathBuf;
32 32
33 33 pub const HELP_TEXT: &str = "
34 34 Show changed files in the working directory
35 35
36 36 This is a pure Rust version of `hg status`.
37 37
38 38 Some options might be missing, check the list below.
39 39 ";
40 40
41 41 pub fn args() -> clap::App<'static, 'static> {
42 42 SubCommand::with_name("status")
43 43 .alias("st")
44 44 .about(HELP_TEXT)
45 45 .arg(
46 46 Arg::with_name("all")
47 47 .help("show status of all files")
48 48 .short("-A")
49 49 .long("--all"),
50 50 )
51 51 .arg(
52 52 Arg::with_name("modified")
53 53 .help("show only modified files")
54 54 .short("-m")
55 55 .long("--modified"),
56 56 )
57 57 .arg(
58 58 Arg::with_name("added")
59 59 .help("show only added files")
60 60 .short("-a")
61 61 .long("--added"),
62 62 )
63 63 .arg(
64 64 Arg::with_name("removed")
65 65 .help("show only removed files")
66 66 .short("-r")
67 67 .long("--removed"),
68 68 )
69 69 .arg(
70 70 Arg::with_name("clean")
71 71 .help("show only clean files")
72 72 .short("-c")
73 73 .long("--clean"),
74 74 )
75 75 .arg(
76 76 Arg::with_name("deleted")
77 77 .help("show only deleted files")
78 78 .short("-d")
79 79 .long("--deleted"),
80 80 )
81 81 .arg(
82 82 Arg::with_name("unknown")
83 83 .help("show only unknown (not tracked) files")
84 84 .short("-u")
85 85 .long("--unknown"),
86 86 )
87 87 .arg(
88 88 Arg::with_name("ignored")
89 89 .help("show only ignored files")
90 90 .short("-i")
91 91 .long("--ignored"),
92 92 )
93 93 .arg(
94 94 Arg::with_name("copies")
95 95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 96 .short("-C")
97 97 .long("--copies"),
98 98 )
99 99 .arg(
100 100 Arg::with_name("no-status")
101 101 .help("hide status prefix")
102 102 .short("-n")
103 103 .long("--no-status"),
104 104 )
105 105 }
106 106
107 107 /// Pure data type allowing the caller to specify file states to display
108 108 #[derive(Copy, Clone, Debug)]
109 109 pub struct DisplayStates {
110 110 pub modified: bool,
111 111 pub added: bool,
112 112 pub removed: bool,
113 113 pub clean: bool,
114 114 pub deleted: bool,
115 115 pub unknown: bool,
116 116 pub ignored: bool,
117 117 }
118 118
119 119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 120 modified: true,
121 121 added: true,
122 122 removed: true,
123 123 clean: false,
124 124 deleted: true,
125 125 unknown: true,
126 126 ignored: false,
127 127 };
128 128
129 129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 130 modified: true,
131 131 added: true,
132 132 removed: true,
133 133 clean: true,
134 134 deleted: true,
135 135 unknown: true,
136 136 ignored: true,
137 137 };
138 138
139 139 impl DisplayStates {
140 140 pub fn is_empty(&self) -> bool {
141 141 !(self.modified
142 142 || self.added
143 143 || self.removed
144 144 || self.clean
145 145 || self.deleted
146 146 || self.unknown
147 147 || self.ignored)
148 148 }
149 149 }
150 150
151 151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 152 let status_enabled_default = false;
153 153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
154 154 if !status_enabled.unwrap_or(status_enabled_default) {
155 155 return Err(CommandError::unsupported(
156 156 "status is experimental in rhg (enable it with 'rhg.status = true' \
157 157 or enable fallback with 'rhg.on-unsupported = fallback')"
158 158 ));
159 159 }
160 160
161 161 // TODO: lift these limitations
162 162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
163 163 return Err(CommandError::unsupported(
164 164 "ui.tweakdefaults is not yet supported with rhg status",
165 165 ));
166 166 }
167 167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
168 168 return Err(CommandError::unsupported(
169 169 "ui.statuscopies is not yet supported with rhg status",
170 170 ));
171 171 }
172 172 if invocation
173 173 .config
174 174 .get(b"commands", b"status.terse")
175 175 .is_some()
176 176 {
177 177 return Err(CommandError::unsupported(
178 178 "status.terse is not yet supported with rhg status",
179 179 ));
180 180 }
181 181
182 182 let ui = invocation.ui;
183 183 let config = invocation.config;
184 184 let args = invocation.subcommand_args;
185
186 let verbose = !ui.plain()
187 && !args.is_present("print0")
188 && (config.get_bool(b"ui", b"verbose")?
189 || config.get_bool(b"commands", b"status.verbose")?);
190 if verbose {
191 return Err(CommandError::unsupported(
192 "verbose status is not supported yet",
193 ));
194 }
195
185 196 let all = args.is_present("all");
186 197 let display_states = if all {
187 198 // TODO when implementing `--quiet`: it excludes clean files
188 199 // from `--all`
189 200 ALL_DISPLAY_STATES
190 201 } else {
191 202 let requested = DisplayStates {
192 203 modified: args.is_present("modified"),
193 204 added: args.is_present("added"),
194 205 removed: args.is_present("removed"),
195 206 clean: args.is_present("clean"),
196 207 deleted: args.is_present("deleted"),
197 208 unknown: args.is_present("unknown"),
198 209 ignored: args.is_present("ignored"),
199 210 };
200 211 if requested.is_empty() {
201 212 DEFAULT_DISPLAY_STATES
202 213 } else {
203 214 requested
204 215 }
205 216 };
206 217 let no_status = args.is_present("no-status");
207 218 let list_copies = all
208 219 || args.is_present("copies")
209 220 || config.get_bool(b"ui", b"statuscopies")?;
210 221
211 222 let repo = invocation.repo?;
212 223
213 224 if repo.has_sparse() || repo.has_narrow() {
214 225 return Err(CommandError::unsupported(
215 226 "rhg status is not supported for sparse checkouts or narrow clones yet"
216 227 ));
217 228 }
218 229
219 230 let mut dmap = repo.dirstate_map_mut()?;
220 231
221 232 let options = StatusOptions {
222 233 // we're currently supporting file systems with exec flags only
223 234 // anyway
224 235 check_exec: true,
225 236 list_clean: display_states.clean,
226 237 list_unknown: display_states.unknown,
227 238 list_ignored: display_states.ignored,
228 239 list_copies,
229 240 collect_traversed_dirs: false,
230 241 };
231 242 let (mut ds_status, pattern_warnings) = dmap.status(
232 243 &AlwaysMatcher,
233 244 repo.working_directory_path().to_owned(),
234 245 ignore_files(repo, config),
235 246 options,
236 247 )?;
237 248 for warning in pattern_warnings {
238 249 match warning {
239 250 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
240 251 .write_stderr(&format_bytes!(
241 252 b"{}: ignoring invalid syntax '{}'\n",
242 253 get_bytes_from_path(path),
243 254 &*syntax
244 255 ))?,
245 256 hg::PatternFileWarning::NoSuchFile(path) => {
246 257 let path = if let Ok(relative) =
247 258 path.strip_prefix(repo.working_directory_path())
248 259 {
249 260 relative
250 261 } else {
251 262 &*path
252 263 };
253 264 ui.write_stderr(&format_bytes!(
254 265 b"skipping unreadable pattern file '{}': \
255 266 No such file or directory\n",
256 267 get_bytes_from_path(path),
257 268 ))?
258 269 }
259 270 }
260 271 }
261 272
262 273 for (path, error) in ds_status.bad {
263 274 let error = match error {
264 275 hg::BadMatch::OsError(code) => {
265 276 std::io::Error::from_raw_os_error(code).to_string()
266 277 }
267 278 hg::BadMatch::BadType(ty) => {
268 279 format!("unsupported file type (type is {})", ty)
269 280 }
270 281 };
271 282 ui.write_stderr(&format_bytes!(
272 283 b"{}: {}\n",
273 284 path.as_bytes(),
274 285 error.as_bytes()
275 286 ))?
276 287 }
277 288 if !ds_status.unsure.is_empty() {
278 289 info!(
279 290 "Files to be rechecked by retrieval from filelog: {:?}",
280 291 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
281 292 );
282 293 }
283 294 let mut fixup = Vec::new();
284 295 if !ds_status.unsure.is_empty()
285 296 && (display_states.modified || display_states.clean)
286 297 {
287 298 let p1 = repo.dirstate_parents()?.p1;
288 299 let manifest = repo.manifest_for_node(p1).map_err(|e| {
289 300 CommandError::from((e, &*format!("{:x}", p1.short())))
290 301 })?;
291 302 for to_check in ds_status.unsure {
292 303 if unsure_is_modified(repo, &manifest, &to_check.path)? {
293 304 if display_states.modified {
294 305 ds_status.modified.push(to_check);
295 306 }
296 307 } else {
297 308 if display_states.clean {
298 309 ds_status.clean.push(to_check.clone());
299 310 }
300 311 fixup.push(to_check.path.into_owned())
301 312 }
302 313 }
303 314 }
304 315 let relative_paths = (!ui.plain())
305 316 && config
306 317 .get_option(b"commands", b"status.relative")?
307 318 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
308 319 let output = DisplayStatusPaths {
309 320 ui,
310 321 no_status,
311 322 relativize: if relative_paths {
312 323 Some(RelativizePaths::new(repo)?)
313 324 } else {
314 325 None
315 326 },
316 327 };
317 328 if display_states.modified {
318 329 output.display(b"M", ds_status.modified)?;
319 330 }
320 331 if display_states.added {
321 332 output.display(b"A", ds_status.added)?;
322 333 }
323 334 if display_states.removed {
324 335 output.display(b"R", ds_status.removed)?;
325 336 }
326 337 if display_states.deleted {
327 338 output.display(b"!", ds_status.deleted)?;
328 339 }
329 340 if display_states.unknown {
330 341 output.display(b"?", ds_status.unknown)?;
331 342 }
332 343 if display_states.ignored {
333 344 output.display(b"I", ds_status.ignored)?;
334 345 }
335 346 if display_states.clean {
336 347 output.display(b"C", ds_status.clean)?;
337 348 }
338 349
339 350 let mut dirstate_write_needed = ds_status.dirty;
340 351 let filesystem_time_at_status_start =
341 352 ds_status.filesystem_time_at_status_start;
342 353
343 354 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
344 355 && !dirstate_write_needed
345 356 {
346 357 // Nothing to update
347 358 return Ok(());
348 359 }
349 360
350 361 // Update the dirstate on disk if we can
351 362 let with_lock_result =
352 363 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
353 364 if let Some(mtime_boundary) = filesystem_time_at_status_start {
354 365 for hg_path in fixup {
355 366 use std::os::unix::fs::MetadataExt;
356 367 let fs_path = hg_path_to_path_buf(&hg_path)
357 368 .expect("HgPath conversion");
358 369 // Specifically do not reuse `fs_metadata` from
359 370 // `unsure_is_clean` which was needed before reading
360 371 // contents. Here we access metadata again after reading
361 372 // content, in case it changed in the meantime.
362 373 let fs_metadata = repo
363 374 .working_directory_vfs()
364 375 .symlink_metadata(&fs_path)?;
365 376 if let Some(mtime) =
366 377 TruncatedTimestamp::for_reliable_mtime_of(
367 378 &fs_metadata,
368 379 &mtime_boundary,
369 380 )
370 381 .when_reading_file(&fs_path)?
371 382 {
372 383 let mode = fs_metadata.mode();
373 384 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
374 385 let mut entry = dmap
375 386 .get(&hg_path)?
376 387 .expect("ambiguous file not in dirstate");
377 388 entry.set_clean(mode, size, mtime);
378 389 dmap.add_file(&hg_path, entry)?;
379 390 dirstate_write_needed = true
380 391 }
381 392 }
382 393 }
383 394 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
384 395 if dirstate_write_needed {
385 396 repo.write_dirstate()?
386 397 }
387 398 Ok(())
388 399 });
389 400 match with_lock_result {
390 401 Ok(closure_result) => closure_result?,
391 402 Err(LockError::AlreadyHeld) => {
392 403 // Not updating the dirstate is not ideal but not critical:
393 404 // don’t keep our caller waiting until some other Mercurial
394 405 // process releases the lock.
395 406 }
396 407 Err(LockError::Other(HgError::IoError { error, .. }))
397 408 if error.kind() == io::ErrorKind::PermissionDenied =>
398 409 {
399 410 // `hg status` on a read-only repository is fine
400 411 }
401 412 Err(LockError::Other(error)) => {
402 413 // Report other I/O errors
403 414 Err(error)?
404 415 }
405 416 }
406 417 Ok(())
407 418 }
408 419
409 420 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
410 421 let mut ignore_files = Vec::new();
411 422 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
412 423 if repo_ignore.exists() {
413 424 ignore_files.push(repo_ignore)
414 425 }
415 426 for (key, value) in config.iter_section(b"ui") {
416 427 if key == b"ignore" || key.starts_with(b"ignore.") {
417 428 let path = get_path_from_bytes(value);
418 429 // TODO:Β expand "~/" and environment variable here, like Python
419 430 // does with `os.path.expanduser` and `os.path.expandvars`
420 431
421 432 let joined = repo.working_directory_path().join(path);
422 433 ignore_files.push(joined);
423 434 }
424 435 }
425 436 ignore_files
426 437 }
427 438
428 439 struct DisplayStatusPaths<'a> {
429 440 ui: &'a Ui,
430 441 no_status: bool,
431 442 relativize: Option<RelativizePaths>,
432 443 }
433 444
434 445 impl DisplayStatusPaths<'_> {
435 446 // Probably more elegant to use a Deref or Borrow trait rather than
436 447 // harcode HgPathBuf, but probably not really useful at this point
437 448 fn display(
438 449 &self,
439 450 status_prefix: &[u8],
440 451 mut paths: Vec<StatusPath<'_>>,
441 452 ) -> Result<(), CommandError> {
442 453 paths.sort_unstable();
443 454 for StatusPath { path, copy_source } in paths {
444 455 let relative;
445 456 let path = if let Some(relativize) = &self.relativize {
446 457 relative = relativize.relativize(&path);
447 458 &*relative
448 459 } else {
449 460 path.as_bytes()
450 461 };
451 462 // TODO optim, probably lots of unneeded copies here, especially
452 463 // if out stream is buffered
453 464 if self.no_status {
454 465 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
455 466 } else {
456 467 self.ui.write_stdout(&format_bytes!(
457 468 b"{} {}\n",
458 469 status_prefix,
459 470 path
460 471 ))?
461 472 }
462 473 if let Some(source) = copy_source {
463 474 self.ui.write_stdout(&format_bytes!(
464 475 b" {}\n",
465 476 source.as_bytes()
466 477 ))?
467 478 }
468 479 }
469 480 Ok(())
470 481 }
471 482 }
472 483
473 484 /// Check if a file is modified by comparing actual repo store and file system.
474 485 ///
475 486 /// This meant to be used for those that the dirstate cannot resolve, due
476 487 /// to time resolution limits.
477 488 fn unsure_is_modified(
478 489 repo: &Repo,
479 490 manifest: &Manifest,
480 491 hg_path: &HgPath,
481 492 ) -> Result<bool, HgError> {
482 493 let vfs = repo.working_directory_vfs();
483 494 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
484 495 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
485 496 let is_symlink = fs_metadata.file_type().is_symlink();
486 497 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
487 498 // dirstate
488 499 let fs_flags = if is_symlink {
489 500 Some(b'l')
490 501 } else if has_exec_bit(&fs_metadata) {
491 502 Some(b'x')
492 503 } else {
493 504 None
494 505 };
495 506
496 507 let entry = manifest
497 508 .find_by_path(hg_path)?
498 509 .expect("ambgious file not in p1");
499 510 if entry.flags != fs_flags {
500 511 return Ok(true);
501 512 }
502 513 let filelog = repo.filelog(hg_path)?;
503 514 let fs_len = fs_metadata.len();
504 515 // TODO: check `fs_len` here like below, but based on
505 516 // `RevlogEntry::uncompressed_len` without decompressing the full filelog
506 517 // contents where possible. This is only valid if the revlog data does not
507 518 // contain metadata. See how Python’s `revlog.rawsize` calls
508 519 // `storageutil.filerevisioncopied`.
509 520 // (Maybe also check for content-modifying flags? See `revlog.size`.)
510 521 let filelog_entry =
511 522 filelog.data_for_node(entry.node_id()?).map_err(|_| {
512 523 HgError::corrupted("filelog missing node from manifest")
513 524 })?;
514 525 let contents_in_p1 = filelog_entry.data()?;
515 526 if contents_in_p1.len() as u64 != fs_len {
516 527 // No need to read the file contents:
517 528 // it cannot be equal if it has a different length.
518 529 return Ok(true);
519 530 }
520 531
521 532 let fs_contents = if is_symlink {
522 533 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
523 534 } else {
524 535 vfs.read(fs_path)?
525 536 };
526 537 Ok(contents_in_p1 != &*fs_contents)
527 538 }
@@ -1,132 +1,132 b''
1 1 use format_bytes::format_bytes;
2 2 use std::borrow::Cow;
3 3 use std::env;
4 4 use std::io;
5 5 use std::io::{ErrorKind, Write};
6 6
7 7 #[derive(Debug)]
8 8 pub struct Ui {
9 9 stdout: std::io::Stdout,
10 10 stderr: std::io::Stderr,
11 11 }
12 12
13 13 /// The kind of user interface error
14 14 pub enum UiError {
15 15 /// The standard output stream cannot be written to
16 16 StdoutError(io::Error),
17 17 /// The standard error stream cannot be written to
18 18 StderrError(io::Error),
19 19 }
20 20
21 21 /// The commandline user interface
22 22 impl Ui {
23 23 pub fn new() -> Self {
24 24 Ui {
25 25 stdout: std::io::stdout(),
26 26 stderr: std::io::stderr(),
27 27 }
28 28 }
29 29
30 30 /// Returns a buffered handle on stdout for faster batch printing
31 31 /// operations.
32 32 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
33 33 StdoutBuffer::new(self.stdout.lock())
34 34 }
35 35
36 36 /// Write bytes to stdout
37 37 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
38 38 let mut stdout = self.stdout.lock();
39 39
40 40 stdout.write_all(bytes).or_else(handle_stdout_error)?;
41 41
42 42 stdout.flush().or_else(handle_stdout_error)
43 43 }
44 44
45 45 /// Write bytes to stderr
46 46 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
47 47 let mut stderr = self.stderr.lock();
48 48
49 49 stderr.write_all(bytes).or_else(handle_stderr_error)?;
50 50
51 51 stderr.flush().or_else(handle_stderr_error)
52 52 }
53 53
54 /// is plain mode active
54 /// Return whether plain mode is active.
55 55 ///
56 56 /// Plain mode means that all configuration variables which affect
57 57 /// the behavior and output of Mercurial should be
58 58 /// ignored. Additionally, the output should be stable,
59 59 /// reproducible and suitable for use in scripts or applications.
60 60 ///
61 61 /// The only way to trigger plain mode is by setting either the
62 62 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
63 63 ///
64 64 /// The return value can either be
65 65 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
66 66 /// - False if feature is disabled by default and not included in HGPLAIN
67 67 /// - True otherwise
68 68 pub fn plain(&self) -> bool {
69 69 // TODO: add support for HGPLAINEXCEPT
70 70 env::var_os("HGPLAIN").is_some()
71 71 }
72 72 }
73 73
74 74 /// A buffered stdout writer for faster batch printing operations.
75 75 pub struct StdoutBuffer<W: Write> {
76 76 buf: io::BufWriter<W>,
77 77 }
78 78
79 79 impl<W: Write> StdoutBuffer<W> {
80 80 pub fn new(writer: W) -> Self {
81 81 let buf = io::BufWriter::new(writer);
82 82 Self { buf }
83 83 }
84 84
85 85 /// Write bytes to stdout buffer
86 86 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
87 87 self.buf.write_all(bytes).or_else(handle_stdout_error)
88 88 }
89 89
90 90 /// Flush bytes to stdout
91 91 pub fn flush(&mut self) -> Result<(), UiError> {
92 92 self.buf.flush().or_else(handle_stdout_error)
93 93 }
94 94 }
95 95
96 96 /// Sometimes writing to stdout is not possible, try writing to stderr to
97 97 /// signal that failure, otherwise just bail.
98 98 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
99 99 if let ErrorKind::BrokenPipe = error.kind() {
100 100 // This makes `| head` work for example
101 101 return Ok(());
102 102 }
103 103 let mut stderr = io::stderr();
104 104
105 105 stderr
106 106 .write_all(&format_bytes!(
107 107 b"abort: {}\n",
108 108 error.to_string().as_bytes()
109 109 ))
110 110 .map_err(UiError::StderrError)?;
111 111
112 112 stderr.flush().map_err(UiError::StderrError)?;
113 113
114 114 Err(UiError::StdoutError(error))
115 115 }
116 116
117 117 /// Sometimes writing to stderr is not possible.
118 118 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
119 119 // A broken pipe should not result in a error
120 120 // like with `| head` for example
121 121 if let ErrorKind::BrokenPipe = error.kind() {
122 122 return Ok(());
123 123 }
124 124 Err(UiError::StdoutError(error))
125 125 }
126 126
127 127 /// Encode rust strings according to the user system.
128 128 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
129 129 // TODO encode for the user's system //
130 130 let bytes = s.as_bytes();
131 131 Cow::Borrowed(bytes)
132 132 }
@@ -1,426 +1,422 b''
1 TODO: fix rhg bugs that make this test fail when status is enabled
2 $ unset RHG_STATUS
3
4
5 1 $ hg init
6 2 $ cat << EOF > a
7 3 > Small Mathematical Series.
8 4 > One
9 5 > Two
10 6 > Three
11 7 > Four
12 8 > Five
13 9 > Hop we are done.
14 10 > EOF
15 11 $ hg add a
16 12 $ hg commit -m ancestor
17 13 $ cat << EOF > a
18 14 > Small Mathematical Series.
19 15 > 1
20 16 > 2
21 17 > 3
22 18 > 4
23 19 > 5
24 20 > Hop we are done.
25 21 > EOF
26 22 $ hg commit -m branch1
27 23 $ hg co 0
28 24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 25 $ cat << EOF > a
30 26 > Small Mathematical Series.
31 27 > 1
32 28 > 2
33 29 > 3
34 30 > 6
35 31 > 8
36 32 > Hop we are done.
37 33 > EOF
38 34 $ hg commit -m branch2
39 35 created new head
40 36
41 37 $ hg merge 1
42 38 merging a
43 39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
44 40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
45 41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
46 42 [1]
47 43
48 44 $ hg id
49 45 618808747361+c0c68e4fe667+ tip
50 46
51 47 $ echo "[commands]" >> $HGRCPATH
52 48 $ echo "status.verbose=true" >> $HGRCPATH
53 49 $ hg status
54 50 M a
55 51 ? a.orig
56 52 # The repository is in an unfinished *merge* state.
57 53
58 54 # Unresolved merge conflicts:
59 55 #
60 56 # a
61 57 #
62 58 # To mark files as resolved: hg resolve --mark FILE
63 59
64 60 # To continue: hg commit
65 61 # To abort: hg merge --abort
66 62
67 63 $ hg status -Tjson
68 64 [
69 65 {
70 66 "itemtype": "file",
71 67 "path": "a",
72 68 "status": "M",
73 69 "unresolved": true
74 70 },
75 71 {
76 72 "itemtype": "file",
77 73 "path": "a.orig",
78 74 "status": "?"
79 75 },
80 76 {
81 77 "itemtype": "morestatus",
82 78 "unfinished": "merge",
83 79 "unfinishedmsg": "To continue: hg commit\nTo abort: hg merge --abort"
84 80 }
85 81 ]
86 82
87 83 $ hg status -0
88 84 M a\x00? a.orig\x00 (no-eol) (esc)
89 85 $ cat a
90 86 Small Mathematical Series.
91 87 1
92 88 2
93 89 3
94 90 <<<<<<< working copy: 618808747361 - test: branch2
95 91 6
96 92 8
97 93 =======
98 94 4
99 95 5
100 96 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
101 97 Hop we are done.
102 98
103 99 $ hg status --config commands.status.verbose=0
104 100 M a
105 101 ? a.orig
106 102
107 103 Verify custom conflict markers
108 104
109 105 $ hg up -q --clean .
110 106 $ cat <<EOF >> .hg/hgrc
111 107 > [command-templates]
112 108 > mergemarker = '{author} {rev}'
113 109 > EOF
114 110
115 111 $ hg merge 1
116 112 merging a
117 113 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
118 114 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
119 115 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
120 116 [1]
121 117
122 118 $ cat a
123 119 Small Mathematical Series.
124 120 1
125 121 2
126 122 3
127 123 <<<<<<< working copy: test 2
128 124 6
129 125 8
130 126 =======
131 127 4
132 128 5
133 129 >>>>>>> merge rev: test 1
134 130 Hop we are done.
135 131
136 132 Verify custom conflict markers with legacy config name
137 133
138 134 $ hg up -q --clean .
139 135 $ cat <<EOF >> .hg/hgrc
140 136 > [ui]
141 137 > mergemarkertemplate = '{author} {rev}'
142 138 > EOF
143 139
144 140 $ hg merge 1
145 141 merging a
146 142 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
147 143 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
148 144 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
149 145 [1]
150 146
151 147 $ cat a
152 148 Small Mathematical Series.
153 149 1
154 150 2
155 151 3
156 152 <<<<<<< working copy: test 2
157 153 6
158 154 8
159 155 =======
160 156 4
161 157 5
162 158 >>>>>>> merge rev: test 1
163 159 Hop we are done.
164 160
165 161 Verify line splitting of custom conflict marker which causes multiple lines
166 162
167 163 $ hg up -q --clean .
168 164 $ cat >> .hg/hgrc <<EOF
169 165 > [command-templates]
170 166 > mergemarker={author} {rev}\nfoo\nbar\nbaz
171 167 > EOF
172 168
173 169 $ hg -q merge 1
174 170 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
175 171 [1]
176 172
177 173 $ cat a
178 174 Small Mathematical Series.
179 175 1
180 176 2
181 177 3
182 178 <<<<<<< working copy: test 2
183 179 6
184 180 8
185 181 =======
186 182 4
187 183 5
188 184 >>>>>>> merge rev: test 1
189 185 Hop we are done.
190 186
191 187 Verify line trimming of custom conflict marker using multi-byte characters
192 188
193 189 $ hg up -q --clean .
194 190 $ "$PYTHON" <<EOF
195 191 > fp = open('logfile', 'wb')
196 192 > fp.write(b'12345678901234567890123456789012345678901234567890' +
197 193 > b'1234567890') # there are 5 more columns for 80 columns
198 194 >
199 195 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
200 196 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
201 197 >
202 198 > fp.close()
203 199 > EOF
204 200 $ hg add logfile
205 201 $ hg --encoding utf-8 commit --logfile logfile
206 202
207 203 $ cat >> .hg/hgrc <<EOF
208 204 > [command-templates]
209 205 > mergemarker={desc|firstline}
210 206 > EOF
211 207
212 208 $ hg -q --encoding utf-8 merge 1
213 209 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
214 210 [1]
215 211
216 212 $ cat a
217 213 Small Mathematical Series.
218 214 1
219 215 2
220 216 3
221 217 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
222 218 6
223 219 8
224 220 =======
225 221 4
226 222 5
227 223 >>>>>>> merge rev: branch1
228 224 Hop we are done.
229 225
230 226 Verify basic conflict markers
231 227
232 228 $ hg up -q --clean 2
233 229 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
234 230
235 231 $ hg merge 1
236 232 merging a
237 233 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
238 234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
239 235 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
240 236 [1]
241 237
242 238 $ cat a
243 239 Small Mathematical Series.
244 240 1
245 241 2
246 242 3
247 243 <<<<<<< working copy
248 244 6
249 245 8
250 246 =======
251 247 4
252 248 5
253 249 >>>>>>> merge rev
254 250 Hop we are done.
255 251
256 252 internal:merge3
257 253
258 254 $ hg up -q --clean .
259 255
260 256 $ hg merge 1 --tool internal:merge3
261 257 merging a
262 258 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
263 259 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
264 260 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
265 261 [1]
266 262 $ cat a
267 263 Small Mathematical Series.
268 264 <<<<<<< working copy
269 265 1
270 266 2
271 267 3
272 268 6
273 269 8
274 270 ||||||| base
275 271 One
276 272 Two
277 273 Three
278 274 Four
279 275 Five
280 276 =======
281 277 1
282 278 2
283 279 3
284 280 4
285 281 5
286 282 >>>>>>> merge rev
287 283 Hop we are done.
288 284
289 285 internal:mergediff
290 286
291 287 $ hg co -C 1
292 288 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 289 $ cat << EOF > a
294 290 > Small Mathematical Series.
295 291 > 1
296 292 > 2
297 293 > 3
298 294 > 4
299 295 > 4.5
300 296 > 5
301 297 > Hop we are done.
302 298 > EOF
303 299 $ hg co -m 2 -t internal:mergediff
304 300 merging a
305 301 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
306 302 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
307 303 use 'hg resolve' to retry unresolved file merges
308 304 [1]
309 305 $ cat a
310 306 Small Mathematical Series.
311 307 1
312 308 2
313 309 3
314 310 <<<<<<<
315 311 ------- base
316 312 +++++++ working copy
317 313 4
318 314 +4.5
319 315 5
320 316 ======= destination
321 317 6
322 318 8
323 319 >>>>>>>
324 320 Hop we are done.
325 321 Test the same thing as above but modify a bit more so we instead get the working
326 322 copy in full and the diff from base to destination.
327 323 $ hg co -C 1
328 324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
329 325 $ cat << EOF > a
330 326 > Small Mathematical Series.
331 327 > 1
332 328 > 2
333 329 > 3.5
334 330 > 4.5
335 331 > 5.5
336 332 > Hop we are done.
337 333 > EOF
338 334 $ hg co -m 2 -t internal:mergediff
339 335 merging a
340 336 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
341 337 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
342 338 use 'hg resolve' to retry unresolved file merges
343 339 [1]
344 340 $ cat a
345 341 Small Mathematical Series.
346 342 1
347 343 2
348 344 <<<<<<<
349 345 ======= working copy
350 346 3.5
351 347 4.5
352 348 5.5
353 349 ------- base
354 350 +++++++ destination
355 351 3
356 352 -4
357 353 -5
358 354 +6
359 355 +8
360 356 >>>>>>>
361 357 Hop we are done.
362 358
363 359 Add some unconflicting changes on each head, to make sure we really
364 360 are merging, unlike :local and :other
365 361
366 362 $ hg up -C
367 363 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 364 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
369 365 1 other heads for branch "default"
370 366 $ printf "\n\nEnd of file\n" >> a
371 367 $ hg ci -m "Add some stuff at the end"
372 368 $ hg up -r 1
373 369 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
374 370 $ printf "Start of file\n\n\n" > tmp
375 371 $ cat a >> tmp
376 372 $ mv tmp a
377 373 $ hg ci -m "Add some stuff at the beginning"
378 374
379 375 Now test :merge-other and :merge-local
380 376
381 377 $ hg merge
382 378 merging a
383 379 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
384 380 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
385 381 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
386 382 [1]
387 383 $ hg resolve --tool :merge-other a
388 384 merging a
389 385 (no more unresolved files)
390 386 $ cat a
391 387 Start of file
392 388
393 389
394 390 Small Mathematical Series.
395 391 1
396 392 2
397 393 3
398 394 6
399 395 8
400 396 Hop we are done.
401 397
402 398
403 399 End of file
404 400
405 401 $ hg up -C
406 402 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
407 403 updated to "18b51d585961: Add some stuff at the beginning"
408 404 1 other heads for branch "default"
409 405 $ hg merge --tool :merge-local
410 406 merging a
411 407 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
412 408 (branch merge, don't forget to commit)
413 409 $ cat a
414 410 Start of file
415 411
416 412
417 413 Small Mathematical Series.
418 414 1
419 415 2
420 416 3
421 417 4
422 418 5
423 419 Hop we are done.
424 420
425 421
426 422 End of file
General Comments 0
You need to be logged in to leave comments. Login now