##// END OF EJS Templates
rhg: exit with relevant code for unsupported requirements...
Simon Sapin -
r46549:2ad2745e default
parent child Browse files
Show More
@@ -1,53 +1,72
1 1 use std::io;
2 2 use std::path::Path;
3 3
4 4 #[derive(Debug)]
5 5 pub enum RequirementsError {
6 6 // TODO: include a path?
7 7 Io(io::Error),
8 8 /// The `requires` file is corrupted
9 9 Corrupted,
10 10 /// The repository requires a feature that we donοΏ½t support
11 11 Unsupported {
12 12 feature: String,
13 13 },
14 14 }
15 15
16 16 fn parse(bytes: &[u8]) -> Result<Vec<String>, ()> {
17 17 // The Python code reading this file uses `str.splitlines`
18 18 // which looks for a number of line separators (even including a couple of
19 19 // non-ASCII ones), but Python code writing it always uses `\n`.
20 20 let lines = bytes.split(|&byte| byte == b'\n');
21 21
22 22 lines
23 23 .filter(|line| !line.is_empty())
24 24 .map(|line| {
25 25 // Python uses Unicode `str.isalnum` but feature names are all
26 26 // ASCII
27 27 if line[0].is_ascii_alphanumeric() {
28 28 Ok(String::from_utf8(line.into()).unwrap())
29 29 } else {
30 30 Err(())
31 31 }
32 32 })
33 33 .collect()
34 34 }
35 35
36 36 pub fn load(repo_root: &Path) -> Result<Vec<String>, RequirementsError> {
37 37 match std::fs::read(repo_root.join(".hg").join("requires")) {
38 38 Ok(bytes) => parse(&bytes).map_err(|()| RequirementsError::Corrupted),
39 39
40 40 // Treat a missing file the same as an empty file.
41 41 // From `mercurial/localrepo.py`:
42 42 // > requires file contains a newline-delimited list of
43 43 // > features/capabilities the opener (us) must have in order to use
44 44 // > the repository. This file was introduced in Mercurial 0.9.2,
45 45 // > which means very old repositories may not have one. We assume
46 46 // > a missing file translates to no requirements.
47 47 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
48 48 Ok(Vec::new())
49 49 }
50 50
51 51 Err(error) => Err(RequirementsError::Io(error))?,
52 52 }
53 53 }
54
55 pub fn check(repo_root: &Path) -> Result<(), RequirementsError> {
56 for feature in load(repo_root)? {
57 if !SUPPORTED.contains(&&*feature) {
58 return Err(RequirementsError::Unsupported { feature })
59 }
60 }
61 Ok(())
62 }
63
64 // TODO: set this to actually-supported features
65 const SUPPORTED: &[&str] = &[
66 "dotencode",
67 "fncache",
68 "generaldelta",
69 "revlogv1",
70 "sparserevlog",
71 "store",
72 ];
@@ -1,99 +1,101
1 1 use crate::commands::Command;
2 2 use crate::error::{CommandError, CommandErrorKind};
3 3 use crate::ui::utf8_to_local;
4 4 use crate::ui::Ui;
5 5 use hg::operations::FindRoot;
6 6 use hg::operations::{CatRev, CatRevError, CatRevErrorKind};
7 use hg::requirements;
7 8 use hg::utils::hg_path::HgPathBuf;
8 9 use micro_timer::timed;
9 10 use std::convert::TryFrom;
10 11
11 12 pub const HELP_TEXT: &str = "
12 13 Output the current or given revision of files
13 14 ";
14 15
15 16 pub struct CatCommand<'a> {
16 17 rev: Option<&'a str>,
17 18 files: Vec<&'a str>,
18 19 }
19 20
20 21 impl<'a> CatCommand<'a> {
21 22 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
22 23 Self { rev, files }
23 24 }
24 25
25 26 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
26 27 ui.write_stdout(data)?;
27 28 Ok(())
28 29 }
29 30 }
30 31
31 32 impl<'a> Command for CatCommand<'a> {
32 33 #[timed]
33 34 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
34 35 let root = FindRoot::new().run()?;
36 requirements::check(&root)?;
35 37 let cwd = std::env::current_dir()
36 38 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
37 39
38 40 let mut files = vec![];
39 41 for file in self.files.iter() {
40 42 let normalized = cwd.join(&file);
41 43 let stripped = normalized
42 44 .strip_prefix(&root)
43 45 .or(Err(CommandErrorKind::Abort(None)))?;
44 46 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
45 47 .or(Err(CommandErrorKind::Abort(None)))?;
46 48 files.push(hg_file);
47 49 }
48 50
49 51 match self.rev {
50 52 Some(rev) => {
51 53 let mut operation = CatRev::new(&root, rev, &files)
52 54 .map_err(|e| map_rev_error(rev, e))?;
53 55 let data =
54 56 operation.run().map_err(|e| map_rev_error(rev, e))?;
55 57 self.display(ui, &data)
56 58 }
57 59 None => Err(CommandErrorKind::Unimplemented.into()),
58 60 }
59 61 }
60 62 }
61 63
62 64 /// Convert `CatRevErrorKind` to `CommandError`
63 65 fn map_rev_error(rev: &str, err: CatRevError) -> CommandError {
64 66 CommandError {
65 67 kind: match err.kind {
66 68 CatRevErrorKind::IoError(err) => CommandErrorKind::Abort(Some(
67 69 utf8_to_local(&format!("abort: {}\n", err)).into(),
68 70 )),
69 71 CatRevErrorKind::InvalidRevision => CommandErrorKind::Abort(Some(
70 72 utf8_to_local(&format!(
71 73 "abort: invalid revision identifier{}\n",
72 74 rev
73 75 ))
74 76 .into(),
75 77 )),
76 78 CatRevErrorKind::UnsuportedRevlogVersion(version) => {
77 79 CommandErrorKind::Abort(Some(
78 80 utf8_to_local(&format!(
79 81 "abort: unsupported revlog version {}\n",
80 82 version
81 83 ))
82 84 .into(),
83 85 ))
84 86 }
85 87 CatRevErrorKind::CorruptedRevlog => CommandErrorKind::Abort(Some(
86 88 "abort: corrupted revlog\n".into(),
87 89 )),
88 90 CatRevErrorKind::UnknowRevlogDataFormat(format) => {
89 91 CommandErrorKind::Abort(Some(
90 92 utf8_to_local(&format!(
91 93 "abort: unknow revlog dataformat {:?}\n",
92 94 format
93 95 ))
94 96 .into(),
95 97 ))
96 98 }
97 99 },
98 100 }
99 101 }
@@ -1,136 +1,138
1 1 use crate::commands::Command;
2 2 use crate::error::{CommandError, CommandErrorKind};
3 3 use crate::ui::utf8_to_local;
4 4 use crate::ui::Ui;
5 5 use hg::operations::FindRoot;
6 6 use hg::operations::{
7 7 ListDirstateTrackedFiles, ListDirstateTrackedFilesError,
8 8 ListDirstateTrackedFilesErrorKind,
9 9 };
10 10 use hg::operations::{
11 11 ListRevTrackedFiles, ListRevTrackedFilesError,
12 12 ListRevTrackedFilesErrorKind,
13 13 };
14 use hg::requirements;
14 15 use hg::utils::files::{get_bytes_from_path, relativize_path};
15 16 use hg::utils::hg_path::{HgPath, HgPathBuf};
16 17 use std::path::PathBuf;
17 18
18 19 pub const HELP_TEXT: &str = "
19 20 List tracked files.
20 21
21 22 Returns 0 on success.
22 23 ";
23 24
24 25 pub struct FilesCommand<'a> {
25 26 rev: Option<&'a str>,
26 27 }
27 28
28 29 impl<'a> FilesCommand<'a> {
29 30 pub fn new(rev: Option<&'a str>) -> Self {
30 31 FilesCommand { rev }
31 32 }
32 33
33 34 fn display_files(
34 35 &self,
35 36 ui: &Ui,
36 37 root: &PathBuf,
37 38 files: impl IntoIterator<Item = &'a HgPath>,
38 39 ) -> Result<(), CommandError> {
39 40 let cwd = std::env::current_dir()
40 41 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
41 42 let rooted_cwd = cwd
42 43 .strip_prefix(&root)
43 44 .expect("cwd was already checked within the repository");
44 45 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
45 46
46 47 let mut stdout = ui.stdout_buffer();
47 48
48 49 for file in files {
49 50 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
50 51 stdout.write_all(b"\n")?;
51 52 }
52 53 stdout.flush()?;
53 54 Ok(())
54 55 }
55 56 }
56 57
57 58 impl<'a> Command for FilesCommand<'a> {
58 59 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
59 60 let root = FindRoot::new().run()?;
61 requirements::check(&root)?;
60 62 if let Some(rev) = self.rev {
61 63 let mut operation = ListRevTrackedFiles::new(&root, rev)
62 64 .map_err(|e| map_rev_error(rev, e))?;
63 65 let files = operation.run().map_err(|e| map_rev_error(rev, e))?;
64 66 self.display_files(ui, &root, files)
65 67 } else {
66 68 let mut operation = ListDirstateTrackedFiles::new(&root)
67 69 .map_err(map_dirstate_error)?;
68 70 let files = operation.run().map_err(map_dirstate_error)?;
69 71 self.display_files(ui, &root, files)
70 72 }
71 73 }
72 74 }
73 75
74 76 /// Convert `ListRevTrackedFilesErrorKind` to `CommandError`
75 77 fn map_rev_error(rev: &str, err: ListRevTrackedFilesError) -> CommandError {
76 78 CommandError {
77 79 kind: match err.kind {
78 80 ListRevTrackedFilesErrorKind::IoError(err) => {
79 81 CommandErrorKind::Abort(Some(
80 82 utf8_to_local(&format!("abort: {}\n", err)).into(),
81 83 ))
82 84 }
83 85 ListRevTrackedFilesErrorKind::InvalidRevision => {
84 86 CommandErrorKind::Abort(Some(
85 87 utf8_to_local(&format!(
86 88 "abort: invalid revision identifier{}\n",
87 89 rev
88 90 ))
89 91 .into(),
90 92 ))
91 93 }
92 94 ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version) => {
93 95 CommandErrorKind::Abort(Some(
94 96 utf8_to_local(&format!(
95 97 "abort: unsupported revlog version {}\n",
96 98 version
97 99 ))
98 100 .into(),
99 101 ))
100 102 }
101 103 ListRevTrackedFilesErrorKind::CorruptedRevlog => {
102 104 CommandErrorKind::Abort(Some(
103 105 "abort: corrupted revlog\n".into(),
104 106 ))
105 107 }
106 108 ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format) => {
107 109 CommandErrorKind::Abort(Some(
108 110 utf8_to_local(&format!(
109 111 "abort: unknow revlog dataformat {:?}\n",
110 112 format
111 113 ))
112 114 .into(),
113 115 ))
114 116 }
115 117 },
116 118 }
117 119 }
118 120
119 121 /// Convert `ListDirstateTrackedFilesError` to `CommandError`
120 122 fn map_dirstate_error(err: ListDirstateTrackedFilesError) -> CommandError {
121 123 CommandError {
122 124 kind: match err.kind {
123 125 ListDirstateTrackedFilesErrorKind::IoError(err) => {
124 126 CommandErrorKind::Abort(Some(
125 127 utf8_to_local(&format!("abort: {}\n", err)).into(),
126 128 ))
127 129 }
128 130 ListDirstateTrackedFilesErrorKind::ParseError(_) => {
129 131 CommandErrorKind::Abort(Some(
130 132 // TODO find a better error message
131 133 b"abort: parse error\n".to_vec(),
132 134 ))
133 135 }
134 136 },
135 137 }
136 138 }
@@ -1,130 +1,133
1 1 use crate::exitcode;
2 2 use crate::ui::UiError;
3 3 use hg::operations::{FindRootError, FindRootErrorKind};
4 4 use hg::requirements::RequirementsError;
5 5 use hg::utils::files::get_bytes_from_path;
6 6 use std::convert::From;
7 7 use std::path::PathBuf;
8 8
9 9 /// The kind of command error
10 10 #[derive(Debug)]
11 11 pub enum CommandErrorKind {
12 12 /// The root of the repository cannot be found
13 13 RootNotFound(PathBuf),
14 14 /// The current directory cannot be found
15 15 CurrentDirNotFound(std::io::Error),
16 16 /// `.hg/requires`
17 17 RequirementsError(RequirementsError),
18 18 /// The standard output stream cannot be written to
19 19 StdoutError,
20 20 /// The standard error stream cannot be written to
21 21 StderrError,
22 22 /// The command aborted
23 23 Abort(Option<Vec<u8>>),
24 24 /// A mercurial capability as not been implemented.
25 25 Unimplemented,
26 26 }
27 27
28 28 impl CommandErrorKind {
29 29 pub fn get_exit_code(&self) -> exitcode::ExitCode {
30 30 match self {
31 31 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
32 32 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
33 CommandErrorKind::RequirementsError(
34 RequirementsError::Unsupported { .. },
35 ) => exitcode::UNIMPLEMENTED_COMMAND,
33 36 CommandErrorKind::RequirementsError(_) => exitcode::ABORT,
34 37 CommandErrorKind::StdoutError => exitcode::ABORT,
35 38 CommandErrorKind::StderrError => exitcode::ABORT,
36 39 CommandErrorKind::Abort(_) => exitcode::ABORT,
37 40 CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
38 41 }
39 42 }
40 43
41 44 /// Return the message corresponding to the error kind if any
42 45 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
43 46 match self {
44 47 // TODO use formating macro
45 48 CommandErrorKind::RootNotFound(path) => {
46 49 let bytes = get_bytes_from_path(path);
47 50 Some(
48 51 [
49 52 b"abort: no repository found in '",
50 53 bytes.as_slice(),
51 54 b"' (.hg not found)!\n",
52 55 ]
53 56 .concat(),
54 57 )
55 58 }
56 59 // TODO use formating macro
57 60 CommandErrorKind::CurrentDirNotFound(e) => Some(
58 61 [
59 62 b"abort: error getting current working directory: ",
60 63 e.to_string().as_bytes(),
61 64 b"\n",
62 65 ]
63 66 .concat(),
64 67 ),
65 68 CommandErrorKind::RequirementsError(
66 69 RequirementsError::Corrupted,
67 70 ) => Some(
68 71 "abort: .hg/requires is corrupted\n".as_bytes().to_owned(),
69 72 ),
70 73 CommandErrorKind::Abort(message) => message.to_owned(),
71 74 _ => None,
72 75 }
73 76 }
74 77 }
75 78
76 79 /// The error type for the Command trait
77 80 #[derive(Debug)]
78 81 pub struct CommandError {
79 82 pub kind: CommandErrorKind,
80 83 }
81 84
82 85 impl CommandError {
83 86 /// Exist the process with the corresponding exit code.
84 87 pub fn exit(&self) {
85 88 std::process::exit(self.kind.get_exit_code())
86 89 }
87 90
88 91 /// Return the message corresponding to the command error if any
89 92 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
90 93 self.kind.get_error_message_bytes()
91 94 }
92 95 }
93 96
94 97 impl From<CommandErrorKind> for CommandError {
95 98 fn from(kind: CommandErrorKind) -> Self {
96 99 CommandError { kind }
97 100 }
98 101 }
99 102
100 103 impl From<UiError> for CommandError {
101 104 fn from(error: UiError) -> Self {
102 105 CommandError {
103 106 kind: match error {
104 107 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
105 108 UiError::StderrError(_) => CommandErrorKind::StderrError,
106 109 },
107 110 }
108 111 }
109 112 }
110 113
111 114 impl From<FindRootError> for CommandError {
112 115 fn from(err: FindRootError) -> Self {
113 116 match err.kind {
114 117 FindRootErrorKind::RootNotFound(path) => CommandError {
115 118 kind: CommandErrorKind::RootNotFound(path),
116 119 },
117 120 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
118 121 kind: CommandErrorKind::CurrentDirNotFound(e),
119 122 },
120 123 }
121 124 }
122 125 }
123 126
124 127 impl From<RequirementsError> for CommandError {
125 128 fn from(err: RequirementsError) -> Self {
126 129 CommandError {
127 130 kind: CommandErrorKind::RequirementsError(err),
128 131 }
129 132 }
130 133 }
@@ -1,126 +1,142
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/debug/rhg" ]; then
6 6 > "$RUNTESTDIR/../rust/target/debug/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 41 abort: error getting current working directory: $ENOENT$
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; 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 e36fa63d37a576b27a69057598351db6ee5746bd
84 84 test
85 85 0 0
86 86 file3
87 87
88 88 commit 3 (no-eol)
89 89 $ rhg debugdata -m 2
90 90 file1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 91 file2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 92 file3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93 93
94 94 Debuging with full node id
95 95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 96 c8e64718e1ca0312eeee0f59d37f8dc612793856
97 97 test
98 98 0 0
99 99 file1
100 100
101 101 commit 1 (no-eol)
102 102
103 103 Cat files
104 104 $ cd $TESTTMP
105 105 $ rm -rf repository
106 106 $ hg init repository
107 107 $ cd repository
108 108 $ echo "original content" > original
109 109 $ hg add original
110 110 $ hg commit -m "add original" original
111 111 $ rhg cat -r 0 original
112 112 original content
113 113 Cat copied file should not display copy metadata
114 114 $ hg copy original copy_of_original
115 115 $ hg commit -m "add copy of original"
116 116 $ rhg cat -r 1 copy_of_original
117 117 original content
118 118
119 119 Requirements
120 120 $ rhg debugrequirements
121 121 dotencode
122 122 fncache
123 123 generaldelta
124 124 revlogv1
125 125 sparserevlog
126 126 store
127
128 $ echo indoor-pool >> .hg/requires
129 $ rhg files
130 [252]
131
132 $ rhg cat -r 1 copy_of_original
133 [252]
134
135 $ rhg debugrequirements
136 dotencode
137 fncache
138 generaldelta
139 revlogv1
140 sparserevlog
141 store
142 indoor-pool
General Comments 0
You need to be logged in to leave comments. Login now