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