##// END OF EJS Templates
rhg: Add support for -R and --repository command-line arguments...
Simon Sapin -
r47231:1a00a578 default draft
parent child Browse files
Show More
@@ -1,216 +1,238 b''
1 1 use crate::config::{Config, ConfigError, ConfigParseError};
2 2 use crate::errors::{HgError, IoResultExt};
3 3 use crate::requirements;
4 use crate::utils::current_dir;
4 5 use crate::utils::files::get_path_from_bytes;
5 6 use memmap::{Mmap, MmapOptions};
6 7 use std::collections::HashSet;
7 8 use std::path::{Path, PathBuf};
8 9
9 10 /// A repository on disk
10 11 pub struct Repo {
11 12 working_directory: PathBuf,
12 13 dot_hg: PathBuf,
13 14 store: PathBuf,
14 15 requirements: HashSet<String>,
15 16 config: Config,
16 17 }
17 18
18 19 #[derive(Debug, derive_more::From)]
19 20 pub enum RepoError {
20 21 NotFound {
21 current_directory: PathBuf,
22 at: PathBuf,
22 23 },
23 24 #[from]
24 25 ConfigParseError(ConfigParseError),
25 26 #[from]
26 27 Other(HgError),
27 28 }
28 29
29 30 impl From<ConfigError> for RepoError {
30 31 fn from(error: ConfigError) -> Self {
31 32 match error {
32 33 ConfigError::Parse(error) => error.into(),
33 34 ConfigError::Other(error) => error.into(),
34 35 }
35 36 }
36 37 }
37 38
38 39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 40 #[derive(Clone, Copy)]
40 41 pub(crate) struct Vfs<'a> {
41 42 base: &'a Path,
42 43 }
43 44
44 45 impl Repo {
45 46 /// Search the current directory and its ancestores for a repository:
46 47 /// a working directory that contains a `.hg` sub-directory.
47 pub fn find(config: &Config) -> Result<Self, RepoError> {
48 let current_directory = crate::utils::current_dir()?;
49 // ancestors() is inclusive: it first yields `current_directory` as-is.
50 for ancestor in current_directory.ancestors() {
51 if ancestor.join(".hg").is_dir() {
52 return Ok(Self::new_at_path(ancestor.to_owned(), config)?);
48 ///
49 /// `explicit_path` is for `--repository` command-line arguments.
50 pub fn find(
51 config: &Config,
52 explicit_path: Option<&Path>,
53 ) -> Result<Self, RepoError> {
54 if let Some(root) = explicit_path {
55 // Having an absolute path isn’t necessary here but can help code
56 // elsewhere
57 let root = current_dir()?.join(root);
58 if root.join(".hg").is_dir() {
59 Self::new_at_path(root, config)
60 } else {
61 Err(RepoError::NotFound {
62 at: root.to_owned(),
63 })
53 64 }
65 } else {
66 let current_directory = crate::utils::current_dir()?;
67 // ancestors() is inclusive: it first yields `current_directory`
68 // as-is.
69 for ancestor in current_directory.ancestors() {
70 if ancestor.join(".hg").is_dir() {
71 return Self::new_at_path(ancestor.to_owned(), config);
72 }
73 }
74 Err(RepoError::NotFound {
75 at: current_directory,
76 })
54 77 }
55 Err(RepoError::NotFound { current_directory })
56 78 }
57 79
58 80 /// To be called after checking that `.hg` is a sub-directory
59 81 fn new_at_path(
60 82 working_directory: PathBuf,
61 83 config: &Config,
62 84 ) -> Result<Self, RepoError> {
63 85 let dot_hg = working_directory.join(".hg");
64 86
65 87 let mut repo_config_files = Vec::new();
66 88 repo_config_files.push(dot_hg.join("hgrc"));
67 89 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
68 90
69 91 let hg_vfs = Vfs { base: &dot_hg };
70 92 let mut reqs = requirements::load_if_exists(hg_vfs)?;
71 93 let relative =
72 94 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
73 95 let shared =
74 96 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
75 97
76 98 // From `mercurial/localrepo.py`:
77 99 //
78 100 // if .hg/requires contains the sharesafe requirement, it means
79 101 // there exists a `.hg/store/requires` too and we should read it
80 102 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
81 103 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
82 104 // is not present, refer checkrequirementscompat() for that
83 105 //
84 106 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
85 107 // repository was shared the old way. We check the share source
86 108 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
87 109 // current repository needs to be reshared
88 110 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
89 111
90 112 let store_path;
91 113 if !shared {
92 114 store_path = dot_hg.join("store");
93 115 if share_safe {
94 116 reqs.extend(requirements::load(Vfs { base: &store_path })?);
95 117 }
96 118 } else {
97 119 let bytes = hg_vfs.read("sharedpath")?;
98 120 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
99 121 if relative {
100 122 shared_path = dot_hg.join(shared_path)
101 123 }
102 124 if !shared_path.is_dir() {
103 125 return Err(HgError::corrupted(format!(
104 126 ".hg/sharedpath points to nonexistent directory {}",
105 127 shared_path.display()
106 128 ))
107 129 .into());
108 130 }
109 131
110 132 store_path = shared_path.join("store");
111 133
112 134 let source_is_share_safe =
113 135 requirements::load(Vfs { base: &shared_path })?
114 136 .contains(requirements::SHARESAFE_REQUIREMENT);
115 137
116 138 if share_safe && !source_is_share_safe {
117 139 return Err(match config
118 140 .get(b"safe-mismatch", b"source-not-safe")
119 141 {
120 142 Some(b"abort") | None => HgError::abort(
121 143 "share source does not support share-safe requirement",
122 144 ),
123 145 _ => HgError::unsupported("share-safe downgrade"),
124 146 }
125 147 .into());
126 148 } else if source_is_share_safe && !share_safe {
127 149 return Err(
128 150 match config.get(b"safe-mismatch", b"source-safe") {
129 151 Some(b"abort") | None => HgError::abort(
130 152 "version mismatch: source uses share-safe \
131 153 functionality while the current share does not",
132 154 ),
133 155 _ => HgError::unsupported("share-safe upgrade"),
134 156 }
135 157 .into(),
136 158 );
137 159 }
138 160
139 161 if share_safe {
140 162 repo_config_files.insert(0, shared_path.join("hgrc"))
141 163 }
142 164 }
143 165
144 166 let repo_config = config.combine_with_repo(&repo_config_files)?;
145 167
146 168 let repo = Self {
147 169 requirements: reqs,
148 170 working_directory,
149 171 store: store_path,
150 172 dot_hg,
151 173 config: repo_config,
152 174 };
153 175
154 176 requirements::check(&repo)?;
155 177
156 178 Ok(repo)
157 179 }
158 180
159 181 pub fn working_directory_path(&self) -> &Path {
160 182 &self.working_directory
161 183 }
162 184
163 185 pub fn requirements(&self) -> &HashSet<String> {
164 186 &self.requirements
165 187 }
166 188
167 189 pub fn config(&self) -> &Config {
168 190 &self.config
169 191 }
170 192
171 193 /// For accessing repository files (in `.hg`), except for the store
172 194 /// (`.hg/store`).
173 195 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
174 196 Vfs { base: &self.dot_hg }
175 197 }
176 198
177 199 /// For accessing repository store files (in `.hg/store`)
178 200 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
179 201 Vfs { base: &self.store }
180 202 }
181 203
182 204 /// For accessing the working copy
183 205
184 206 // The undescore prefix silences the "never used" warning. Remove before
185 207 // using.
186 208 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
187 209 Vfs {
188 210 base: &self.working_directory,
189 211 }
190 212 }
191 213 }
192 214
193 215 impl Vfs<'_> {
194 216 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
195 217 self.base.join(relative_path)
196 218 }
197 219
198 220 pub(crate) fn read(
199 221 &self,
200 222 relative_path: impl AsRef<Path>,
201 223 ) -> Result<Vec<u8>, HgError> {
202 224 let path = self.join(relative_path);
203 225 std::fs::read(&path).for_file(&path)
204 226 }
205 227
206 228 pub(crate) fn mmap_open(
207 229 &self,
208 230 relative_path: impl AsRef<Path>,
209 231 ) -> Result<Mmap, HgError> {
210 232 let path = self.base.join(relative_path);
211 233 let file = std::fs::File::open(&path).for_file(&path)?;
212 234 // TODO: what are the safety requirements here?
213 235 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
214 236 Ok(mmap)
215 237 }
216 238 }
@@ -1,73 +1,75 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use clap::Arg;
4 4 use clap::ArgMatches;
5 5 use hg::config::Config;
6 6 use hg::operations::cat;
7 7 use hg::repo::Repo;
8 8 use hg::utils::hg_path::HgPathBuf;
9 9 use micro_timer::timed;
10 10 use std::convert::TryFrom;
11 use std::path::Path;
11 12
12 13 pub const HELP_TEXT: &str = "
13 14 Output the current or given revision of files
14 15 ";
15 16
16 17 pub fn args() -> clap::App<'static, 'static> {
17 18 clap::SubCommand::with_name("cat")
18 19 .arg(
19 20 Arg::with_name("rev")
20 21 .help("search the repository as it is in REV")
21 22 .short("-r")
22 23 .long("--revision")
23 24 .value_name("REV")
24 25 .takes_value(true),
25 26 )
26 27 .arg(
27 28 clap::Arg::with_name("files")
28 29 .required(true)
29 30 .multiple(true)
30 31 .empty_values(false)
31 32 .value_name("FILE")
32 33 .help("Activity to start: activity@category"),
33 34 )
34 35 .about(HELP_TEXT)
35 36 }
36 37
37 38 #[timed]
38 39 pub fn run(
39 40 ui: &Ui,
40 41 config: &Config,
42 repo_path: Option<&Path>,
41 43 args: &ArgMatches,
42 44 ) -> Result<(), CommandError> {
43 45 let rev = args.value_of("rev");
44 46 let file_args = match args.values_of("files") {
45 47 Some(files) => files.collect(),
46 48 None => vec![],
47 49 };
48 50
49 let repo = Repo::find(config)?;
51 let repo = Repo::find(config, repo_path)?;
50 52 let cwd = hg::utils::current_dir()?;
51 53
52 54 let mut files = vec![];
53 55 for file in file_args.iter() {
54 56 // TODO: actually normalize `..` path segments etc?
55 57 let normalized = cwd.join(&file);
56 58 let stripped = normalized
57 59 .strip_prefix(&repo.working_directory_path())
58 60 // TODO: error message for path arguments outside of the repo
59 61 .map_err(|_| CommandError::abort(""))?;
60 62 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
61 63 .map_err(|e| CommandError::abort(e.to_string()))?;
62 64 files.push(hg_file);
63 65 }
64 66
65 67 match rev {
66 68 Some(rev) => {
67 69 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
68 70 ui.write_stdout(&data)?;
69 71 Ok(())
70 72 }
71 73 None => Err(CommandError::Unimplemented.into()),
72 74 }
73 75 }
@@ -1,72 +1,74 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use clap::Arg;
4 4 use clap::ArgGroup;
5 5 use clap::ArgMatches;
6 6 use hg::config::Config;
7 7 use hg::operations::{debug_data, DebugDataKind};
8 8 use hg::repo::Repo;
9 9 use micro_timer::timed;
10 use std::path::Path;
10 11
11 12 pub const HELP_TEXT: &str = "
12 13 Dump the contents of a data file revision
13 14 ";
14 15
15 16 pub fn args() -> clap::App<'static, 'static> {
16 17 clap::SubCommand::with_name("debugdata")
17 18 .arg(
18 19 Arg::with_name("changelog")
19 20 .help("open changelog")
20 21 .short("-c")
21 22 .long("--changelog"),
22 23 )
23 24 .arg(
24 25 Arg::with_name("manifest")
25 26 .help("open manifest")
26 27 .short("-m")
27 28 .long("--manifest"),
28 29 )
29 30 .group(
30 31 ArgGroup::with_name("")
31 32 .args(&["changelog", "manifest"])
32 33 .required(true),
33 34 )
34 35 .arg(
35 36 Arg::with_name("rev")
36 37 .help("revision")
37 38 .required(true)
38 39 .value_name("REV"),
39 40 )
40 41 .about(HELP_TEXT)
41 42 }
42 43
43 44 #[timed]
44 45 pub fn run(
45 46 ui: &Ui,
46 47 config: &Config,
48 repo_path: Option<&Path>,
47 49 args: &ArgMatches,
48 50 ) -> Result<(), CommandError> {
49 51 let rev = args
50 52 .value_of("rev")
51 53 .expect("rev should be a required argument");
52 54 let kind =
53 55 match (args.is_present("changelog"), args.is_present("manifest")) {
54 56 (true, false) => DebugDataKind::Changelog,
55 57 (false, true) => DebugDataKind::Manifest,
56 58 (true, true) => {
57 59 unreachable!("Should not happen since options are exclusive")
58 60 }
59 61 (false, false) => {
60 62 unreachable!("Should not happen since options are required")
61 63 }
62 64 };
63 65
64 let repo = Repo::find(config)?;
66 let repo = Repo::find(config, repo_path)?;
65 67 let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?;
66 68
67 69 let mut stdout = ui.stdout_buffer();
68 70 stdout.write_all(&data)?;
69 71 stdout.flush()?;
70 72
71 73 Ok(())
72 74 }
@@ -1,30 +1,32 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use clap::ArgMatches;
4 4 use hg::config::Config;
5 5 use hg::repo::Repo;
6 use std::path::Path;
6 7
7 8 pub const HELP_TEXT: &str = "
8 9 Print the current repo requirements.
9 10 ";
10 11
11 12 pub fn args() -> clap::App<'static, 'static> {
12 13 clap::SubCommand::with_name("debugrequirements").about(HELP_TEXT)
13 14 }
14 15
15 16 pub fn run(
16 17 ui: &Ui,
17 18 config: &Config,
19 repo_path: Option<&Path>,
18 20 _args: &ArgMatches,
19 21 ) -> Result<(), CommandError> {
20 let repo = Repo::find(config)?;
22 let repo = Repo::find(config, repo_path)?;
21 23 let mut output = String::new();
22 24 let mut requirements: Vec<_> = repo.requirements().iter().collect();
23 25 requirements.sort();
24 26 for req in requirements {
25 27 output.push_str(req);
26 28 output.push('\n');
27 29 }
28 30 ui.write_stdout(output.as_bytes())?;
29 31 Ok(())
30 32 }
@@ -1,69 +1,70 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use clap::Arg;
4 4 use clap::ArgMatches;
5 5 use hg::config::Config;
6 6 use hg::operations::list_rev_tracked_files;
7 7 use hg::operations::Dirstate;
8 8 use hg::repo::Repo;
9 9 use hg::utils::files::{get_bytes_from_path, relativize_path};
10 10 use hg::utils::hg_path::{HgPath, HgPathBuf};
11 use std::path::Path;
11 12
12 13 pub const HELP_TEXT: &str = "
13 14 List tracked files.
14 15
15 16 Returns 0 on success.
16 17 ";
17 18
18 19 pub fn args() -> clap::App<'static, 'static> {
19 20 clap::SubCommand::with_name("files")
20 21 .arg(
21 22 Arg::with_name("rev")
22 23 .help("search the repository as it is in REV")
23 24 .short("-r")
24 25 .long("--revision")
25 26 .value_name("REV")
26 27 .takes_value(true),
27 28 )
28 29 .about(HELP_TEXT)
29 30 }
30 31
31 32 pub fn run(
32 33 ui: &Ui,
33 34 config: &Config,
35 repo_path: Option<&Path>,
34 36 args: &ArgMatches,
35 37 ) -> Result<(), CommandError> {
36 38 let rev = args.value_of("rev");
37 39
38 let repo = Repo::find(config)?;
40 let repo = Repo::find(config, repo_path)?;
39 41 if let Some(rev) = rev {
40 42 let files =
41 43 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
42 44 display_files(ui, &repo, files.iter())
43 45 } else {
44 46 let distate = Dirstate::new(&repo)?;
45 47 let files = distate.tracked_files()?;
46 48 display_files(ui, &repo, files)
47 49 }
48 50 }
49 51
50 52 fn display_files<'a>(
51 53 ui: &Ui,
52 54 repo: &Repo,
53 55 files: impl IntoIterator<Item = &'a HgPath>,
54 56 ) -> Result<(), CommandError> {
55 let cwd = hg::utils::current_dir()?;
56 let rooted_cwd = cwd
57 .strip_prefix(repo.working_directory_path())
58 .expect("cwd was already checked within the repository");
59 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
57 let cwd = HgPathBuf::from(get_bytes_from_path(hg::utils::current_dir()?));
58 let working_directory =
59 HgPathBuf::from(get_bytes_from_path(repo.working_directory_path()));
60 60
61 61 let mut stdout = ui.stdout_buffer();
62 62
63 63 for file in files {
64 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
64 let file = working_directory.join(file);
65 stdout.write_all(relativize_path(&file, &cwd).as_ref())?;
65 66 stdout.write_all(b"\n")?;
66 67 }
67 68 stdout.flush()?;
68 69 Ok(())
69 70 }
@@ -1,28 +1,30 b''
1 1 use crate::error::CommandError;
2 2 use crate::ui::Ui;
3 3 use clap::ArgMatches;
4 4 use format_bytes::format_bytes;
5 5 use hg::config::Config;
6 6 use hg::repo::Repo;
7 7 use hg::utils::files::get_bytes_from_path;
8 use std::path::Path;
8 9
9 10 pub const HELP_TEXT: &str = "
10 11 Print the root directory of the current repository.
11 12
12 13 Returns 0 on success.
13 14 ";
14 15
15 16 pub fn args() -> clap::App<'static, 'static> {
16 17 clap::SubCommand::with_name("root").about(HELP_TEXT)
17 18 }
18 19
19 20 pub fn run(
20 21 ui: &Ui,
21 22 config: &Config,
23 repo_path: Option<&Path>,
22 24 _args: &ArgMatches,
23 25 ) -> Result<(), CommandError> {
24 let repo = Repo::find(config)?;
26 let repo = Repo::find(config, repo_path)?;
25 27 let bytes = get_bytes_from_path(repo.working_directory_path());
26 28 ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
27 29 Ok(())
28 30 }
@@ -1,115 +1,115 b''
1 1 use crate::ui::utf8_to_local;
2 2 use crate::ui::UiError;
3 3 use format_bytes::format_bytes;
4 4 use hg::config::{ConfigError, ConfigParseError};
5 5 use hg::errors::HgError;
6 6 use hg::repo::RepoError;
7 7 use hg::revlog::revlog::RevlogError;
8 8 use hg::utils::files::get_bytes_from_path;
9 9 use std::convert::From;
10 10
11 11 /// The kind of command error
12 12 #[derive(Debug)]
13 13 pub enum CommandError {
14 14 /// Exit with an error message and "standard" failure exit code.
15 15 Abort { message: Vec<u8> },
16 16
17 17 /// A mercurial capability as not been implemented.
18 18 ///
19 19 /// There is no error message printed in this case.
20 20 /// Instead, we exit with a specic status code and a wrapper script may
21 21 /// fallback to Python-based Mercurial.
22 22 Unimplemented,
23 23 }
24 24
25 25 impl CommandError {
26 26 pub fn abort(message: impl AsRef<str>) -> Self {
27 27 CommandError::Abort {
28 28 // TODO: bytes-based (instead of Unicode-based) formatting
29 29 // of error messages to handle non-UTF-8 filenames etc:
30 30 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
31 31 message: utf8_to_local(message.as_ref()).into(),
32 32 }
33 33 }
34 34 }
35 35
36 36 impl From<HgError> for CommandError {
37 37 fn from(error: HgError) -> Self {
38 38 match error {
39 39 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
40 40 _ => CommandError::abort(error.to_string()),
41 41 }
42 42 }
43 43 }
44 44
45 45 impl From<UiError> for CommandError {
46 46 fn from(_error: UiError) -> Self {
47 47 // If we already failed writing to stdout or stderr,
48 48 // writing an error message to stderr about it would be likely to fail
49 49 // too.
50 50 CommandError::abort("")
51 51 }
52 52 }
53 53
54 54 impl From<RepoError> for CommandError {
55 55 fn from(error: RepoError) -> Self {
56 56 match error {
57 RepoError::NotFound { current_directory } => CommandError::Abort {
57 RepoError::NotFound { at } => CommandError::Abort {
58 58 message: format_bytes!(
59 59 b"no repository found in '{}' (.hg not found)!",
60 get_bytes_from_path(current_directory)
60 get_bytes_from_path(at)
61 61 ),
62 62 },
63 63 RepoError::ConfigParseError(error) => error.into(),
64 64 RepoError::Other(error) => error.into(),
65 65 }
66 66 }
67 67 }
68 68
69 69 impl From<ConfigError> for CommandError {
70 70 fn from(error: ConfigError) -> Self {
71 71 match error {
72 72 ConfigError::Parse(error) => error.into(),
73 73 ConfigError::Other(error) => error.into(),
74 74 }
75 75 }
76 76 }
77 77
78 78 impl From<ConfigParseError> for CommandError {
79 79 fn from(error: ConfigParseError) -> Self {
80 80 let ConfigParseError {
81 81 origin,
82 82 line,
83 83 bytes,
84 84 } = error;
85 85 let line_message = if let Some(line_number) = line {
86 86 format_bytes!(b" at line {}", line_number.to_string().into_bytes())
87 87 } else {
88 88 Vec::new()
89 89 };
90 90 CommandError::Abort {
91 91 message: format_bytes!(
92 92 b"config parse error in {}{}: '{}'",
93 93 origin,
94 94 line_message,
95 95 bytes
96 96 ),
97 97 }
98 98 }
99 99 }
100 100
101 101 impl From<(RevlogError, &str)> for CommandError {
102 102 fn from((err, rev): (RevlogError, &str)) -> CommandError {
103 103 match err {
104 104 RevlogError::InvalidRevision => CommandError::abort(format!(
105 105 "invalid revision identifier {}",
106 106 rev
107 107 )),
108 108 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
109 109 "ambiguous revision identifier {}",
110 110 rev
111 111 )),
112 112 RevlogError::Other(error) => error.into(),
113 113 }
114 114 }
115 115 }
@@ -1,94 +1,116 b''
1 1 extern crate log;
2 2 use clap::App;
3 3 use clap::AppSettings;
4 use clap::Arg;
4 5 use clap::ArgMatches;
5 6 use format_bytes::format_bytes;
7 use std::path::Path;
6 8
7 9 mod error;
8 10 mod exitcode;
9 11 mod ui;
10 12 use error::CommandError;
11 13
14 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
15 app.arg(
16 Arg::with_name("repository")
17 .help("repository root directory")
18 .short("-R")
19 .long("--repository")
20 .value_name("REPO")
21 .takes_value(true),
22 )
23 }
24
12 25 fn main() {
13 26 env_logger::init();
14 27 let app = App::new("rhg")
15 28 .setting(AppSettings::AllowInvalidUtf8)
16 29 .setting(AppSettings::SubcommandRequired)
17 30 .setting(AppSettings::VersionlessSubcommands)
18 31 .version("0.0.1");
32 let app = add_global_args(app);
19 33 let app = add_subcommand_args(app);
20 34
21 35 let ui = ui::Ui::new();
22 36
23 37 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
24 38 let _ = ui.writeln_stderr_str(&err.message);
25 39 std::process::exit(exitcode::UNIMPLEMENTED)
26 40 });
41
27 42 let (subcommand_name, subcommand_matches) = matches.subcommand();
28 43 let run = subcommand_run_fn(subcommand_name)
29 44 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
30 45 let args = subcommand_matches
31 46 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
32 47
48 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
49 // `hg log -R ./foo`
50 let global_arg =
51 |name| args.value_of_os(name).or_else(|| matches.value_of_os(name));
52
53 let repo_path = global_arg("repository").map(Path::new);
33 54 let result = (|| -> Result<(), CommandError> {
34 55 let config = hg::config::Config::load()?;
35 run(&ui, &config, args)
56 run(&ui, &config, repo_path, args)
36 57 })();
37 58
38 59 let exit_code = match result {
39 60 Ok(_) => exitcode::OK,
40 61
41 62 // Exit with a specific code and no error message to let a potential
42 63 // wrapper script fallback to Python-based Mercurial.
43 64 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
44 65
45 66 Err(CommandError::Abort { message }) => {
46 67 if !message.is_empty() {
47 68 // Ignore errors when writing to stderr, we’re already exiting
48 69 // with failure code so there’s not much more we can do.
49 70 let _ =
50 71 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
51 72 }
52 73 exitcode::ABORT
53 74 }
54 75 };
55 76 std::process::exit(exit_code)
56 77 }
57 78
58 79 macro_rules! subcommands {
59 80 ($( $command: ident )+) => {
60 81 mod commands {
61 82 $(
62 83 pub mod $command;
63 84 )+
64 85 }
65 86
66 87 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
67 88 app
68 89 $(
69 .subcommand(commands::$command::args())
90 .subcommand(add_global_args(commands::$command::args()))
70 91 )+
71 92 }
72 93
73 94 fn subcommand_run_fn(name: &str) -> Option<fn(
74 95 &ui::Ui,
75 96 &hg::config::Config,
97 Option<&Path>,
76 98 &ArgMatches,
77 99 ) -> Result<(), CommandError>> {
78 100 match name {
79 101 $(
80 102 stringify!($command) => Some(commands::$command::run),
81 103 )+
82 104 _ => None,
83 105 }
84 106 }
85 107 };
86 108 }
87 109
88 110 subcommands! {
89 111 cat
90 112 debugdata
91 113 debugrequirements
92 114 files
93 115 root
94 116 }
@@ -1,262 +1,257 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/release/rhg" ]; then
6 6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
7 7 > else
8 8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 9 > exit 80
10 10 > fi
11 11 > }
12 12
13 13 Unimplemented command
14 14 $ rhg unimplemented-command
15 15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16 16
17 17 USAGE:
18 rhg <SUBCOMMAND>
18 rhg [OPTIONS] <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: $ENOENT$: current directory
42 42 [255]
43 43
44 44 Listing tracked files
45 45 $ cd $TESTTMP
46 46 $ hg init repository
47 47 $ cd repository
48 48 $ for i in 1 2 3; do
49 49 > echo $i >> file$i
50 50 > hg add file$i
51 51 > done
52 52 > hg commit -m "commit $i" -q
53 53
54 54 Listing tracked files from root
55 55 $ rhg files
56 56 file1
57 57 file2
58 58 file3
59 59
60 60 Listing tracked files from subdirectory
61 61 $ mkdir -p path/to/directory
62 62 $ cd path/to/directory
63 63 $ rhg files
64 64 ../../../file1
65 65 ../../../file2
66 66 ../../../file3
67 67
68 68 Listing tracked files through broken pipe
69 69 $ rhg files | head -n 1
70 70 ../../../file1
71 71
72 72 Debuging data in inline index
73 73 $ cd $TESTTMP
74 74 $ rm -rf repository
75 75 $ hg init repository
76 76 $ cd repository
77 77 $ for i in 1 2 3 4 5 6; do
78 78 > echo $i >> file-$i
79 79 > hg add file-$i
80 80 > hg commit -m "Commit $i" -q
81 81 > done
82 82 $ rhg debugdata -c 2
83 83 8d0267cb034247ebfa5ee58ce59e22e57a492297
84 84 test
85 85 0 0
86 86 file-3
87 87
88 88 Commit 3 (no-eol)
89 89 $ rhg debugdata -m 2
90 90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93 93
94 94 Debuging with full node id
95 95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 96 d1d1c679d3053e8926061b6f45ca52009f011e3f
97 97 test
98 98 0 0
99 99 file-1
100 100
101 101 Commit 1 (no-eol)
102 102
103 103 Specifying revisions by changeset ID
104 104 $ hg log -T '{node}\n'
105 105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
106 106 d654274993d0149eecc3cc03214f598320211900
107 107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
108 108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
109 109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
110 110 6ae9681c6d30389694d8701faf24b583cf3ccafe
111 111 $ rhg files -r cf8b83
112 112 file-1
113 113 file-2
114 114 file-3
115 115 $ rhg cat -r cf8b83 file-2
116 116 2
117 117 $ rhg cat -r c file-2
118 118 abort: ambiguous revision identifier c
119 119 [255]
120 120 $ rhg cat -r d file-2
121 121 2
122 122
123 123 Cat files
124 124 $ cd $TESTTMP
125 125 $ rm -rf repository
126 126 $ hg init repository
127 127 $ cd repository
128 128 $ echo "original content" > original
129 129 $ hg add original
130 130 $ hg commit -m "add original" original
131 131 $ rhg cat -r 0 original
132 132 original content
133 133 Cat copied file should not display copy metadata
134 134 $ hg copy original copy_of_original
135 135 $ hg commit -m "add copy of original"
136 136 $ rhg cat -r 1 copy_of_original
137 137 original content
138 138
139 139 Requirements
140 140 $ rhg debugrequirements
141 141 dotencode
142 142 fncache
143 143 generaldelta
144 144 revlogv1
145 145 sparserevlog
146 146 store
147 147
148 148 $ echo indoor-pool >> .hg/requires
149 149 $ rhg files
150 150 [252]
151 151
152 152 $ rhg cat -r 1 copy_of_original
153 153 [252]
154 154
155 155 $ rhg debugrequirements
156 156 [252]
157 157
158 158 $ echo -e '\xFF' >> .hg/requires
159 159 $ rhg debugrequirements
160 160 abort: corrupted repository: parse error in 'requires' file
161 161 [255]
162 162
163 163 Persistent nodemap
164 164 $ cd $TESTTMP
165 165 $ rm -rf repository
166 166 $ hg init repository
167 167 $ cd repository
168 168 $ rhg debugrequirements | grep nodemap
169 169 [1]
170 170 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
171 171 $ hg id -r tip
172 172 c3ae8dec9fad tip
173 173 $ ls .hg/store/00changelog*
174 174 .hg/store/00changelog.d
175 175 .hg/store/00changelog.i
176 176 $ rhg files -r c3ae8dec9fad
177 177 of
178 178
179 179 $ cd $TESTTMP
180 180 $ rm -rf repository
181 181 $ hg --config format.use-persistent-nodemap=True init repository
182 182 $ cd repository
183 183 $ rhg debugrequirements | grep nodemap
184 184 persistent-nodemap
185 185 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
186 186 $ hg id -r tip
187 187 c3ae8dec9fad tip
188 188 $ ls .hg/store/00changelog*
189 189 .hg/store/00changelog-*.nd (glob)
190 190 .hg/store/00changelog.d
191 191 .hg/store/00changelog.i
192 192 .hg/store/00changelog.n
193 193
194 194 Specifying revisions by changeset ID
195 195 $ rhg files -r c3ae8dec9fad
196 196 of
197 197 $ rhg cat -r c3ae8dec9fad of
198 198 r5000
199 199
200 200 Crate a shared repository
201 201
202 202 $ echo "[extensions]" >> $HGRCPATH
203 203 $ echo "share = " >> $HGRCPATH
204 204
205 205 $ cd $TESTTMP
206 206 $ hg init repo1
207 $ cd repo1
208 $ echo a > a
209 $ hg commit -A -m'init'
207 $ echo a > repo1/a
208 $ hg -R repo1 commit -A -m'init'
210 209 adding a
211 210
212 $ cd ..
213 211 $ hg share repo1 repo2
214 212 updating working directory
215 213 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 214
217 215 And check that basic rhg commands work with sharing
218 216
219 $ cd repo2
220 $ rhg files
221 a
222 $ rhg cat -r 0 a
217 $ rhg files -R repo2
218 repo2/a
219 $ rhg -R repo2 cat -r 0 repo2/a
223 220 a
224 221
225 222 Same with relative sharing
226 223
227 $ cd ..
228 224 $ hg share repo2 repo3 --relative
229 225 updating working directory
230 226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 227
232 $ cd repo3
233 $ rhg files
234 a
235 $ rhg cat -r 0 a
228 $ rhg files -R repo3
229 repo3/a
230 $ rhg -R repo3 cat -r 0 repo3/a
236 231 a
237 232
238 233 Same with share-safe
239 234
240 235 $ echo "[format]" >> $HGRCPATH
241 236 $ echo "use-share-safe = True" >> $HGRCPATH
242 237
243 238 $ cd $TESTTMP
244 239 $ hg init repo4
245 240 $ cd repo4
246 241 $ echo a > a
247 242 $ hg commit -A -m'init'
248 243 adding a
249 244
250 245 $ cd ..
251 246 $ hg share repo4 repo5
252 247 updating working directory
253 248 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 249
255 250 And check that basic rhg commands work with sharing
256 251
257 252 $ cd repo5
258 253 $ rhg files
259 254 a
260 255 $ rhg cat -r 0 a
261 256 a
262 257
General Comments 0
You need to be logged in to leave comments. Login now