##// END OF EJS Templates
rhg: add a `debugrequirements` subcommand...
Simon Sapin -
r46535:ead435aa default
parent child Browse files
Show More
@@ -0,0 +1,40 b''
1 use crate::commands::Command;
2 use crate::error::{CommandError, CommandErrorKind};
3 use crate::ui::Ui;
4 use hg::operations::FindRoot;
5
6 pub const HELP_TEXT: &str = "
7 Print the current repo requirements.
8 ";
9
10 pub struct DebugRequirementsCommand {}
11
12 impl DebugRequirementsCommand {
13 pub fn new() -> Self {
14 DebugRequirementsCommand {}
15 }
16 }
17
18 impl Command for DebugRequirementsCommand {
19 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
20 let root = FindRoot::new().run()?;
21 let requires = root.join(".hg").join("requires");
22 let requirements = match std::fs::read(requires) {
23 Ok(bytes) => bytes,
24
25 // Treat a missing file the same as an empty file.
26 // From `mercurial/localrepo.py`:
27 // > requires file contains a newline-delimited list of
28 // > features/capabilities the opener (us) must have in order to use
29 // > the repository. This file was introduced in Mercurial 0.9.2,
30 // > which means very old repositories may not have one. We assume
31 // > a missing file translates to no requirements.
32 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Vec::new(),
33
34 Err(error) => Err(CommandErrorKind::FileError(error))?,
35 };
36
37 ui.write_stdout(&requirements)?;
38 Ok(())
39 }
40 }
@@ -1,13 +1,14 b''
1 1 pub mod cat;
2 2 pub mod debugdata;
3 pub mod debugrequirements;
3 4 pub mod files;
4 5 pub mod root;
5 6 use crate::error::CommandError;
6 7 use crate::ui::Ui;
7 8
8 9 /// The common trait for rhg commands
9 10 ///
10 11 /// Normalize the interface of the commands provided by rhg
11 12 pub trait Command {
12 13 fn run(&self, ui: &Ui) -> Result<(), CommandError>;
13 14 }
@@ -1,113 +1,117 b''
1 1 use crate::exitcode;
2 2 use crate::ui::UiError;
3 3 use hg::operations::{FindRootError, FindRootErrorKind};
4 4 use hg::utils::files::get_bytes_from_path;
5 5 use std::convert::From;
6 6 use std::path::PathBuf;
7 7
8 8 /// The kind of command error
9 9 #[derive(Debug)]
10 10 pub enum CommandErrorKind {
11 11 /// The root of the repository cannot be found
12 12 RootNotFound(PathBuf),
13 13 /// The current directory cannot be found
14 14 CurrentDirNotFound(std::io::Error),
15 /// Error while reading or writing a file
16 // TODO: add the file name/path?
17 FileError(std::io::Error),
15 18 /// The standard output stream cannot be written to
16 19 StdoutError,
17 20 /// The standard error stream cannot be written to
18 21 StderrError,
19 22 /// The command aborted
20 23 Abort(Option<Vec<u8>>),
21 24 /// A mercurial capability as not been implemented.
22 25 Unimplemented,
23 26 }
24 27
25 28 impl CommandErrorKind {
26 29 pub fn get_exit_code(&self) -> exitcode::ExitCode {
27 30 match self {
28 31 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
29 32 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
33 CommandErrorKind::FileError(_) => exitcode::ABORT,
30 34 CommandErrorKind::StdoutError => exitcode::ABORT,
31 35 CommandErrorKind::StderrError => exitcode::ABORT,
32 36 CommandErrorKind::Abort(_) => exitcode::ABORT,
33 37 CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
34 38 }
35 39 }
36 40
37 41 /// Return the message corresponding to the error kind if any
38 42 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
39 43 match self {
40 44 // TODO use formating macro
41 45 CommandErrorKind::RootNotFound(path) => {
42 46 let bytes = get_bytes_from_path(path);
43 47 Some(
44 48 [
45 49 b"abort: no repository found in '",
46 50 bytes.as_slice(),
47 51 b"' (.hg not found)!\n",
48 52 ]
49 53 .concat(),
50 54 )
51 55 }
52 56 // TODO use formating macro
53 57 CommandErrorKind::CurrentDirNotFound(e) => Some(
54 58 [
55 59 b"abort: error getting current working directory: ",
56 60 e.to_string().as_bytes(),
57 61 b"\n",
58 62 ]
59 63 .concat(),
60 64 ),
61 65 CommandErrorKind::Abort(message) => message.to_owned(),
62 66 _ => None,
63 67 }
64 68 }
65 69 }
66 70
67 71 /// The error type for the Command trait
68 72 #[derive(Debug)]
69 73 pub struct CommandError {
70 74 pub kind: CommandErrorKind,
71 75 }
72 76
73 77 impl CommandError {
74 78 /// Exist the process with the corresponding exit code.
75 79 pub fn exit(&self) {
76 80 std::process::exit(self.kind.get_exit_code())
77 81 }
78 82
79 83 /// Return the message corresponding to the command error if any
80 84 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
81 85 self.kind.get_error_message_bytes()
82 86 }
83 87 }
84 88
85 89 impl From<CommandErrorKind> for CommandError {
86 90 fn from(kind: CommandErrorKind) -> Self {
87 91 CommandError { kind }
88 92 }
89 93 }
90 94
91 95 impl From<UiError> for CommandError {
92 96 fn from(error: UiError) -> Self {
93 97 CommandError {
94 98 kind: match error {
95 99 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
96 100 UiError::StderrError(_) => CommandErrorKind::StderrError,
97 101 },
98 102 }
99 103 }
100 104 }
101 105
102 106 impl From<FindRootError> for CommandError {
103 107 fn from(err: FindRootError) -> Self {
104 108 match err.kind {
105 109 FindRootErrorKind::RootNotFound(path) => CommandError {
106 110 kind: CommandErrorKind::RootNotFound(path),
107 111 },
108 112 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
109 113 kind: CommandErrorKind::CurrentDirNotFound(e),
110 114 },
111 115 }
112 116 }
113 117 }
@@ -1,177 +1,185 b''
1 1 extern crate log;
2 2 use clap::App;
3 3 use clap::AppSettings;
4 4 use clap::Arg;
5 5 use clap::ArgGroup;
6 6 use clap::ArgMatches;
7 7 use clap::SubCommand;
8 8 use hg::operations::DebugDataKind;
9 9 use std::convert::TryFrom;
10 10
11 11 mod commands;
12 12 mod error;
13 13 mod exitcode;
14 14 mod ui;
15 15 use commands::Command;
16 16 use error::CommandError;
17 17
18 18 fn main() {
19 19 env_logger::init();
20 20 let app = App::new("rhg")
21 21 .setting(AppSettings::AllowInvalidUtf8)
22 22 .setting(AppSettings::SubcommandRequired)
23 23 .setting(AppSettings::VersionlessSubcommands)
24 24 .version("0.0.1")
25 25 .subcommand(
26 26 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
27 27 )
28 28 .subcommand(
29 29 SubCommand::with_name("files")
30 30 .arg(
31 31 Arg::with_name("rev")
32 32 .help("search the repository as it is in REV")
33 33 .short("-r")
34 34 .long("--revision")
35 35 .value_name("REV")
36 36 .takes_value(true),
37 37 )
38 38 .about(commands::files::HELP_TEXT),
39 39 )
40 40 .subcommand(
41 41 SubCommand::with_name("cat")
42 42 .arg(
43 43 Arg::with_name("rev")
44 44 .help("search the repository as it is in REV")
45 45 .short("-r")
46 46 .long("--revision")
47 47 .value_name("REV")
48 48 .takes_value(true),
49 49 )
50 50 .arg(
51 51 clap::Arg::with_name("files")
52 52 .required(true)
53 53 .multiple(true)
54 54 .empty_values(false)
55 55 .value_name("FILE")
56 56 .help("Activity to start: activity@category"),
57 57 )
58 58 .about(commands::cat::HELP_TEXT),
59 59 )
60 60 .subcommand(
61 61 SubCommand::with_name("debugdata")
62 62 .about(commands::debugdata::HELP_TEXT)
63 63 .arg(
64 64 Arg::with_name("changelog")
65 65 .help("open changelog")
66 66 .short("-c")
67 67 .long("--changelog"),
68 68 )
69 69 .arg(
70 70 Arg::with_name("manifest")
71 71 .help("open manifest")
72 72 .short("-m")
73 73 .long("--manifest"),
74 74 )
75 75 .group(
76 76 ArgGroup::with_name("")
77 77 .args(&["changelog", "manifest"])
78 78 .required(true),
79 79 )
80 80 .arg(
81 81 Arg::with_name("rev")
82 82 .help("revision")
83 83 .required(true)
84 84 .value_name("REV"),
85 85 ),
86 )
87 .subcommand(
88 SubCommand::with_name("debugrequirements")
89 .about(commands::debugrequirements::HELP_TEXT),
86 90 );
87 91
88 92 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
89 93 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
90 94 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
91 95 });
92 96
93 97 let ui = ui::Ui::new();
94 98
95 99 let command_result = match_subcommand(matches, &ui);
96 100
97 101 match command_result {
98 102 Ok(_) => std::process::exit(exitcode::OK),
99 103 Err(e) => {
100 104 let message = e.get_error_message_bytes();
101 105 if let Some(msg) = message {
102 106 match ui.write_stderr(&msg) {
103 107 Ok(_) => (),
104 108 Err(_) => std::process::exit(exitcode::ABORT),
105 109 };
106 110 };
107 111 e.exit()
108 112 }
109 113 }
110 114 }
111 115
112 116 fn match_subcommand(
113 117 matches: ArgMatches,
114 118 ui: &ui::Ui,
115 119 ) -> Result<(), CommandError> {
116 120 match matches.subcommand() {
117 121 ("root", _) => commands::root::RootCommand::new().run(&ui),
118 122 ("files", Some(matches)) => {
119 123 commands::files::FilesCommand::try_from(matches)?.run(&ui)
120 124 }
121 125 ("cat", Some(matches)) => {
122 126 commands::cat::CatCommand::try_from(matches)?.run(&ui)
123 127 }
124 128 ("debugdata", Some(matches)) => {
125 129 commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui)
126 130 }
131 ("debugrequirements", _) => {
132 commands::debugrequirements::DebugRequirementsCommand::new()
133 .run(&ui)
134 }
127 135 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
128 136 }
129 137 }
130 138
131 139 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
132 140 type Error = CommandError;
133 141
134 142 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
135 143 let rev = args.value_of("rev");
136 144 Ok(commands::files::FilesCommand::new(rev))
137 145 }
138 146 }
139 147
140 148 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> {
141 149 type Error = CommandError;
142 150
143 151 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
144 152 let rev = args.value_of("rev");
145 153 let files = match args.values_of("files") {
146 154 Some(files) => files.collect(),
147 155 None => vec![],
148 156 };
149 157 Ok(commands::cat::CatCommand::new(rev, files))
150 158 }
151 159 }
152 160
153 161 impl<'a> TryFrom<&'a ArgMatches<'_>>
154 162 for commands::debugdata::DebugDataCommand<'a>
155 163 {
156 164 type Error = CommandError;
157 165
158 166 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
159 167 let rev = args
160 168 .value_of("rev")
161 169 .expect("rev should be a required argument");
162 170 let kind = match (
163 171 args.is_present("changelog"),
164 172 args.is_present("manifest"),
165 173 ) {
166 174 (true, false) => DebugDataKind::Changelog,
167 175 (false, true) => DebugDataKind::Manifest,
168 176 (true, true) => {
169 177 unreachable!("Should not happen since options are exclusive")
170 178 }
171 179 (false, false) => {
172 180 unreachable!("Should not happen since options are required")
173 181 }
174 182 };
175 183 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
176 184 }
177 185 }
@@ -1,117 +1,126 b''
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
119 Requirements
120 $ rhg debugrequirements
121 dotencode
122 fncache
123 generaldelta
124 revlogv1
125 sparserevlog
126 store
General Comments 0
You need to be logged in to leave comments. Login now