##// END OF EJS Templates
rhg: Simplify CommandError based on its use...
Simon Sapin -
r47174:ca3f73cc default
parent child Browse files
Show More
@@ -1,178 +1,186
1 1 // utils module
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! Contains useful functions, traits, structs, etc. for use in core.
9 9
10 use crate::errors::{HgError, IoErrorContext};
10 11 use crate::utils::hg_path::HgPath;
11 12 use std::{io::Write, ops::Deref};
12 13
13 14 pub mod files;
14 15 pub mod hg_path;
15 16 pub mod path_auditor;
16 17
17 18 /// Useful until rust/issues/56345 is stable
18 19 ///
19 20 /// # Examples
20 21 ///
21 22 /// ```
22 23 /// use crate::hg::utils::find_slice_in_slice;
23 24 ///
24 25 /// let haystack = b"This is the haystack".to_vec();
25 26 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
26 27 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
27 28 /// ```
28 29 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
29 30 where
30 31 for<'a> &'a [T]: PartialEq,
31 32 {
32 33 slice
33 34 .windows(needle.len())
34 35 .position(|window| window == needle)
35 36 }
36 37
37 38 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
38 39 ///
39 40 /// # Examples
40 41 ///
41 42 /// ```
42 43 /// use crate::hg::utils::replace_slice;
43 44 /// let mut line = b"I hate writing tests!".to_vec();
44 45 /// replace_slice(&mut line, b"hate", b"love");
45 46 /// assert_eq!(
46 47 /// line,
47 48 /// b"I love writing tests!".to_vec()
48 49 /// );
49 50 /// ```
50 51 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
51 52 where
52 53 T: Clone + PartialEq,
53 54 {
54 55 if buf.len() < from.len() || from.len() != to.len() {
55 56 return;
56 57 }
57 58 for i in 0..=buf.len() - from.len() {
58 59 if buf[i..].starts_with(from) {
59 60 buf[i..(i + from.len())].clone_from_slice(to);
60 61 }
61 62 }
62 63 }
63 64
64 65 pub trait SliceExt {
65 66 fn trim_end(&self) -> &Self;
66 67 fn trim_start(&self) -> &Self;
67 68 fn trim(&self) -> &Self;
68 69 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
69 70 }
70 71
71 72 #[allow(clippy::trivially_copy_pass_by_ref)]
72 73 fn is_not_whitespace(c: &u8) -> bool {
73 74 !(*c as char).is_whitespace()
74 75 }
75 76
76 77 impl SliceExt for [u8] {
77 78 fn trim_end(&self) -> &[u8] {
78 79 if let Some(last) = self.iter().rposition(is_not_whitespace) {
79 80 &self[..=last]
80 81 } else {
81 82 &[]
82 83 }
83 84 }
84 85 fn trim_start(&self) -> &[u8] {
85 86 if let Some(first) = self.iter().position(is_not_whitespace) {
86 87 &self[first..]
87 88 } else {
88 89 &[]
89 90 }
90 91 }
91 92
92 93 /// ```
93 94 /// use hg::utils::SliceExt;
94 95 /// assert_eq!(
95 96 /// b" to trim ".trim(),
96 97 /// b"to trim"
97 98 /// );
98 99 /// assert_eq!(
99 100 /// b"to trim ".trim(),
100 101 /// b"to trim"
101 102 /// );
102 103 /// assert_eq!(
103 104 /// b" to trim".trim(),
104 105 /// b"to trim"
105 106 /// );
106 107 /// ```
107 108 fn trim(&self) -> &[u8] {
108 109 self.trim_start().trim_end()
109 110 }
110 111
111 112 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
112 113 if self.starts_with(needle) {
113 114 Some(&self[needle.len()..])
114 115 } else {
115 116 None
116 117 }
117 118 }
118 119 }
119 120
120 121 pub trait Escaped {
121 122 /// Return bytes escaped for display to the user
122 123 fn escaped_bytes(&self) -> Vec<u8>;
123 124 }
124 125
125 126 impl Escaped for u8 {
126 127 fn escaped_bytes(&self) -> Vec<u8> {
127 128 let mut acc = vec![];
128 129 match self {
129 130 c @ b'\'' | c @ b'\\' => {
130 131 acc.push(b'\\');
131 132 acc.push(*c);
132 133 }
133 134 b'\t' => {
134 135 acc.extend(br"\\t");
135 136 }
136 137 b'\n' => {
137 138 acc.extend(br"\\n");
138 139 }
139 140 b'\r' => {
140 141 acc.extend(br"\\r");
141 142 }
142 143 c if (*c < b' ' || *c >= 127) => {
143 144 write!(acc, "\\x{:x}", self).unwrap();
144 145 }
145 146 c => {
146 147 acc.push(*c);
147 148 }
148 149 }
149 150 acc
150 151 }
151 152 }
152 153
153 154 impl<'a, T: Escaped> Escaped for &'a [T] {
154 155 fn escaped_bytes(&self) -> Vec<u8> {
155 156 self.iter().flat_map(Escaped::escaped_bytes).collect()
156 157 }
157 158 }
158 159
159 160 impl<T: Escaped> Escaped for Vec<T> {
160 161 fn escaped_bytes(&self) -> Vec<u8> {
161 162 self.deref().escaped_bytes()
162 163 }
163 164 }
164 165
165 166 impl<'a> Escaped for &'a HgPath {
166 167 fn escaped_bytes(&self) -> Vec<u8> {
167 168 self.as_bytes().escaped_bytes()
168 169 }
169 170 }
170 171
171 172 // TODO: use the str method when we require Rust 1.45
172 173 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
173 174 if s.ends_with(suffix) {
174 175 Some(&s[..s.len() - suffix.len()])
175 176 } else {
176 177 None
177 178 }
178 179 }
180
181 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
182 std::env::current_dir().map_err(|error| HgError::IoError {
183 error,
184 context: IoErrorContext::CurrentDir,
185 })
186 }
@@ -1,57 +1,58
1 1 use crate::commands::Command;
2 2 use crate::error::CommandError;
3 3 use crate::ui::Ui;
4 4 use hg::operations::cat;
5 5 use hg::repo::Repo;
6 6 use hg::utils::hg_path::HgPathBuf;
7 7 use micro_timer::timed;
8 8 use std::convert::TryFrom;
9 9
10 10 pub const HELP_TEXT: &str = "
11 11 Output the current or given revision of files
12 12 ";
13 13
14 14 pub struct CatCommand<'a> {
15 15 rev: Option<&'a str>,
16 16 files: Vec<&'a str>,
17 17 }
18 18
19 19 impl<'a> CatCommand<'a> {
20 20 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
21 21 Self { rev, files }
22 22 }
23 23
24 24 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
25 25 ui.write_stdout(data)?;
26 26 Ok(())
27 27 }
28 28 }
29 29
30 30 impl<'a> Command for CatCommand<'a> {
31 31 #[timed]
32 32 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
33 33 let repo = Repo::find()?;
34 34 repo.check_requirements()?;
35 let cwd = std::env::current_dir()
36 .or_else(|e| Err(CommandError::CurrentDirNotFound(e)))?;
35 let cwd = hg::utils::current_dir()?;
37 36
38 37 let mut files = vec![];
39 38 for file in self.files.iter() {
39 // TODO: actually normalize `..` path segments etc?
40 40 let normalized = cwd.join(&file);
41 41 let stripped = normalized
42 42 .strip_prefix(&repo.working_directory_path())
43 .or(Err(CommandError::Abort(None)))?;
43 // TODO: error message for path arguments outside of the repo
44 .map_err(|_| CommandError::abort(""))?;
44 45 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
45 .or(Err(CommandError::Abort(None)))?;
46 .map_err(|e| CommandError::abort(e.to_string()))?;
46 47 files.push(hg_file);
47 48 }
48 49
49 50 match self.rev {
50 51 Some(rev) => {
51 52 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
52 53 self.display(ui, &data)
53 54 }
54 55 None => Err(CommandError::Unimplemented.into()),
55 56 }
56 57 }
57 58 }
@@ -1,63 +1,62
1 1 use crate::commands::Command;
2 2 use crate::error::CommandError;
3 3 use crate::ui::Ui;
4 4 use hg::operations::list_rev_tracked_files;
5 5 use hg::operations::Dirstate;
6 6 use hg::repo::Repo;
7 7 use hg::utils::files::{get_bytes_from_path, relativize_path};
8 8 use hg::utils::hg_path::{HgPath, HgPathBuf};
9 9
10 10 pub const HELP_TEXT: &str = "
11 11 List tracked files.
12 12
13 13 Returns 0 on success.
14 14 ";
15 15
16 16 pub struct FilesCommand<'a> {
17 17 rev: Option<&'a str>,
18 18 }
19 19
20 20 impl<'a> FilesCommand<'a> {
21 21 pub fn new(rev: Option<&'a str>) -> Self {
22 22 FilesCommand { rev }
23 23 }
24 24
25 25 fn display_files(
26 26 &self,
27 27 ui: &Ui,
28 28 repo: &Repo,
29 29 files: impl IntoIterator<Item = &'a HgPath>,
30 30 ) -> Result<(), CommandError> {
31 let cwd = std::env::current_dir()
32 .or_else(|e| Err(CommandError::CurrentDirNotFound(e)))?;
31 let cwd = hg::utils::current_dir()?;
33 32 let rooted_cwd = cwd
34 33 .strip_prefix(repo.working_directory_path())
35 34 .expect("cwd was already checked within the repository");
36 35 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
37 36
38 37 let mut stdout = ui.stdout_buffer();
39 38
40 39 for file in files {
41 40 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
42 41 stdout.write_all(b"\n")?;
43 42 }
44 43 stdout.flush()?;
45 44 Ok(())
46 45 }
47 46 }
48 47
49 48 impl<'a> Command for FilesCommand<'a> {
50 49 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
51 50 let repo = Repo::find()?;
52 51 repo.check_requirements()?;
53 52 if let Some(rev) = self.rev {
54 53 let files =
55 54 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
56 55 self.display_files(ui, &repo, files.iter())
57 56 } else {
58 57 let distate = Dirstate::new(&repo)?;
59 58 let files = distate.tracked_files()?;
60 59 self.display_files(ui, &repo, files)
61 60 }
62 61 }
63 62 }
@@ -1,123 +1,81
1 use crate::exitcode;
2 1 use crate::ui::utf8_to_local;
3 2 use crate::ui::UiError;
4 use format_bytes::format_bytes;
5 use hg::errors::HgError;
3 use hg::errors::{HgError, IoErrorContext};
6 4 use hg::operations::FindRootError;
7 5 use hg::revlog::revlog::RevlogError;
8 use hg::utils::files::get_bytes_from_path;
9 6 use std::convert::From;
10 use std::path::PathBuf;
11 7
12 8 /// The kind of command error
13 #[derive(Debug, derive_more::From)]
9 #[derive(Debug)]
14 10 pub enum CommandError {
15 /// The root of the repository cannot be found
16 RootNotFound(PathBuf),
17 /// The current directory cannot be found
18 CurrentDirNotFound(std::io::Error),
19 /// The standard output stream cannot be written to
20 StdoutError,
21 /// The standard error stream cannot be written to
22 StderrError,
23 /// The command aborted
24 Abort(Option<Vec<u8>>),
11 /// Exit with an error message and "standard" failure exit code.
12 Abort { message: Vec<u8> },
13
25 14 /// A mercurial capability as not been implemented.
15 ///
16 /// There is no error message printed in this case.
17 /// Instead, we exit with a specic status code and a wrapper script may
18 /// fallback to Python-based Mercurial.
26 19 Unimplemented,
27 /// Common cases
28 #[from]
29 Other(HgError),
30 20 }
31 21
32 22 impl CommandError {
33 pub fn get_exit_code(&self) -> exitcode::ExitCode {
34 match self {
35 CommandError::RootNotFound(_) => exitcode::ABORT,
36 CommandError::CurrentDirNotFound(_) => exitcode::ABORT,
37 CommandError::StdoutError => exitcode::ABORT,
38 CommandError::StderrError => exitcode::ABORT,
39 CommandError::Abort(_) => exitcode::ABORT,
40 CommandError::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
41 CommandError::Other(HgError::UnsupportedFeature(_)) => {
42 exitcode::UNIMPLEMENTED_COMMAND
43 }
44 CommandError::Other(_) => exitcode::ABORT,
23 pub fn abort(message: impl AsRef<str>) -> Self {
24 CommandError::Abort {
25 // TODO: bytes-based (instead of Unicode-based) formatting
26 // of error messages to handle non-UTF-8 filenames etc:
27 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
28 message: utf8_to_local(message.as_ref()).into(),
45 29 }
46 30 }
31 }
47 32
48 /// Return the message corresponding to the error if any
49 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
50 match self {
51 CommandError::RootNotFound(path) => {
52 let bytes = get_bytes_from_path(path);
53 Some(format_bytes!(
54 b"abort: no repository found in '{}' (.hg not found)!\n",
55 bytes.as_slice()
56 ))
57 }
58 CommandError::CurrentDirNotFound(e) => Some(format_bytes!(
59 b"abort: error getting current working directory: {}\n",
60 e.to_string().as_bytes(),
61 )),
62 CommandError::Abort(message) => message.to_owned(),
63
64 CommandError::StdoutError
65 | CommandError::StderrError
66 | CommandError::Unimplemented
67 | CommandError::Other(HgError::UnsupportedFeature(_)) => None,
68
69 CommandError::Other(e) => {
70 Some(format_bytes!(b"{}\n", e.to_string().as_bytes()))
71 }
33 impl From<HgError> for CommandError {
34 fn from(error: HgError) -> Self {
35 match error {
36 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
37 _ => CommandError::abort(error.to_string()),
72 38 }
73 39 }
74
75 /// Exist the process with the corresponding exit code.
76 pub fn exit(&self) {
77 std::process::exit(self.get_exit_code())
78 }
79 40 }
80 41
81 42 impl From<UiError> for CommandError {
82 fn from(error: UiError) -> Self {
83 match error {
84 UiError::StdoutError(_) => CommandError::StdoutError,
85 UiError::StderrError(_) => CommandError::StderrError,
86 }
43 fn from(_error: UiError) -> Self {
44 // If we already failed writing to stdout or stderr,
45 // writing an error message to stderr about it would be likely to fail
46 // too.
47 CommandError::abort("")
87 48 }
88 49 }
89 50
90 51 impl From<FindRootError> for CommandError {
91 52 fn from(err: FindRootError) -> Self {
92 53 match err {
93 FindRootError::RootNotFound(path) => {
94 CommandError::RootNotFound(path)
54 FindRootError::RootNotFound(path) => CommandError::abort(format!(
55 "no repository found in '{}' (.hg not found)!",
56 path.display()
57 )),
58 FindRootError::GetCurrentDirError(error) => HgError::IoError {
59 error,
60 context: IoErrorContext::CurrentDir,
95 61 }
96 FindRootError::GetCurrentDirError(e) => {
97 CommandError::CurrentDirNotFound(e)
98 }
62 .into(),
99 63 }
100 64 }
101 65 }
102 66
103 67 impl From<(RevlogError, &str)> for CommandError {
104 68 fn from((err, rev): (RevlogError, &str)) -> CommandError {
105 69 match err {
106 RevlogError::InvalidRevision => CommandError::Abort(Some(
107 utf8_to_local(&format!(
108 "abort: invalid revision identifier {}\n",
109 rev
110 ))
111 .into(),
70 RevlogError::InvalidRevision => CommandError::abort(format!(
71 "invalid revision identifier {}",
72 rev
112 73 )),
113 RevlogError::AmbiguousPrefix => CommandError::Abort(Some(
114 utf8_to_local(&format!(
115 "abort: ambiguous revision identifier {}\n",
116 rev
117 ))
118 .into(),
74 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
75 "ambiguous revision identifier {}",
76 rev
119 77 )),
120 RevlogError::Other(err) => CommandError::Other(err),
78 RevlogError::Other(error) => error.into(),
121 79 }
122 80 }
123 81 }
@@ -1,10 +1,10
1 1 pub type ExitCode = i32;
2 2
3 3 /// Successful exit
4 4 pub const OK: ExitCode = 0;
5 5
6 6 /// Generic abort
7 7 pub const ABORT: ExitCode = 255;
8 8
9 /// Command not implemented by rhg
10 pub const UNIMPLEMENTED_COMMAND: ExitCode = 252;
9 /// Command or feature not implemented by rhg
10 pub const UNIMPLEMENTED: ExitCode = 252;
@@ -1,185 +1,191
1 1 extern crate log;
2 2 use clap::App;
3 3 use clap::AppSettings;
4 4 use clap::Arg;
5 5 use clap::ArgGroup;
6 6 use clap::ArgMatches;
7 7 use clap::SubCommand;
8 use format_bytes::format_bytes;
8 9 use hg::operations::DebugDataKind;
9 10 use std::convert::TryFrom;
10 11
11 12 mod commands;
12 13 mod error;
13 14 mod exitcode;
14 15 mod ui;
15 16 use commands::Command;
16 17 use error::CommandError;
17 18
18 19 fn main() {
19 20 env_logger::init();
20 21 let app = App::new("rhg")
21 22 .setting(AppSettings::AllowInvalidUtf8)
22 23 .setting(AppSettings::SubcommandRequired)
23 24 .setting(AppSettings::VersionlessSubcommands)
24 25 .version("0.0.1")
25 26 .subcommand(
26 27 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
27 28 )
28 29 .subcommand(
29 30 SubCommand::with_name("files")
30 31 .arg(
31 32 Arg::with_name("rev")
32 33 .help("search the repository as it is in REV")
33 34 .short("-r")
34 35 .long("--revision")
35 36 .value_name("REV")
36 37 .takes_value(true),
37 38 )
38 39 .about(commands::files::HELP_TEXT),
39 40 )
40 41 .subcommand(
41 42 SubCommand::with_name("cat")
42 43 .arg(
43 44 Arg::with_name("rev")
44 45 .help("search the repository as it is in REV")
45 46 .short("-r")
46 47 .long("--revision")
47 48 .value_name("REV")
48 49 .takes_value(true),
49 50 )
50 51 .arg(
51 52 clap::Arg::with_name("files")
52 53 .required(true)
53 54 .multiple(true)
54 55 .empty_values(false)
55 56 .value_name("FILE")
56 57 .help("Activity to start: activity@category"),
57 58 )
58 59 .about(commands::cat::HELP_TEXT),
59 60 )
60 61 .subcommand(
61 62 SubCommand::with_name("debugdata")
62 63 .about(commands::debugdata::HELP_TEXT)
63 64 .arg(
64 65 Arg::with_name("changelog")
65 66 .help("open changelog")
66 67 .short("-c")
67 68 .long("--changelog"),
68 69 )
69 70 .arg(
70 71 Arg::with_name("manifest")
71 72 .help("open manifest")
72 73 .short("-m")
73 74 .long("--manifest"),
74 75 )
75 76 .group(
76 77 ArgGroup::with_name("")
77 78 .args(&["changelog", "manifest"])
78 79 .required(true),
79 80 )
80 81 .arg(
81 82 Arg::with_name("rev")
82 83 .help("revision")
83 84 .required(true)
84 85 .value_name("REV"),
85 86 ),
86 87 )
87 88 .subcommand(
88 89 SubCommand::with_name("debugrequirements")
89 90 .about(commands::debugrequirements::HELP_TEXT),
90 91 );
91 92
92 93 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
93 94 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
94 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
95 std::process::exit(exitcode::UNIMPLEMENTED)
95 96 });
96 97
97 98 let ui = ui::Ui::new();
98 99
99 100 let command_result = match_subcommand(matches, &ui);
100 101
101 match command_result {
102 Ok(_) => std::process::exit(exitcode::OK),
103 Err(e) => {
104 let message = e.get_error_message_bytes();
105 if let Some(msg) = message {
106 match ui.write_stderr(&msg) {
107 Ok(_) => (),
108 Err(_) => std::process::exit(exitcode::ABORT),
109 };
110 };
111 e.exit()
102 let exit_code = match command_result {
103 Ok(_) => exitcode::OK,
104
105 // Exit with a specific code and no error message to let a potential
106 // wrapper script fallback to Python-based Mercurial.
107 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
108
109 Err(CommandError::Abort { message }) => {
110 if !message.is_empty() {
111 // Ignore errors when writing to stderr, we’re already exiting
112 // with failure code so there’s not much more we can do.
113 let _ =
114 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
115 }
116 exitcode::ABORT
112 117 }
113 }
118 };
119 std::process::exit(exit_code)
114 120 }
115 121
116 122 fn match_subcommand(
117 123 matches: ArgMatches,
118 124 ui: &ui::Ui,
119 125 ) -> Result<(), CommandError> {
120 126 match matches.subcommand() {
121 127 ("root", _) => commands::root::RootCommand::new().run(&ui),
122 128 ("files", Some(matches)) => {
123 129 commands::files::FilesCommand::try_from(matches)?.run(&ui)
124 130 }
125 131 ("cat", Some(matches)) => {
126 132 commands::cat::CatCommand::try_from(matches)?.run(&ui)
127 133 }
128 134 ("debugdata", Some(matches)) => {
129 135 commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui)
130 136 }
131 137 ("debugrequirements", _) => {
132 138 commands::debugrequirements::DebugRequirementsCommand::new()
133 139 .run(&ui)
134 140 }
135 141 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
136 142 }
137 143 }
138 144
139 145 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
140 146 type Error = CommandError;
141 147
142 148 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
143 149 let rev = args.value_of("rev");
144 150 Ok(commands::files::FilesCommand::new(rev))
145 151 }
146 152 }
147 153
148 154 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> {
149 155 type Error = CommandError;
150 156
151 157 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
152 158 let rev = args.value_of("rev");
153 159 let files = match args.values_of("files") {
154 160 Some(files) => files.collect(),
155 161 None => vec![],
156 162 };
157 163 Ok(commands::cat::CatCommand::new(rev, files))
158 164 }
159 165 }
160 166
161 167 impl<'a> TryFrom<&'a ArgMatches<'_>>
162 168 for commands::debugdata::DebugDataCommand<'a>
163 169 {
164 170 type Error = CommandError;
165 171
166 172 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
167 173 let rev = args
168 174 .value_of("rev")
169 175 .expect("rev should be a required argument");
170 176 let kind = match (
171 177 args.is_present("changelog"),
172 178 args.is_present("manifest"),
173 179 ) {
174 180 (true, false) => DebugDataKind::Changelog,
175 181 (false, true) => DebugDataKind::Manifest,
176 182 (true, true) => {
177 183 unreachable!("Should not happen since options are exclusive")
178 184 }
179 185 (false, false) => {
180 186 unreachable!("Should not happen since options are required")
181 187 }
182 188 };
183 189 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
184 190 }
185 191 }
@@ -1,204 +1,204
1 1 #require rust
2 2
3 3 Define an rhg function that will only run if rhg exists
4 4 $ rhg() {
5 5 > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then
6 6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
7 7 > else
8 8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 9 > exit 80
10 10 > fi
11 11 > }
12 12
13 13 Unimplemented command
14 14 $ rhg unimplemented-command
15 15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16 16
17 17 USAGE:
18 18 rhg <SUBCOMMAND>
19 19
20 20 For more information try --help
21 21 [252]
22 22
23 23 Finding root
24 24 $ rhg root
25 25 abort: no repository found in '$TESTTMP' (.hg not found)!
26 26 [255]
27 27
28 28 $ hg init repository
29 29 $ cd repository
30 30 $ rhg root
31 31 $TESTTMP/repository
32 32
33 33 Unwritable file descriptor
34 34 $ rhg root > /dev/full
35 35 abort: No space left on device (os error 28)
36 36 [255]
37 37
38 38 Deleted repository
39 39 $ rm -rf `pwd`
40 40 $ rhg root
41 abort: error getting current working directory: $ENOENT$
41 abort: $ENOENT$: current directory
42 42 [255]
43 43
44 44 Listing tracked files
45 45 $ cd $TESTTMP
46 46 $ hg init repository
47 47 $ cd repository
48 48 $ for i in 1 2 3; do
49 49 > echo $i >> file$i
50 50 > hg add file$i
51 51 > done
52 52 > hg commit -m "commit $i" -q
53 53
54 54 Listing tracked files from root
55 55 $ rhg files
56 56 file1
57 57 file2
58 58 file3
59 59
60 60 Listing tracked files from subdirectory
61 61 $ mkdir -p path/to/directory
62 62 $ cd path/to/directory
63 63 $ rhg files
64 64 ../../../file1
65 65 ../../../file2
66 66 ../../../file3
67 67
68 68 Listing tracked files through broken pipe
69 69 $ rhg files | head -n 1
70 70 ../../../file1
71 71
72 72 Debuging data in inline index
73 73 $ cd $TESTTMP
74 74 $ rm -rf repository
75 75 $ hg init repository
76 76 $ cd repository
77 77 $ for i in 1 2 3 4 5 6; do
78 78 > echo $i >> file-$i
79 79 > hg add file-$i
80 80 > hg commit -m "Commit $i" -q
81 81 > done
82 82 $ rhg debugdata -c 2
83 83 8d0267cb034247ebfa5ee58ce59e22e57a492297
84 84 test
85 85 0 0
86 86 file-3
87 87
88 88 Commit 3 (no-eol)
89 89 $ rhg debugdata -m 2
90 90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93 93
94 94 Debuging with full node id
95 95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 96 d1d1c679d3053e8926061b6f45ca52009f011e3f
97 97 test
98 98 0 0
99 99 file-1
100 100
101 101 Commit 1 (no-eol)
102 102
103 103 Specifying revisions by changeset ID
104 104 $ hg log -T '{node}\n'
105 105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
106 106 d654274993d0149eecc3cc03214f598320211900
107 107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
108 108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
109 109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
110 110 6ae9681c6d30389694d8701faf24b583cf3ccafe
111 111 $ rhg files -r cf8b83
112 112 file-1
113 113 file-2
114 114 file-3
115 115 $ rhg cat -r cf8b83 file-2
116 116 2
117 117 $ rhg cat -r c file-2
118 118 abort: ambiguous revision identifier c
119 119 [255]
120 120 $ rhg cat -r d file-2
121 121 2
122 122
123 123 Cat files
124 124 $ cd $TESTTMP
125 125 $ rm -rf repository
126 126 $ hg init repository
127 127 $ cd repository
128 128 $ echo "original content" > original
129 129 $ hg add original
130 130 $ hg commit -m "add original" original
131 131 $ rhg cat -r 0 original
132 132 original content
133 133 Cat copied file should not display copy metadata
134 134 $ hg copy original copy_of_original
135 135 $ hg commit -m "add copy of original"
136 136 $ rhg cat -r 1 copy_of_original
137 137 original content
138 138
139 139 Requirements
140 140 $ rhg debugrequirements
141 141 dotencode
142 142 fncache
143 143 generaldelta
144 144 revlogv1
145 145 sparserevlog
146 146 store
147 147
148 148 $ echo indoor-pool >> .hg/requires
149 149 $ rhg files
150 150 [252]
151 151
152 152 $ rhg cat -r 1 copy_of_original
153 153 [252]
154 154
155 155 $ rhg debugrequirements
156 156 dotencode
157 157 fncache
158 158 generaldelta
159 159 revlogv1
160 160 sparserevlog
161 161 store
162 162 indoor-pool
163 163
164 164 $ echo -e '\xFF' >> .hg/requires
165 165 $ rhg debugrequirements
166 corrupted repository: parse error in 'requires' file
166 abort: corrupted repository: parse error in 'requires' file
167 167 [255]
168 168
169 169 Persistent nodemap
170 170 $ cd $TESTTMP
171 171 $ rm -rf repository
172 172 $ hg init repository
173 173 $ cd repository
174 174 $ rhg debugrequirements | grep nodemap
175 175 [1]
176 176 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
177 177 $ hg id -r tip
178 178 c3ae8dec9fad tip
179 179 $ ls .hg/store/00changelog*
180 180 .hg/store/00changelog.d
181 181 .hg/store/00changelog.i
182 182 $ rhg files -r c3ae8dec9fad
183 183 of
184 184
185 185 $ cd $TESTTMP
186 186 $ rm -rf repository
187 187 $ hg --config format.use-persistent-nodemap=True init repository
188 188 $ cd repository
189 189 $ rhg debugrequirements | grep nodemap
190 190 persistent-nodemap
191 191 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
192 192 $ hg id -r tip
193 193 c3ae8dec9fad tip
194 194 $ ls .hg/store/00changelog*
195 195 .hg/store/00changelog-*.nd (glob)
196 196 .hg/store/00changelog.d
197 197 .hg/store/00changelog.i
198 198 .hg/store/00changelog.n
199 199
200 200 Specifying revisions by changeset ID
201 201 $ rhg files -r c3ae8dec9fad
202 202 of
203 203 $ rhg cat -r c3ae8dec9fad of
204 204 r5000
General Comments 0
You need to be logged in to leave comments. Login now