##// END OF EJS Templates
rhg: look for repository in ancestors also instead of cwd only...
Pulkit Goyal -
r48197:88119fff default
parent child Browse files
Show More
@@ -1,277 +1,284 b''
1 use crate::config::{Config, ConfigError, ConfigParseError};
1 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
3 use crate::requirements;
3 use crate::requirements;
4 use crate::utils::files::get_path_from_bytes;
4 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::SliceExt;
5 use crate::utils::SliceExt;
6 use memmap::{Mmap, MmapOptions};
6 use memmap::{Mmap, MmapOptions};
7 use std::collections::HashSet;
7 use std::collections::HashSet;
8 use std::path::{Path, PathBuf};
8 use std::path::{Path, PathBuf};
9
9
10 /// A repository on disk
10 /// A repository on disk
11 pub struct Repo {
11 pub struct Repo {
12 working_directory: PathBuf,
12 working_directory: PathBuf,
13 dot_hg: PathBuf,
13 dot_hg: PathBuf,
14 store: PathBuf,
14 store: PathBuf,
15 requirements: HashSet<String>,
15 requirements: HashSet<String>,
16 config: Config,
16 config: Config,
17 }
17 }
18
18
19 #[derive(Debug, derive_more::From)]
19 #[derive(Debug, derive_more::From)]
20 pub enum RepoError {
20 pub enum RepoError {
21 NotFound {
21 NotFound {
22 at: PathBuf,
22 at: PathBuf,
23 },
23 },
24 #[from]
24 #[from]
25 ConfigParseError(ConfigParseError),
25 ConfigParseError(ConfigParseError),
26 #[from]
26 #[from]
27 Other(HgError),
27 Other(HgError),
28 }
28 }
29
29
30 impl From<ConfigError> for RepoError {
30 impl From<ConfigError> for RepoError {
31 fn from(error: ConfigError) -> Self {
31 fn from(error: ConfigError) -> Self {
32 match error {
32 match error {
33 ConfigError::Parse(error) => error.into(),
33 ConfigError::Parse(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 #[derive(Clone, Copy)]
40 #[derive(Clone, Copy)]
41 pub struct Vfs<'a> {
41 pub struct Vfs<'a> {
42 pub(crate) base: &'a Path,
42 pub(crate) base: &'a Path,
43 }
43 }
44
44
45 impl Repo {
45 impl Repo {
46 /// tries to find nearest repository root in current working directory or
47 /// its ancestors
48 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
49 let current_directory = crate::utils::current_dir()?;
50 // ancestors() is inclusive: it first yields `current_directory`
51 // as-is.
52 for ancestor in current_directory.ancestors() {
53 if ancestor.join(".hg").is_dir() {
54 return Ok(ancestor.to_path_buf());
55 }
56 }
57 return Err(RepoError::NotFound {
58 at: current_directory,
59 });
60 }
61
46 /// Find a repository, either at the given path (which must contain a `.hg`
62 /// Find a repository, either at the given path (which must contain a `.hg`
47 /// sub-directory) or by searching the current directory and its
63 /// sub-directory) or by searching the current directory and its
48 /// ancestors.
64 /// ancestors.
49 ///
65 ///
50 /// A method with two very different "modes" like this usually a code smell
66 /// A method with two very different "modes" like this usually a code smell
51 /// to make two methods instead, but in this case an `Option` is what rhg
67 /// to make two methods instead, but in this case an `Option` is what rhg
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
68 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 /// Having two methods would just move that `if` to almost all callers.
69 /// Having two methods would just move that `if` to almost all callers.
54 pub fn find(
70 pub fn find(
55 config: &Config,
71 config: &Config,
56 explicit_path: Option<PathBuf>,
72 explicit_path: Option<PathBuf>,
57 ) -> Result<Self, RepoError> {
73 ) -> Result<Self, RepoError> {
58 if let Some(root) = explicit_path {
74 if let Some(root) = explicit_path {
59 if root.join(".hg").is_dir() {
75 if root.join(".hg").is_dir() {
60 Self::new_at_path(root.to_owned(), config)
76 Self::new_at_path(root.to_owned(), config)
61 } else if root.is_file() {
77 } else if root.is_file() {
62 Err(HgError::unsupported("bundle repository").into())
78 Err(HgError::unsupported("bundle repository").into())
63 } else {
79 } else {
64 Err(RepoError::NotFound {
80 Err(RepoError::NotFound {
65 at: root.to_owned(),
81 at: root.to_owned(),
66 })
82 })
67 }
83 }
68 } else {
84 } else {
69 let current_directory = crate::utils::current_dir()?;
85 let root = Self::find_repo_root()?;
70 // ancestors() is inclusive: it first yields `current_directory`
86 Self::new_at_path(root, config)
71 // as-is.
72 for ancestor in current_directory.ancestors() {
73 if ancestor.join(".hg").is_dir() {
74 return Self::new_at_path(ancestor.to_owned(), config);
75 }
76 }
77 Err(RepoError::NotFound {
78 at: current_directory,
79 })
80 }
87 }
81 }
88 }
82
89
83 /// To be called after checking that `.hg` is a sub-directory
90 /// To be called after checking that `.hg` is a sub-directory
84 fn new_at_path(
91 fn new_at_path(
85 working_directory: PathBuf,
92 working_directory: PathBuf,
86 config: &Config,
93 config: &Config,
87 ) -> Result<Self, RepoError> {
94 ) -> Result<Self, RepoError> {
88 let dot_hg = working_directory.join(".hg");
95 let dot_hg = working_directory.join(".hg");
89
96
90 let mut repo_config_files = Vec::new();
97 let mut repo_config_files = Vec::new();
91 repo_config_files.push(dot_hg.join("hgrc"));
98 repo_config_files.push(dot_hg.join("hgrc"));
92 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
99 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
93
100
94 let hg_vfs = Vfs { base: &dot_hg };
101 let hg_vfs = Vfs { base: &dot_hg };
95 let mut reqs = requirements::load_if_exists(hg_vfs)?;
102 let mut reqs = requirements::load_if_exists(hg_vfs)?;
96 let relative =
103 let relative =
97 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
104 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
98 let shared =
105 let shared =
99 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
106 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
100
107
101 // From `mercurial/localrepo.py`:
108 // From `mercurial/localrepo.py`:
102 //
109 //
103 // if .hg/requires contains the sharesafe requirement, it means
110 // if .hg/requires contains the sharesafe requirement, it means
104 // there exists a `.hg/store/requires` too and we should read it
111 // there exists a `.hg/store/requires` too and we should read it
105 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
112 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
106 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
113 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
107 // is not present, refer checkrequirementscompat() for that
114 // is not present, refer checkrequirementscompat() for that
108 //
115 //
109 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
116 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
110 // repository was shared the old way. We check the share source
117 // repository was shared the old way. We check the share source
111 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
118 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
112 // current repository needs to be reshared
119 // current repository needs to be reshared
113 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
120 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
114
121
115 let store_path;
122 let store_path;
116 if !shared {
123 if !shared {
117 store_path = dot_hg.join("store");
124 store_path = dot_hg.join("store");
118 } else {
125 } else {
119 let bytes = hg_vfs.read("sharedpath")?;
126 let bytes = hg_vfs.read("sharedpath")?;
120 let mut shared_path =
127 let mut shared_path =
121 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
128 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
122 if relative {
129 if relative {
123 shared_path = dot_hg.join(shared_path)
130 shared_path = dot_hg.join(shared_path)
124 }
131 }
125 if !shared_path.is_dir() {
132 if !shared_path.is_dir() {
126 return Err(HgError::corrupted(format!(
133 return Err(HgError::corrupted(format!(
127 ".hg/sharedpath points to nonexistent directory {}",
134 ".hg/sharedpath points to nonexistent directory {}",
128 shared_path.display()
135 shared_path.display()
129 ))
136 ))
130 .into());
137 .into());
131 }
138 }
132
139
133 store_path = shared_path.join("store");
140 store_path = shared_path.join("store");
134
141
135 let source_is_share_safe =
142 let source_is_share_safe =
136 requirements::load(Vfs { base: &shared_path })?
143 requirements::load(Vfs { base: &shared_path })?
137 .contains(requirements::SHARESAFE_REQUIREMENT);
144 .contains(requirements::SHARESAFE_REQUIREMENT);
138
145
139 if share_safe && !source_is_share_safe {
146 if share_safe && !source_is_share_safe {
140 return Err(match config
147 return Err(match config
141 .get(b"share", b"safe-mismatch.source-not-safe")
148 .get(b"share", b"safe-mismatch.source-not-safe")
142 {
149 {
143 Some(b"abort") | None => HgError::abort(
150 Some(b"abort") | None => HgError::abort(
144 "abort: share source does not support share-safe requirement\n\
151 "abort: share source does not support share-safe requirement\n\
145 (see `hg help config.format.use-share-safe` for more information)",
152 (see `hg help config.format.use-share-safe` for more information)",
146 ),
153 ),
147 _ => HgError::unsupported("share-safe downgrade"),
154 _ => HgError::unsupported("share-safe downgrade"),
148 }
155 }
149 .into());
156 .into());
150 } else if source_is_share_safe && !share_safe {
157 } else if source_is_share_safe && !share_safe {
151 return Err(
158 return Err(
152 match config.get(b"share", b"safe-mismatch.source-safe") {
159 match config.get(b"share", b"safe-mismatch.source-safe") {
153 Some(b"abort") | None => HgError::abort(
160 Some(b"abort") | None => HgError::abort(
154 "abort: version mismatch: source uses share-safe \
161 "abort: version mismatch: source uses share-safe \
155 functionality while the current share does not\n\
162 functionality while the current share does not\n\
156 (see `hg help config.format.use-share-safe` for more information)",
163 (see `hg help config.format.use-share-safe` for more information)",
157 ),
164 ),
158 _ => HgError::unsupported("share-safe upgrade"),
165 _ => HgError::unsupported("share-safe upgrade"),
159 }
166 }
160 .into(),
167 .into(),
161 );
168 );
162 }
169 }
163
170
164 if share_safe {
171 if share_safe {
165 repo_config_files.insert(0, shared_path.join("hgrc"))
172 repo_config_files.insert(0, shared_path.join("hgrc"))
166 }
173 }
167 }
174 }
168 if share_safe {
175 if share_safe {
169 reqs.extend(requirements::load(Vfs { base: &store_path })?);
176 reqs.extend(requirements::load(Vfs { base: &store_path })?);
170 }
177 }
171
178
172 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
179 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
173 config.combine_with_repo(&repo_config_files)?
180 config.combine_with_repo(&repo_config_files)?
174 } else {
181 } else {
175 config.clone()
182 config.clone()
176 };
183 };
177
184
178 let repo = Self {
185 let repo = Self {
179 requirements: reqs,
186 requirements: reqs,
180 working_directory,
187 working_directory,
181 store: store_path,
188 store: store_path,
182 dot_hg,
189 dot_hg,
183 config: repo_config,
190 config: repo_config,
184 };
191 };
185
192
186 requirements::check(&repo)?;
193 requirements::check(&repo)?;
187
194
188 Ok(repo)
195 Ok(repo)
189 }
196 }
190
197
191 pub fn working_directory_path(&self) -> &Path {
198 pub fn working_directory_path(&self) -> &Path {
192 &self.working_directory
199 &self.working_directory
193 }
200 }
194
201
195 pub fn requirements(&self) -> &HashSet<String> {
202 pub fn requirements(&self) -> &HashSet<String> {
196 &self.requirements
203 &self.requirements
197 }
204 }
198
205
199 pub fn config(&self) -> &Config {
206 pub fn config(&self) -> &Config {
200 &self.config
207 &self.config
201 }
208 }
202
209
203 /// For accessing repository files (in `.hg`), except for the store
210 /// For accessing repository files (in `.hg`), except for the store
204 /// (`.hg/store`).
211 /// (`.hg/store`).
205 pub fn hg_vfs(&self) -> Vfs<'_> {
212 pub fn hg_vfs(&self) -> Vfs<'_> {
206 Vfs { base: &self.dot_hg }
213 Vfs { base: &self.dot_hg }
207 }
214 }
208
215
209 /// For accessing repository store files (in `.hg/store`)
216 /// For accessing repository store files (in `.hg/store`)
210 pub fn store_vfs(&self) -> Vfs<'_> {
217 pub fn store_vfs(&self) -> Vfs<'_> {
211 Vfs { base: &self.store }
218 Vfs { base: &self.store }
212 }
219 }
213
220
214 /// For accessing the working copy
221 /// For accessing the working copy
215 pub fn working_directory_vfs(&self) -> Vfs<'_> {
222 pub fn working_directory_vfs(&self) -> Vfs<'_> {
216 Vfs {
223 Vfs {
217 base: &self.working_directory,
224 base: &self.working_directory,
218 }
225 }
219 }
226 }
220
227
221 pub fn has_dirstate_v2(&self) -> bool {
228 pub fn has_dirstate_v2(&self) -> bool {
222 self.requirements
229 self.requirements
223 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
230 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
224 }
231 }
225
232
226 pub fn dirstate_parents(
233 pub fn dirstate_parents(
227 &self,
234 &self,
228 ) -> Result<crate::dirstate::DirstateParents, HgError> {
235 ) -> Result<crate::dirstate::DirstateParents, HgError> {
229 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
236 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
230 if dirstate.is_empty() {
237 if dirstate.is_empty() {
231 return Ok(crate::dirstate::DirstateParents::NULL);
238 return Ok(crate::dirstate::DirstateParents::NULL);
232 }
239 }
233 let parents = if self.has_dirstate_v2() {
240 let parents = if self.has_dirstate_v2() {
234 crate::dirstate_tree::on_disk::parse_dirstate_parents(&dirstate)?
241 crate::dirstate_tree::on_disk::parse_dirstate_parents(&dirstate)?
235 } else {
242 } else {
236 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
243 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
237 };
244 };
238 Ok(parents.clone())
245 Ok(parents.clone())
239 }
246 }
240 }
247 }
241
248
242 impl Vfs<'_> {
249 impl Vfs<'_> {
243 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
250 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
244 self.base.join(relative_path)
251 self.base.join(relative_path)
245 }
252 }
246
253
247 pub fn read(
254 pub fn read(
248 &self,
255 &self,
249 relative_path: impl AsRef<Path>,
256 relative_path: impl AsRef<Path>,
250 ) -> Result<Vec<u8>, HgError> {
257 ) -> Result<Vec<u8>, HgError> {
251 let path = self.join(relative_path);
258 let path = self.join(relative_path);
252 std::fs::read(&path).when_reading_file(&path)
259 std::fs::read(&path).when_reading_file(&path)
253 }
260 }
254
261
255 pub fn mmap_open(
262 pub fn mmap_open(
256 &self,
263 &self,
257 relative_path: impl AsRef<Path>,
264 relative_path: impl AsRef<Path>,
258 ) -> Result<Mmap, HgError> {
265 ) -> Result<Mmap, HgError> {
259 let path = self.base.join(relative_path);
266 let path = self.base.join(relative_path);
260 let file = std::fs::File::open(&path).when_reading_file(&path)?;
267 let file = std::fs::File::open(&path).when_reading_file(&path)?;
261 // TODO: what are the safety requirements here?
268 // TODO: what are the safety requirements here?
262 let mmap = unsafe { MmapOptions::new().map(&file) }
269 let mmap = unsafe { MmapOptions::new().map(&file) }
263 .when_reading_file(&path)?;
270 .when_reading_file(&path)?;
264 Ok(mmap)
271 Ok(mmap)
265 }
272 }
266
273
267 pub fn rename(
274 pub fn rename(
268 &self,
275 &self,
269 relative_from: impl AsRef<Path>,
276 relative_from: impl AsRef<Path>,
270 relative_to: impl AsRef<Path>,
277 relative_to: impl AsRef<Path>,
271 ) -> Result<(), HgError> {
278 ) -> Result<(), HgError> {
272 let from = self.join(relative_from);
279 let from = self.join(relative_from);
273 let to = self.join(relative_to);
280 let to = self.join(relative_to);
274 std::fs::rename(&from, &to)
281 std::fs::rename(&from, &to)
275 .with_context(|| IoErrorContext::RenamingFile { from, to })
282 .with_context(|| IoErrorContext::RenamingFile { from, to })
276 }
283 }
277 }
284 }
@@ -1,575 +1,574 b''
1 extern crate log;
1 extern crate log;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::App;
3 use clap::App;
4 use clap::AppSettings;
4 use clap::AppSettings;
5 use clap::Arg;
5 use clap::Arg;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use format_bytes::{format_bytes, join};
7 use format_bytes::{format_bytes, join};
8 use hg::config::{Config, ConfigSource};
8 use hg::config::{Config, ConfigSource};
9 use hg::repo::{Repo, RepoError};
9 use hg::repo::{Repo, RepoError};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
11 use hg::utils::SliceExt;
11 use hg::utils::SliceExt;
12 use std::ffi::OsString;
12 use std::ffi::OsString;
13 use std::path::PathBuf;
13 use std::path::PathBuf;
14 use std::process::Command;
14 use std::process::Command;
15
15
16 mod blackbox;
16 mod blackbox;
17 mod error;
17 mod error;
18 mod exitcode;
18 mod exitcode;
19 mod ui;
19 mod ui;
20 use error::CommandError;
20 use error::CommandError;
21
21
22 fn main_with_result(
22 fn main_with_result(
23 process_start_time: &blackbox::ProcessStartTime,
23 process_start_time: &blackbox::ProcessStartTime,
24 ui: &ui::Ui,
24 ui: &ui::Ui,
25 repo: Result<&Repo, &NoRepoInCwdError>,
25 repo: Result<&Repo, &NoRepoInCwdError>,
26 config: &Config,
26 config: &Config,
27 ) -> Result<(), CommandError> {
27 ) -> Result<(), CommandError> {
28 check_extensions(config)?;
28 check_extensions(config)?;
29
29
30 let app = App::new("rhg")
30 let app = App::new("rhg")
31 .global_setting(AppSettings::AllowInvalidUtf8)
31 .global_setting(AppSettings::AllowInvalidUtf8)
32 .global_setting(AppSettings::DisableVersion)
32 .global_setting(AppSettings::DisableVersion)
33 .setting(AppSettings::SubcommandRequired)
33 .setting(AppSettings::SubcommandRequired)
34 .setting(AppSettings::VersionlessSubcommands)
34 .setting(AppSettings::VersionlessSubcommands)
35 .arg(
35 .arg(
36 Arg::with_name("repository")
36 Arg::with_name("repository")
37 .help("repository root directory")
37 .help("repository root directory")
38 .short("-R")
38 .short("-R")
39 .long("--repository")
39 .long("--repository")
40 .value_name("REPO")
40 .value_name("REPO")
41 .takes_value(true)
41 .takes_value(true)
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
43 .global(true),
43 .global(true),
44 )
44 )
45 .arg(
45 .arg(
46 Arg::with_name("config")
46 Arg::with_name("config")
47 .help("set/override config option (use 'section.name=value')")
47 .help("set/override config option (use 'section.name=value')")
48 .long("--config")
48 .long("--config")
49 .value_name("CONFIG")
49 .value_name("CONFIG")
50 .takes_value(true)
50 .takes_value(true)
51 .global(true)
51 .global(true)
52 // Ok: `--config section.key1=val --config section.key2=val2`
52 // Ok: `--config section.key1=val --config section.key2=val2`
53 .multiple(true)
53 .multiple(true)
54 // Not ok: `--config section.key1=val section.key2=val2`
54 // Not ok: `--config section.key1=val section.key2=val2`
55 .number_of_values(1),
55 .number_of_values(1),
56 )
56 )
57 .arg(
57 .arg(
58 Arg::with_name("cwd")
58 Arg::with_name("cwd")
59 .help("change working directory")
59 .help("change working directory")
60 .long("--cwd")
60 .long("--cwd")
61 .value_name("DIR")
61 .value_name("DIR")
62 .takes_value(true)
62 .takes_value(true)
63 .global(true),
63 .global(true),
64 )
64 )
65 .version("0.0.1");
65 .version("0.0.1");
66 let app = add_subcommand_args(app);
66 let app = add_subcommand_args(app);
67
67
68 let matches = app.clone().get_matches_safe()?;
68 let matches = app.clone().get_matches_safe()?;
69
69
70 let (subcommand_name, subcommand_matches) = matches.subcommand();
70 let (subcommand_name, subcommand_matches) = matches.subcommand();
71 let run = subcommand_run_fn(subcommand_name)
71 let run = subcommand_run_fn(subcommand_name)
72 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
72 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
73 let subcommand_args = subcommand_matches
73 let subcommand_args = subcommand_matches
74 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
74 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
75
75
76 let invocation = CliInvocation {
76 let invocation = CliInvocation {
77 ui,
77 ui,
78 subcommand_args,
78 subcommand_args,
79 config,
79 config,
80 repo,
80 repo,
81 };
81 };
82 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
82 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
83 blackbox.log_command_start();
83 blackbox.log_command_start();
84 let result = run(&invocation);
84 let result = run(&invocation);
85 blackbox.log_command_end(exit_code(
85 blackbox.log_command_end(exit_code(
86 &result,
86 &result,
87 // TODO: show a warning or combine with original error if `get_bool`
87 // TODO: show a warning or combine with original error if `get_bool`
88 // returns an error
88 // returns an error
89 config
89 config
90 .get_bool(b"ui", b"detailed-exit-code")
90 .get_bool(b"ui", b"detailed-exit-code")
91 .unwrap_or(false),
91 .unwrap_or(false),
92 ));
92 ));
93 result
93 result
94 }
94 }
95
95
96 fn main() {
96 fn main() {
97 // Run this first, before we find out if the blackbox extension is even
97 // Run this first, before we find out if the blackbox extension is even
98 // enabled, in order to include everything in-between in the duration
98 // enabled, in order to include everything in-between in the duration
99 // measurements. Reading config files can be slow if they’re on NFS.
99 // measurements. Reading config files can be slow if they’re on NFS.
100 let process_start_time = blackbox::ProcessStartTime::now();
100 let process_start_time = blackbox::ProcessStartTime::now();
101
101
102 env_logger::init();
102 env_logger::init();
103 let ui = ui::Ui::new();
103 let ui = ui::Ui::new();
104
104
105 let early_args = EarlyArgs::parse(std::env::args_os());
105 let early_args = EarlyArgs::parse(std::env::args_os());
106
106
107 let initial_current_dir = early_args.cwd.map(|cwd| {
107 let initial_current_dir = early_args.cwd.map(|cwd| {
108 let cwd = get_path_from_bytes(&cwd);
108 let cwd = get_path_from_bytes(&cwd);
109 std::env::current_dir()
109 std::env::current_dir()
110 .and_then(|initial| {
110 .and_then(|initial| {
111 std::env::set_current_dir(cwd)?;
111 std::env::set_current_dir(cwd)?;
112 Ok(initial)
112 Ok(initial)
113 })
113 })
114 .unwrap_or_else(|error| {
114 .unwrap_or_else(|error| {
115 exit(
115 exit(
116 &None,
116 &None,
117 &ui,
117 &ui,
118 OnUnsupported::Abort,
118 OnUnsupported::Abort,
119 Err(CommandError::abort(format!(
119 Err(CommandError::abort(format!(
120 "abort: {}: '{}'",
120 "abort: {}: '{}'",
121 error,
121 error,
122 cwd.display()
122 cwd.display()
123 ))),
123 ))),
124 false,
124 false,
125 )
125 )
126 })
126 })
127 });
127 });
128
128
129 let non_repo_config =
129 let non_repo_config =
130 Config::load(early_args.config).unwrap_or_else(|error| {
130 Config::load(early_args.config).unwrap_or_else(|error| {
131 // Normally this is decided based on config, but we don’t have that
131 // Normally this is decided based on config, but we don’t have that
132 // available. As of this writing config loading never returns an
132 // available. As of this writing config loading never returns an
133 // "unsupported" error but that is not enforced by the type system.
133 // "unsupported" error but that is not enforced by the type system.
134 let on_unsupported = OnUnsupported::Abort;
134 let on_unsupported = OnUnsupported::Abort;
135
135
136 exit(
136 exit(
137 &initial_current_dir,
137 &initial_current_dir,
138 &ui,
138 &ui,
139 on_unsupported,
139 on_unsupported,
140 Err(error.into()),
140 Err(error.into()),
141 false,
141 false,
142 )
142 )
143 });
143 });
144
144
145 if let Some(repo_path_bytes) = &early_args.repo {
145 if let Some(repo_path_bytes) = &early_args.repo {
146 lazy_static::lazy_static! {
146 lazy_static::lazy_static! {
147 static ref SCHEME_RE: regex::bytes::Regex =
147 static ref SCHEME_RE: regex::bytes::Regex =
148 // Same as `_matchscheme` in `mercurial/util.py`
148 // Same as `_matchscheme` in `mercurial/util.py`
149 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
149 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
150 }
150 }
151 if SCHEME_RE.is_match(&repo_path_bytes) {
151 if SCHEME_RE.is_match(&repo_path_bytes) {
152 exit(
152 exit(
153 &initial_current_dir,
153 &initial_current_dir,
154 &ui,
154 &ui,
155 OnUnsupported::from_config(&ui, &non_repo_config),
155 OnUnsupported::from_config(&ui, &non_repo_config),
156 Err(CommandError::UnsupportedFeature {
156 Err(CommandError::UnsupportedFeature {
157 message: format_bytes!(
157 message: format_bytes!(
158 b"URL-like --repository {}",
158 b"URL-like --repository {}",
159 repo_path_bytes
159 repo_path_bytes
160 ),
160 ),
161 }),
161 }),
162 // TODO: show a warning or combine with original error if
162 // TODO: show a warning or combine with original error if
163 // `get_bool` returns an error
163 // `get_bool` returns an error
164 non_repo_config
164 non_repo_config
165 .get_bool(b"ui", b"detailed-exit-code")
165 .get_bool(b"ui", b"detailed-exit-code")
166 .unwrap_or(false),
166 .unwrap_or(false),
167 )
167 )
168 }
168 }
169 }
169 }
170 let repo_arg = early_args.repo.unwrap_or(Vec::new());
170 let repo_arg = early_args.repo.unwrap_or(Vec::new());
171 let repo_path: Option<PathBuf> = {
171 let repo_path: Option<PathBuf> = {
172 if repo_arg.is_empty() {
172 if repo_arg.is_empty() {
173 None
173 None
174 } else {
174 } else {
175 let local_config = {
175 let local_config = {
176 if std::env::var_os("HGRCSKIPREPO").is_none() {
176 if std::env::var_os("HGRCSKIPREPO").is_none() {
177 let current_dir = hg::utils::current_dir();
177 // TODO: handle errors from find_repo_root
178 // TODO: handle errors from current_dir
178 if let Ok(current_dir_path) = Repo::find_repo_root() {
179 if let Ok(current_dir_path) = current_dir {
180 let config_files = vec![
179 let config_files = vec![
181 ConfigSource::AbsPath(
180 ConfigSource::AbsPath(
182 current_dir_path.join(".hg/hgrc"),
181 current_dir_path.join(".hg/hgrc"),
183 ),
182 ),
184 ConfigSource::AbsPath(
183 ConfigSource::AbsPath(
185 current_dir_path.join(".hg/hgrc-not-shared"),
184 current_dir_path.join(".hg/hgrc-not-shared"),
186 ),
185 ),
187 ];
186 ];
188 // TODO: handle errors from
187 // TODO: handle errors from
189 // `load_from_explicit_sources`
188 // `load_from_explicit_sources`
190 Config::load_from_explicit_sources(config_files).ok()
189 Config::load_from_explicit_sources(config_files).ok()
191 } else {
190 } else {
192 None
191 None
193 }
192 }
194 } else {
193 } else {
195 None
194 None
196 }
195 }
197 };
196 };
198
197
199 let non_repo_config_val = {
198 let non_repo_config_val = {
200 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
199 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
201 match &non_repo_val {
200 match &non_repo_val {
202 Some(val) if val.len() > 0 => home::home_dir()
201 Some(val) if val.len() > 0 => home::home_dir()
203 .unwrap_or_else(|| PathBuf::from("~"))
202 .unwrap_or_else(|| PathBuf::from("~"))
204 .join(get_path_from_bytes(val))
203 .join(get_path_from_bytes(val))
205 .canonicalize()
204 .canonicalize()
206 // TODO: handle error and make it similar to python
205 // TODO: handle error and make it similar to python
207 // implementation maybe?
206 // implementation maybe?
208 .ok(),
207 .ok(),
209 _ => None,
208 _ => None,
210 }
209 }
211 };
210 };
212
211
213 let config_val = match &local_config {
212 let config_val = match &local_config {
214 None => non_repo_config_val,
213 None => non_repo_config_val,
215 Some(val) => {
214 Some(val) => {
216 let local_config_val = val.get(b"paths", &repo_arg);
215 let local_config_val = val.get(b"paths", &repo_arg);
217 match &local_config_val {
216 match &local_config_val {
218 Some(val) if val.len() > 0 => {
217 Some(val) if val.len() > 0 => {
219 // presence of a local_config assures that
218 // presence of a local_config assures that
220 // current_dir
219 // current_dir
221 // wont result in an Error
220 // wont result in an Error
222 let canpath = hg::utils::current_dir()
221 let canpath = hg::utils::current_dir()
223 .unwrap()
222 .unwrap()
224 .join(get_path_from_bytes(val))
223 .join(get_path_from_bytes(val))
225 .canonicalize();
224 .canonicalize();
226 canpath.ok().or(non_repo_config_val)
225 canpath.ok().or(non_repo_config_val)
227 }
226 }
228 _ => non_repo_config_val,
227 _ => non_repo_config_val,
229 }
228 }
230 }
229 }
231 };
230 };
232 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
231 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
233 }
232 }
234 };
233 };
235
234
236 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
235 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
237 {
236 {
238 Ok(repo) => Ok(repo),
237 Ok(repo) => Ok(repo),
239 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
238 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
240 // Not finding a repo is not fatal yet, if `-R` was not given
239 // Not finding a repo is not fatal yet, if `-R` was not given
241 Err(NoRepoInCwdError { cwd: at })
240 Err(NoRepoInCwdError { cwd: at })
242 }
241 }
243 Err(error) => exit(
242 Err(error) => exit(
244 &initial_current_dir,
243 &initial_current_dir,
245 &ui,
244 &ui,
246 OnUnsupported::from_config(&ui, &non_repo_config),
245 OnUnsupported::from_config(&ui, &non_repo_config),
247 Err(error.into()),
246 Err(error.into()),
248 // TODO: show a warning or combine with original error if
247 // TODO: show a warning or combine with original error if
249 // `get_bool` returns an error
248 // `get_bool` returns an error
250 non_repo_config
249 non_repo_config
251 .get_bool(b"ui", b"detailed-exit-code")
250 .get_bool(b"ui", b"detailed-exit-code")
252 .unwrap_or(false),
251 .unwrap_or(false),
253 ),
252 ),
254 };
253 };
255
254
256 let config = if let Ok(repo) = &repo_result {
255 let config = if let Ok(repo) = &repo_result {
257 repo.config()
256 repo.config()
258 } else {
257 } else {
259 &non_repo_config
258 &non_repo_config
260 };
259 };
261 let on_unsupported = OnUnsupported::from_config(&ui, config);
260 let on_unsupported = OnUnsupported::from_config(&ui, config);
262
261
263 let result = main_with_result(
262 let result = main_with_result(
264 &process_start_time,
263 &process_start_time,
265 &ui,
264 &ui,
266 repo_result.as_ref(),
265 repo_result.as_ref(),
267 config,
266 config,
268 );
267 );
269 exit(
268 exit(
270 &initial_current_dir,
269 &initial_current_dir,
271 &ui,
270 &ui,
272 on_unsupported,
271 on_unsupported,
273 result,
272 result,
274 // TODO: show a warning or combine with original error if `get_bool`
273 // TODO: show a warning or combine with original error if `get_bool`
275 // returns an error
274 // returns an error
276 config
275 config
277 .get_bool(b"ui", b"detailed-exit-code")
276 .get_bool(b"ui", b"detailed-exit-code")
278 .unwrap_or(false),
277 .unwrap_or(false),
279 )
278 )
280 }
279 }
281
280
282 fn exit_code(
281 fn exit_code(
283 result: &Result<(), CommandError>,
282 result: &Result<(), CommandError>,
284 use_detailed_exit_code: bool,
283 use_detailed_exit_code: bool,
285 ) -> i32 {
284 ) -> i32 {
286 match result {
285 match result {
287 Ok(()) => exitcode::OK,
286 Ok(()) => exitcode::OK,
288 Err(CommandError::Abort {
287 Err(CommandError::Abort {
289 message: _,
288 message: _,
290 detailed_exit_code,
289 detailed_exit_code,
291 }) => {
290 }) => {
292 if use_detailed_exit_code {
291 if use_detailed_exit_code {
293 *detailed_exit_code
292 *detailed_exit_code
294 } else {
293 } else {
295 exitcode::ABORT
294 exitcode::ABORT
296 }
295 }
297 }
296 }
298 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
297 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
299
298
300 // Exit with a specific code and no error message to let a potential
299 // Exit with a specific code and no error message to let a potential
301 // wrapper script fallback to Python-based Mercurial.
300 // wrapper script fallback to Python-based Mercurial.
302 Err(CommandError::UnsupportedFeature { .. }) => {
301 Err(CommandError::UnsupportedFeature { .. }) => {
303 exitcode::UNIMPLEMENTED
302 exitcode::UNIMPLEMENTED
304 }
303 }
305 }
304 }
306 }
305 }
307
306
308 fn exit(
307 fn exit(
309 initial_current_dir: &Option<PathBuf>,
308 initial_current_dir: &Option<PathBuf>,
310 ui: &Ui,
309 ui: &Ui,
311 mut on_unsupported: OnUnsupported,
310 mut on_unsupported: OnUnsupported,
312 result: Result<(), CommandError>,
311 result: Result<(), CommandError>,
313 use_detailed_exit_code: bool,
312 use_detailed_exit_code: bool,
314 ) -> ! {
313 ) -> ! {
315 if let (
314 if let (
316 OnUnsupported::Fallback { executable },
315 OnUnsupported::Fallback { executable },
317 Err(CommandError::UnsupportedFeature { .. }),
316 Err(CommandError::UnsupportedFeature { .. }),
318 ) = (&on_unsupported, &result)
317 ) = (&on_unsupported, &result)
319 {
318 {
320 let mut args = std::env::args_os();
319 let mut args = std::env::args_os();
321 let executable_path = get_path_from_bytes(&executable);
320 let executable_path = get_path_from_bytes(&executable);
322 let this_executable = args.next().expect("exepcted argv[0] to exist");
321 let this_executable = args.next().expect("exepcted argv[0] to exist");
323 if executable_path == &PathBuf::from(this_executable) {
322 if executable_path == &PathBuf::from(this_executable) {
324 // Avoid spawning infinitely many processes until resource
323 // Avoid spawning infinitely many processes until resource
325 // exhaustion.
324 // exhaustion.
326 let _ = ui.write_stderr(&format_bytes!(
325 let _ = ui.write_stderr(&format_bytes!(
327 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
326 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
328 points to `rhg` itself.\n",
327 points to `rhg` itself.\n",
329 executable
328 executable
330 ));
329 ));
331 on_unsupported = OnUnsupported::Abort
330 on_unsupported = OnUnsupported::Abort
332 } else {
331 } else {
333 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
332 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
334 let mut command = Command::new(executable_path);
333 let mut command = Command::new(executable_path);
335 command.args(args);
334 command.args(args);
336 if let Some(initial) = initial_current_dir {
335 if let Some(initial) = initial_current_dir {
337 command.current_dir(initial);
336 command.current_dir(initial);
338 }
337 }
339 let result = command.status();
338 let result = command.status();
340 match result {
339 match result {
341 Ok(status) => std::process::exit(
340 Ok(status) => std::process::exit(
342 status.code().unwrap_or(exitcode::ABORT),
341 status.code().unwrap_or(exitcode::ABORT),
343 ),
342 ),
344 Err(error) => {
343 Err(error) => {
345 let _ = ui.write_stderr(&format_bytes!(
344 let _ = ui.write_stderr(&format_bytes!(
346 b"tried to fall back to a '{}' sub-process but got error {}\n",
345 b"tried to fall back to a '{}' sub-process but got error {}\n",
347 executable, format_bytes::Utf8(error)
346 executable, format_bytes::Utf8(error)
348 ));
347 ));
349 on_unsupported = OnUnsupported::Abort
348 on_unsupported = OnUnsupported::Abort
350 }
349 }
351 }
350 }
352 }
351 }
353 }
352 }
354 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
353 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
355 }
354 }
356
355
357 fn exit_no_fallback(
356 fn exit_no_fallback(
358 ui: &Ui,
357 ui: &Ui,
359 on_unsupported: OnUnsupported,
358 on_unsupported: OnUnsupported,
360 result: Result<(), CommandError>,
359 result: Result<(), CommandError>,
361 use_detailed_exit_code: bool,
360 use_detailed_exit_code: bool,
362 ) -> ! {
361 ) -> ! {
363 match &result {
362 match &result {
364 Ok(_) => {}
363 Ok(_) => {}
365 Err(CommandError::Unsuccessful) => {}
364 Err(CommandError::Unsuccessful) => {}
366 Err(CommandError::Abort {
365 Err(CommandError::Abort {
367 message,
366 message,
368 detailed_exit_code: _,
367 detailed_exit_code: _,
369 }) => {
368 }) => {
370 if !message.is_empty() {
369 if !message.is_empty() {
371 // Ignore errors when writing to stderr, we’re already exiting
370 // Ignore errors when writing to stderr, we’re already exiting
372 // with failure code so there’s not much more we can do.
371 // with failure code so there’s not much more we can do.
373 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
372 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
374 }
373 }
375 }
374 }
376 Err(CommandError::UnsupportedFeature { message }) => {
375 Err(CommandError::UnsupportedFeature { message }) => {
377 match on_unsupported {
376 match on_unsupported {
378 OnUnsupported::Abort => {
377 OnUnsupported::Abort => {
379 let _ = ui.write_stderr(&format_bytes!(
378 let _ = ui.write_stderr(&format_bytes!(
380 b"unsupported feature: {}\n",
379 b"unsupported feature: {}\n",
381 message
380 message
382 ));
381 ));
383 }
382 }
384 OnUnsupported::AbortSilent => {}
383 OnUnsupported::AbortSilent => {}
385 OnUnsupported::Fallback { .. } => unreachable!(),
384 OnUnsupported::Fallback { .. } => unreachable!(),
386 }
385 }
387 }
386 }
388 }
387 }
389 std::process::exit(exit_code(&result, use_detailed_exit_code))
388 std::process::exit(exit_code(&result, use_detailed_exit_code))
390 }
389 }
391
390
392 macro_rules! subcommands {
391 macro_rules! subcommands {
393 ($( $command: ident )+) => {
392 ($( $command: ident )+) => {
394 mod commands {
393 mod commands {
395 $(
394 $(
396 pub mod $command;
395 pub mod $command;
397 )+
396 )+
398 }
397 }
399
398
400 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
399 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
401 app
400 app
402 $(
401 $(
403 .subcommand(commands::$command::args())
402 .subcommand(commands::$command::args())
404 )+
403 )+
405 }
404 }
406
405
407 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
406 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
408
407
409 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
408 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
410 match name {
409 match name {
411 $(
410 $(
412 stringify!($command) => Some(commands::$command::run),
411 stringify!($command) => Some(commands::$command::run),
413 )+
412 )+
414 _ => None,
413 _ => None,
415 }
414 }
416 }
415 }
417 };
416 };
418 }
417 }
419
418
420 subcommands! {
419 subcommands! {
421 cat
420 cat
422 debugdata
421 debugdata
423 debugrequirements
422 debugrequirements
424 files
423 files
425 root
424 root
426 config
425 config
427 status
426 status
428 }
427 }
429
428
430 pub struct CliInvocation<'a> {
429 pub struct CliInvocation<'a> {
431 ui: &'a Ui,
430 ui: &'a Ui,
432 subcommand_args: &'a ArgMatches<'a>,
431 subcommand_args: &'a ArgMatches<'a>,
433 config: &'a Config,
432 config: &'a Config,
434 /// References inside `Result` is a bit peculiar but allow
433 /// References inside `Result` is a bit peculiar but allow
435 /// `invocation.repo?` to work out with `&CliInvocation` since this
434 /// `invocation.repo?` to work out with `&CliInvocation` since this
436 /// `Result` type is `Copy`.
435 /// `Result` type is `Copy`.
437 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
436 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
438 }
437 }
439
438
440 struct NoRepoInCwdError {
439 struct NoRepoInCwdError {
441 cwd: PathBuf,
440 cwd: PathBuf,
442 }
441 }
443
442
444 /// CLI arguments to be parsed "early" in order to be able to read
443 /// CLI arguments to be parsed "early" in order to be able to read
445 /// configuration before using Clap. Ideally we would also use Clap for this,
444 /// configuration before using Clap. Ideally we would also use Clap for this,
446 /// see <https://github.com/clap-rs/clap/discussions/2366>.
445 /// see <https://github.com/clap-rs/clap/discussions/2366>.
447 ///
446 ///
448 /// These arguments are still declared when we do use Clap later, so that Clap
447 /// These arguments are still declared when we do use Clap later, so that Clap
449 /// does not return an error for their presence.
448 /// does not return an error for their presence.
450 struct EarlyArgs {
449 struct EarlyArgs {
451 /// Values of all `--config` arguments. (Possibly none)
450 /// Values of all `--config` arguments. (Possibly none)
452 config: Vec<Vec<u8>>,
451 config: Vec<Vec<u8>>,
453 /// Value of the `-R` or `--repository` argument, if any.
452 /// Value of the `-R` or `--repository` argument, if any.
454 repo: Option<Vec<u8>>,
453 repo: Option<Vec<u8>>,
455 /// Value of the `--cwd` argument, if any.
454 /// Value of the `--cwd` argument, if any.
456 cwd: Option<Vec<u8>>,
455 cwd: Option<Vec<u8>>,
457 }
456 }
458
457
459 impl EarlyArgs {
458 impl EarlyArgs {
460 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
459 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
461 let mut args = args.into_iter().map(get_bytes_from_os_str);
460 let mut args = args.into_iter().map(get_bytes_from_os_str);
462 let mut config = Vec::new();
461 let mut config = Vec::new();
463 let mut repo = None;
462 let mut repo = None;
464 let mut cwd = None;
463 let mut cwd = None;
465 // Use `while let` instead of `for` so that we can also call
464 // Use `while let` instead of `for` so that we can also call
466 // `args.next()` inside the loop.
465 // `args.next()` inside the loop.
467 while let Some(arg) = args.next() {
466 while let Some(arg) = args.next() {
468 if arg == b"--config" {
467 if arg == b"--config" {
469 if let Some(value) = args.next() {
468 if let Some(value) = args.next() {
470 config.push(value)
469 config.push(value)
471 }
470 }
472 } else if let Some(value) = arg.drop_prefix(b"--config=") {
471 } else if let Some(value) = arg.drop_prefix(b"--config=") {
473 config.push(value.to_owned())
472 config.push(value.to_owned())
474 }
473 }
475
474
476 if arg == b"--cwd" {
475 if arg == b"--cwd" {
477 if let Some(value) = args.next() {
476 if let Some(value) = args.next() {
478 cwd = Some(value)
477 cwd = Some(value)
479 }
478 }
480 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
479 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
481 cwd = Some(value.to_owned())
480 cwd = Some(value.to_owned())
482 }
481 }
483
482
484 if arg == b"--repository" || arg == b"-R" {
483 if arg == b"--repository" || arg == b"-R" {
485 if let Some(value) = args.next() {
484 if let Some(value) = args.next() {
486 repo = Some(value)
485 repo = Some(value)
487 }
486 }
488 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
487 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
489 repo = Some(value.to_owned())
488 repo = Some(value.to_owned())
490 } else if let Some(value) = arg.drop_prefix(b"-R") {
489 } else if let Some(value) = arg.drop_prefix(b"-R") {
491 repo = Some(value.to_owned())
490 repo = Some(value.to_owned())
492 }
491 }
493 }
492 }
494 Self { config, repo, cwd }
493 Self { config, repo, cwd }
495 }
494 }
496 }
495 }
497
496
498 /// What to do when encountering some unsupported feature.
497 /// What to do when encountering some unsupported feature.
499 ///
498 ///
500 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
499 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
501 enum OnUnsupported {
500 enum OnUnsupported {
502 /// Print an error message describing what feature is not supported,
501 /// Print an error message describing what feature is not supported,
503 /// and exit with code 252.
502 /// and exit with code 252.
504 Abort,
503 Abort,
505 /// Silently exit with code 252.
504 /// Silently exit with code 252.
506 AbortSilent,
505 AbortSilent,
507 /// Try running a Python implementation
506 /// Try running a Python implementation
508 Fallback { executable: Vec<u8> },
507 Fallback { executable: Vec<u8> },
509 }
508 }
510
509
511 impl OnUnsupported {
510 impl OnUnsupported {
512 const DEFAULT: Self = OnUnsupported::Abort;
511 const DEFAULT: Self = OnUnsupported::Abort;
513
512
514 fn from_config(ui: &Ui, config: &Config) -> Self {
513 fn from_config(ui: &Ui, config: &Config) -> Self {
515 match config
514 match config
516 .get(b"rhg", b"on-unsupported")
515 .get(b"rhg", b"on-unsupported")
517 .map(|value| value.to_ascii_lowercase())
516 .map(|value| value.to_ascii_lowercase())
518 .as_deref()
517 .as_deref()
519 {
518 {
520 Some(b"abort") => OnUnsupported::Abort,
519 Some(b"abort") => OnUnsupported::Abort,
521 Some(b"abort-silent") => OnUnsupported::AbortSilent,
520 Some(b"abort-silent") => OnUnsupported::AbortSilent,
522 Some(b"fallback") => OnUnsupported::Fallback {
521 Some(b"fallback") => OnUnsupported::Fallback {
523 executable: config
522 executable: config
524 .get(b"rhg", b"fallback-executable")
523 .get(b"rhg", b"fallback-executable")
525 .unwrap_or_else(|| {
524 .unwrap_or_else(|| {
526 exit_no_fallback(
525 exit_no_fallback(
527 ui,
526 ui,
528 Self::Abort,
527 Self::Abort,
529 Err(CommandError::abort(
528 Err(CommandError::abort(
530 "abort: 'rhg.on-unsupported=fallback' without \
529 "abort: 'rhg.on-unsupported=fallback' without \
531 'rhg.fallback-executable' set."
530 'rhg.fallback-executable' set."
532 )),
531 )),
533 false,
532 false,
534 )
533 )
535 })
534 })
536 .to_owned(),
535 .to_owned(),
537 },
536 },
538 None => Self::DEFAULT,
537 None => Self::DEFAULT,
539 Some(_) => {
538 Some(_) => {
540 // TODO: warn about unknown config value
539 // TODO: warn about unknown config value
541 Self::DEFAULT
540 Self::DEFAULT
542 }
541 }
543 }
542 }
544 }
543 }
545 }
544 }
546
545
547 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
546 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
548
547
549 fn check_extensions(config: &Config) -> Result<(), CommandError> {
548 fn check_extensions(config: &Config) -> Result<(), CommandError> {
550 let enabled = config.get_section_keys(b"extensions");
549 let enabled = config.get_section_keys(b"extensions");
551
550
552 let mut unsupported = enabled;
551 let mut unsupported = enabled;
553 for supported in SUPPORTED_EXTENSIONS {
552 for supported in SUPPORTED_EXTENSIONS {
554 unsupported.remove(supported);
553 unsupported.remove(supported);
555 }
554 }
556
555
557 if let Some(ignored_list) =
556 if let Some(ignored_list) =
558 config.get_simple_list(b"rhg", b"ignored-extensions")
557 config.get_simple_list(b"rhg", b"ignored-extensions")
559 {
558 {
560 for ignored in ignored_list {
559 for ignored in ignored_list {
561 unsupported.remove(ignored);
560 unsupported.remove(ignored);
562 }
561 }
563 }
562 }
564
563
565 if unsupported.is_empty() {
564 if unsupported.is_empty() {
566 Ok(())
565 Ok(())
567 } else {
566 } else {
568 Err(CommandError::UnsupportedFeature {
567 Err(CommandError::UnsupportedFeature {
569 message: format_bytes!(
568 message: format_bytes!(
570 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
569 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
571 join(unsupported, b", ")
570 join(unsupported, b", ")
572 ),
571 ),
573 })
572 })
574 }
573 }
575 }
574 }
General Comments 0
You need to be logged in to leave comments. Login now