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 = |
|
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 |
. |
|
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 = |
|
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 |
|
|
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 |
|
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 |
|
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:: |
|
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:: |
|
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) => |
|
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 |
|
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 |
|
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(_) => |
|
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 |
|
|
42 | [255] | |
43 |
|
43 | |||
44 |
|
|
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