##// END OF EJS Templates
rhg: Add support for the HGRCSKIPREPO environment variable...
Simon Sapin -
r47475:25e3dac5 default
parent child Browse files
Show More
@@ -1,463 +1,464 b''
1 1 // config.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 use super::layer;
11 11 use super::values;
12 12 use crate::config::layer::{
13 13 ConfigError, ConfigLayer, ConfigOrigin, ConfigValue,
14 14 };
15 15 use crate::utils::files::get_bytes_from_os_str;
16 16 use crate::utils::SliceExt;
17 17 use format_bytes::{write_bytes, DisplayBytes};
18 18 use std::collections::HashSet;
19 19 use std::env;
20 20 use std::path::{Path, PathBuf};
21 21 use std::str;
22 22
23 23 use crate::errors::{HgResultExt, IoResultExt};
24 24
25 25 /// Holds the config values for the current repository
26 26 /// TODO update this docstring once we support more sources
27 #[derive(Clone)]
27 28 pub struct Config {
28 29 layers: Vec<layer::ConfigLayer>,
29 30 }
30 31
31 32 impl DisplayBytes for Config {
32 33 fn display_bytes(
33 34 &self,
34 35 out: &mut dyn std::io::Write,
35 36 ) -> std::io::Result<()> {
36 37 for (index, layer) in self.layers.iter().rev().enumerate() {
37 38 write_bytes!(
38 39 out,
39 40 b"==== Layer {} (trusted: {}) ====\n{}",
40 41 index,
41 42 if layer.trusted {
42 43 &b"yes"[..]
43 44 } else {
44 45 &b"no"[..]
45 46 },
46 47 layer
47 48 )?;
48 49 }
49 50 Ok(())
50 51 }
51 52 }
52 53
53 54 pub enum ConfigSource {
54 55 /// Absolute path to a config file
55 56 AbsPath(PathBuf),
56 57 /// Already parsed (from the CLI, env, Python resources, etc.)
57 58 Parsed(layer::ConfigLayer),
58 59 }
59 60
60 61 #[derive(Debug)]
61 62 pub struct ConfigValueParseError {
62 63 pub origin: ConfigOrigin,
63 64 pub line: Option<usize>,
64 65 pub section: Vec<u8>,
65 66 pub item: Vec<u8>,
66 67 pub value: Vec<u8>,
67 68 pub expected_type: &'static str,
68 69 }
69 70
70 71 impl Config {
71 72 /// Load system and user configuration from various files.
72 73 ///
73 74 /// This is also affected by some environment variables.
74 75 pub fn load(
75 76 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
76 77 ) -> Result<Self, ConfigError> {
77 78 let mut config = Self { layers: Vec::new() };
78 79 let opt_rc_path = env::var_os("HGRCPATH");
79 80 // HGRCPATH replaces system config
80 81 if opt_rc_path.is_none() {
81 82 config.add_system_config()?
82 83 }
83 84
84 85 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
85 86 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
86 87 config.add_for_environment_variable("PAGER", b"pager", b"pager");
87 88
88 89 // These are set by `run-tests.py --rhg` to enable fallback for the
89 90 // entire test suite. Alternatives would be setting configuration
90 91 // through `$HGRCPATH` but some tests override that, or changing the
91 92 // `hg` shell alias to include `--config` but that disrupts tests that
92 93 // print command lines and check expected output.
93 94 config.add_for_environment_variable(
94 95 "RHG_ON_UNSUPPORTED",
95 96 b"rhg",
96 97 b"on-unsupported",
97 98 );
98 99 config.add_for_environment_variable(
99 100 "RHG_FALLBACK_EXECUTABLE",
100 101 b"rhg",
101 102 b"fallback-executable",
102 103 );
103 104
104 105 // HGRCPATH replaces user config
105 106 if opt_rc_path.is_none() {
106 107 config.add_user_config()?
107 108 }
108 109 if let Some(rc_path) = &opt_rc_path {
109 110 for path in env::split_paths(rc_path) {
110 111 if !path.as_os_str().is_empty() {
111 112 if path.is_dir() {
112 113 config.add_trusted_dir(&path)?
113 114 } else {
114 115 config.add_trusted_file(&path)?
115 116 }
116 117 }
117 118 }
118 119 }
119 120 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
120 121 config.layers.push(layer)
121 122 }
122 123 Ok(config)
123 124 }
124 125
125 126 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
126 127 if let Some(entries) = std::fs::read_dir(path)
127 128 .when_reading_file(path)
128 129 .io_not_found_as_none()?
129 130 {
130 131 let mut file_paths = entries
131 132 .map(|result| {
132 133 result.when_reading_file(path).map(|entry| entry.path())
133 134 })
134 135 .collect::<Result<Vec<_>, _>>()?;
135 136 file_paths.sort();
136 137 for file_path in &file_paths {
137 138 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
138 139 self.add_trusted_file(&file_path)?
139 140 }
140 141 }
141 142 }
142 143 Ok(())
143 144 }
144 145
145 146 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
146 147 if let Some(data) = std::fs::read(path)
147 148 .when_reading_file(path)
148 149 .io_not_found_as_none()?
149 150 {
150 151 self.layers.extend(ConfigLayer::parse(path, &data)?)
151 152 }
152 153 Ok(())
153 154 }
154 155
155 156 fn add_for_environment_variable(
156 157 &mut self,
157 158 var: &str,
158 159 section: &[u8],
159 160 key: &[u8],
160 161 ) {
161 162 if let Some(value) = env::var_os(var) {
162 163 let origin = layer::ConfigOrigin::Environment(var.into());
163 164 let mut layer = ConfigLayer::new(origin);
164 165 layer.add(
165 166 section.to_owned(),
166 167 key.to_owned(),
167 168 get_bytes_from_os_str(value),
168 169 None,
169 170 );
170 171 self.layers.push(layer)
171 172 }
172 173 }
173 174
174 175 #[cfg(unix)] // TODO: other platforms
175 176 fn add_system_config(&mut self) -> Result<(), ConfigError> {
176 177 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
177 178 let etc = prefix.join("etc").join("mercurial");
178 179 self.add_trusted_file(&etc.join("hgrc"))?;
179 180 self.add_trusted_dir(&etc.join("hgrc.d"))
180 181 };
181 182 let root = Path::new("/");
182 183 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
183 184 // instead? TODO: can this be a relative path?
184 185 let hg = crate::utils::current_exe()?;
185 186 // TODO: this order (per-installation then per-system) matches
186 187 // `systemrcpath()` in `mercurial/scmposix.py`, but
187 188 // `mercurial/helptext/config.txt` suggests it should be reversed
188 189 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
189 190 if installation_prefix != root {
190 191 add_for_prefix(&installation_prefix)?
191 192 }
192 193 }
193 194 add_for_prefix(root)?;
194 195 Ok(())
195 196 }
196 197
197 198 #[cfg(unix)] // TODO: other plateforms
198 199 fn add_user_config(&mut self) -> Result<(), ConfigError> {
199 200 let opt_home = home::home_dir();
200 201 if let Some(home) = &opt_home {
201 202 self.add_trusted_file(&home.join(".hgrc"))?
202 203 }
203 204 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
204 205 if !darwin {
205 206 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
206 207 .map(PathBuf::from)
207 208 .or_else(|| opt_home.map(|home| home.join(".config")))
208 209 {
209 210 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
210 211 }
211 212 }
212 213 Ok(())
213 214 }
214 215
215 216 /// Loads in order, which means that the precedence is the same
216 217 /// as the order of `sources`.
217 218 pub fn load_from_explicit_sources(
218 219 sources: Vec<ConfigSource>,
219 220 ) -> Result<Self, ConfigError> {
220 221 let mut layers = vec![];
221 222
222 223 for source in sources.into_iter() {
223 224 match source {
224 225 ConfigSource::Parsed(c) => layers.push(c),
225 226 ConfigSource::AbsPath(c) => {
226 227 // TODO check if it should be trusted
227 228 // mercurial/ui.py:427
228 229 let data = match std::fs::read(&c) {
229 230 Err(_) => continue, // same as the python code
230 231 Ok(data) => data,
231 232 };
232 233 layers.extend(ConfigLayer::parse(&c, &data)?)
233 234 }
234 235 }
235 236 }
236 237
237 238 Ok(Config { layers })
238 239 }
239 240
240 241 /// Loads the per-repository config into a new `Config` which is combined
241 242 /// with `self`.
242 243 pub(crate) fn combine_with_repo(
243 244 &self,
244 245 repo_config_files: &[PathBuf],
245 246 ) -> Result<Self, ConfigError> {
246 247 let (cli_layers, other_layers) = self
247 248 .layers
248 249 .iter()
249 250 .cloned()
250 251 .partition(ConfigLayer::is_from_command_line);
251 252
252 253 let mut repo_config = Self {
253 254 layers: other_layers,
254 255 };
255 256 for path in repo_config_files {
256 257 // TODO: check if this file should be trusted:
257 258 // `mercurial/ui.py:427`
258 259 repo_config.add_trusted_file(path)?;
259 260 }
260 261 repo_config.layers.extend(cli_layers);
261 262 Ok(repo_config)
262 263 }
263 264
264 265 fn get_parse<'config, T: 'config>(
265 266 &'config self,
266 267 section: &[u8],
267 268 item: &[u8],
268 269 expected_type: &'static str,
269 270 parse: impl Fn(&'config [u8]) -> Option<T>,
270 271 ) -> Result<Option<T>, ConfigValueParseError> {
271 272 match self.get_inner(&section, &item) {
272 273 Some((layer, v)) => match parse(&v.bytes) {
273 274 Some(b) => Ok(Some(b)),
274 275 None => Err(ConfigValueParseError {
275 276 origin: layer.origin.to_owned(),
276 277 line: v.line,
277 278 value: v.bytes.to_owned(),
278 279 section: section.to_owned(),
279 280 item: item.to_owned(),
280 281 expected_type,
281 282 }),
282 283 },
283 284 None => Ok(None),
284 285 }
285 286 }
286 287
287 288 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
288 289 /// Otherwise, returns an `Ok(value)` if found, or `None`.
289 290 pub fn get_str(
290 291 &self,
291 292 section: &[u8],
292 293 item: &[u8],
293 294 ) -> Result<Option<&str>, ConfigValueParseError> {
294 295 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
295 296 str::from_utf8(value).ok()
296 297 })
297 298 }
298 299
299 300 /// Returns an `Err` if the first value found is not a valid unsigned
300 301 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
301 302 pub fn get_u32(
302 303 &self,
303 304 section: &[u8],
304 305 item: &[u8],
305 306 ) -> Result<Option<u32>, ConfigValueParseError> {
306 307 self.get_parse(section, item, "valid integer", |value| {
307 308 str::from_utf8(value).ok()?.parse().ok()
308 309 })
309 310 }
310 311
311 312 /// Returns an `Err` if the first value found is not a valid file size
312 313 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
313 314 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
314 315 pub fn get_byte_size(
315 316 &self,
316 317 section: &[u8],
317 318 item: &[u8],
318 319 ) -> Result<Option<u64>, ConfigValueParseError> {
319 320 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
320 321 }
321 322
322 323 /// Returns an `Err` if the first value found is not a valid boolean.
323 324 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
324 325 /// found, or `None`.
325 326 pub fn get_option(
326 327 &self,
327 328 section: &[u8],
328 329 item: &[u8],
329 330 ) -> Result<Option<bool>, ConfigValueParseError> {
330 331 self.get_parse(section, item, "boolean", values::parse_bool)
331 332 }
332 333
333 334 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
334 335 /// if the value is not found, an `Err` if it's not a valid boolean.
335 336 pub fn get_bool(
336 337 &self,
337 338 section: &[u8],
338 339 item: &[u8],
339 340 ) -> Result<bool, ConfigValueParseError> {
340 341 Ok(self.get_option(section, item)?.unwrap_or(false))
341 342 }
342 343
343 344 /// Returns the corresponding list-value in the config if found, or `None`.
344 345 ///
345 346 /// This is appropriate for new configuration keys. The value syntax is
346 347 /// **not** the same as most existing list-valued config, which has Python
347 348 /// parsing implemented in `parselist()` in `mercurial/config.py`.
348 349 /// Faithfully porting that parsing algorithm to Rust (including behavior
349 350 /// that are arguably bugs) turned out to be non-trivial and hasn’t been
350 351 /// completed as of this writing.
351 352 ///
352 353 /// Instead, the "simple" syntax is: split on comma, then trim leading and
353 354 /// trailing whitespace of each component. Quotes or backslashes are not
354 355 /// interpreted in any way. Commas are mandatory between values. Values
355 356 /// that contain a comma are not supported.
356 357 pub fn get_simple_list(
357 358 &self,
358 359 section: &[u8],
359 360 item: &[u8],
360 361 ) -> Option<impl Iterator<Item = &[u8]>> {
361 362 self.get(section, item).map(|value| {
362 363 value
363 364 .split(|&byte| byte == b',')
364 365 .map(|component| component.trim())
365 366 })
366 367 }
367 368
368 369 /// Returns the raw value bytes of the first one found, or `None`.
369 370 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
370 371 self.get_inner(section, item)
371 372 .map(|(_, value)| value.bytes.as_ref())
372 373 }
373 374
374 375 /// Returns the layer and the value of the first one found, or `None`.
375 376 fn get_inner(
376 377 &self,
377 378 section: &[u8],
378 379 item: &[u8],
379 380 ) -> Option<(&ConfigLayer, &ConfigValue)> {
380 381 for layer in self.layers.iter().rev() {
381 382 if !layer.trusted {
382 383 continue;
383 384 }
384 385 if let Some(v) = layer.get(&section, &item) {
385 386 return Some((&layer, v));
386 387 }
387 388 }
388 389 None
389 390 }
390 391
391 392 /// Return all keys defined for the given section
392 393 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
393 394 self.layers
394 395 .iter()
395 396 .flat_map(|layer| layer.iter_keys(section))
396 397 .collect()
397 398 }
398 399
399 400 /// Get raw values bytes from all layers (even untrusted ones) in order
400 401 /// of precedence.
401 402 #[cfg(test)]
402 403 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
403 404 let mut res = vec![];
404 405 for layer in self.layers.iter().rev() {
405 406 if let Some(v) = layer.get(&section, &item) {
406 407 res.push(v.bytes.as_ref());
407 408 }
408 409 }
409 410 res
410 411 }
411 412 }
412 413
413 414 #[cfg(test)]
414 415 mod tests {
415 416 use super::*;
416 417 use pretty_assertions::assert_eq;
417 418 use std::fs::File;
418 419 use std::io::Write;
419 420
420 421 #[test]
421 422 fn test_include_layer_ordering() {
422 423 let tmpdir = tempfile::tempdir().unwrap();
423 424 let tmpdir_path = tmpdir.path();
424 425 let mut included_file =
425 426 File::create(&tmpdir_path.join("included.rc")).unwrap();
426 427
427 428 included_file.write_all(b"[section]\nitem=value1").unwrap();
428 429 let base_config_path = tmpdir_path.join("base.rc");
429 430 let mut config_file = File::create(&base_config_path).unwrap();
430 431 let data =
431 432 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
432 433 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
433 434 config_file.write_all(data).unwrap();
434 435
435 436 let sources = vec![ConfigSource::AbsPath(base_config_path)];
436 437 let config = Config::load_from_explicit_sources(sources)
437 438 .expect("expected valid config");
438 439
439 440 let (_, value) = config.get_inner(b"section", b"item").unwrap();
440 441 assert_eq!(
441 442 value,
442 443 &ConfigValue {
443 444 bytes: b"value2".to_vec(),
444 445 line: Some(4)
445 446 }
446 447 );
447 448
448 449 let value = config.get(b"section", b"item").unwrap();
449 450 assert_eq!(value, b"value2",);
450 451 assert_eq!(
451 452 config.get_all(b"section", b"item"),
452 453 [b"value2", b"value1", b"value0"]
453 454 );
454 455
455 456 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
456 457 assert_eq!(
457 458 config.get_byte_size(b"section2", b"size").unwrap(),
458 459 Some(1024 + 512)
459 460 );
460 461 assert!(config.get_u32(b"section2", b"not-count").is_err());
461 462 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
462 463 }
463 464 }
@@ -1,265 +1,269 b''
1 1 use crate::config::{Config, ConfigError, ConfigParseError};
2 2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
3 3 use crate::requirements;
4 4 use crate::utils::files::get_path_from_bytes;
5 5 use crate::utils::SliceExt;
6 6 use memmap::{Mmap, MmapOptions};
7 7 use std::collections::HashSet;
8 8 use std::path::{Path, PathBuf};
9 9
10 10 /// A repository on disk
11 11 pub struct Repo {
12 12 working_directory: PathBuf,
13 13 dot_hg: PathBuf,
14 14 store: PathBuf,
15 15 requirements: HashSet<String>,
16 16 config: Config,
17 17 }
18 18
19 19 #[derive(Debug, derive_more::From)]
20 20 pub enum RepoError {
21 21 NotFound {
22 22 at: PathBuf,
23 23 },
24 24 #[from]
25 25 ConfigParseError(ConfigParseError),
26 26 #[from]
27 27 Other(HgError),
28 28 }
29 29
30 30 impl From<ConfigError> for RepoError {
31 31 fn from(error: ConfigError) -> Self {
32 32 match error {
33 33 ConfigError::Parse(error) => error.into(),
34 34 ConfigError::Other(error) => error.into(),
35 35 }
36 36 }
37 37 }
38 38
39 39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 40 #[derive(Clone, Copy)]
41 41 pub struct Vfs<'a> {
42 42 pub(crate) base: &'a Path,
43 43 }
44 44
45 45 impl Repo {
46 46 /// Find a repository, either at the given path (which must contain a `.hg`
47 47 /// sub-directory) or by searching the current directory and its
48 48 /// ancestors.
49 49 ///
50 50 /// A method with two very different "modes" like this usually a code smell
51 51 /// to make two methods instead, but in this case an `Option` is what rhg
52 52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 53 /// Having two methods would just move that `if` to almost all callers.
54 54 pub fn find(
55 55 config: &Config,
56 56 explicit_path: Option<&Path>,
57 57 ) -> Result<Self, RepoError> {
58 58 if let Some(root) = explicit_path {
59 59 if root.join(".hg").is_dir() {
60 60 Self::new_at_path(root.to_owned(), config)
61 61 } else if root.is_file() {
62 62 Err(HgError::unsupported("bundle repository").into())
63 63 } else {
64 64 Err(RepoError::NotFound {
65 65 at: root.to_owned(),
66 66 })
67 67 }
68 68 } else {
69 69 let current_directory = crate::utils::current_dir()?;
70 70 // ancestors() is inclusive: it first yields `current_directory`
71 71 // as-is.
72 72 for ancestor in current_directory.ancestors() {
73 73 if ancestor.join(".hg").is_dir() {
74 74 return Self::new_at_path(ancestor.to_owned(), config);
75 75 }
76 76 }
77 77 Err(RepoError::NotFound {
78 78 at: current_directory,
79 79 })
80 80 }
81 81 }
82 82
83 83 /// To be called after checking that `.hg` is a sub-directory
84 84 fn new_at_path(
85 85 working_directory: PathBuf,
86 86 config: &Config,
87 87 ) -> Result<Self, RepoError> {
88 88 let dot_hg = working_directory.join(".hg");
89 89
90 90 let mut repo_config_files = Vec::new();
91 91 repo_config_files.push(dot_hg.join("hgrc"));
92 92 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
93 93
94 94 let hg_vfs = Vfs { base: &dot_hg };
95 95 let mut reqs = requirements::load_if_exists(hg_vfs)?;
96 96 let relative =
97 97 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
98 98 let shared =
99 99 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
100 100
101 101 // From `mercurial/localrepo.py`:
102 102 //
103 103 // if .hg/requires contains the sharesafe requirement, it means
104 104 // there exists a `.hg/store/requires` too and we should read it
105 105 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
106 106 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
107 107 // is not present, refer checkrequirementscompat() for that
108 108 //
109 109 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
110 110 // repository was shared the old way. We check the share source
111 111 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
112 112 // current repository needs to be reshared
113 113 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
114 114
115 115 let store_path;
116 116 if !shared {
117 117 store_path = dot_hg.join("store");
118 118 } else {
119 119 let bytes = hg_vfs.read("sharedpath")?;
120 120 let mut shared_path =
121 121 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
122 122 if relative {
123 123 shared_path = dot_hg.join(shared_path)
124 124 }
125 125 if !shared_path.is_dir() {
126 126 return Err(HgError::corrupted(format!(
127 127 ".hg/sharedpath points to nonexistent directory {}",
128 128 shared_path.display()
129 129 ))
130 130 .into());
131 131 }
132 132
133 133 store_path = shared_path.join("store");
134 134
135 135 let source_is_share_safe =
136 136 requirements::load(Vfs { base: &shared_path })?
137 137 .contains(requirements::SHARESAFE_REQUIREMENT);
138 138
139 139 if share_safe && !source_is_share_safe {
140 140 return Err(match config
141 141 .get(b"share", b"safe-mismatch.source-not-safe")
142 142 {
143 143 Some(b"abort") | None => HgError::abort(
144 144 "abort: share source does not support share-safe requirement\n\
145 145 (see `hg help config.format.use-share-safe` for more information)",
146 146 ),
147 147 _ => HgError::unsupported("share-safe downgrade"),
148 148 }
149 149 .into());
150 150 } else if source_is_share_safe && !share_safe {
151 151 return Err(
152 152 match config.get(b"share", b"safe-mismatch.source-safe") {
153 153 Some(b"abort") | None => HgError::abort(
154 154 "abort: version mismatch: source uses share-safe \
155 155 functionality while the current share does not\n\
156 156 (see `hg help config.format.use-share-safe` for more information)",
157 157 ),
158 158 _ => HgError::unsupported("share-safe upgrade"),
159 159 }
160 160 .into(),
161 161 );
162 162 }
163 163
164 164 if share_safe {
165 165 repo_config_files.insert(0, shared_path.join("hgrc"))
166 166 }
167 167 }
168 168 if share_safe {
169 169 reqs.extend(requirements::load(Vfs { base: &store_path })?);
170 170 }
171 171
172 let repo_config = config.combine_with_repo(&repo_config_files)?;
172 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
173 config.combine_with_repo(&repo_config_files)?
174 } else {
175 config.clone()
176 };
173 177
174 178 let repo = Self {
175 179 requirements: reqs,
176 180 working_directory,
177 181 store: store_path,
178 182 dot_hg,
179 183 config: repo_config,
180 184 };
181 185
182 186 requirements::check(&repo)?;
183 187
184 188 Ok(repo)
185 189 }
186 190
187 191 pub fn working_directory_path(&self) -> &Path {
188 192 &self.working_directory
189 193 }
190 194
191 195 pub fn requirements(&self) -> &HashSet<String> {
192 196 &self.requirements
193 197 }
194 198
195 199 pub fn config(&self) -> &Config {
196 200 &self.config
197 201 }
198 202
199 203 /// For accessing repository files (in `.hg`), except for the store
200 204 /// (`.hg/store`).
201 205 pub fn hg_vfs(&self) -> Vfs<'_> {
202 206 Vfs { base: &self.dot_hg }
203 207 }
204 208
205 209 /// For accessing repository store files (in `.hg/store`)
206 210 pub fn store_vfs(&self) -> Vfs<'_> {
207 211 Vfs { base: &self.store }
208 212 }
209 213
210 214 /// For accessing the working copy
211 215
212 216 // The undescore prefix silences the "never used" warning. Remove before
213 217 // using.
214 218 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
215 219 Vfs {
216 220 base: &self.working_directory,
217 221 }
218 222 }
219 223
220 224 pub fn dirstate_parents(
221 225 &self,
222 226 ) -> Result<crate::dirstate::DirstateParents, HgError> {
223 227 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
224 228 let parents =
225 229 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
226 230 Ok(parents.clone())
227 231 }
228 232 }
229 233
230 234 impl Vfs<'_> {
231 235 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
232 236 self.base.join(relative_path)
233 237 }
234 238
235 239 pub fn read(
236 240 &self,
237 241 relative_path: impl AsRef<Path>,
238 242 ) -> Result<Vec<u8>, HgError> {
239 243 let path = self.join(relative_path);
240 244 std::fs::read(&path).when_reading_file(&path)
241 245 }
242 246
243 247 pub fn mmap_open(
244 248 &self,
245 249 relative_path: impl AsRef<Path>,
246 250 ) -> Result<Mmap, HgError> {
247 251 let path = self.base.join(relative_path);
248 252 let file = std::fs::File::open(&path).when_reading_file(&path)?;
249 253 // TODO: what are the safety requirements here?
250 254 let mmap = unsafe { MmapOptions::new().map(&file) }
251 255 .when_reading_file(&path)?;
252 256 Ok(mmap)
253 257 }
254 258
255 259 pub fn rename(
256 260 &self,
257 261 relative_from: impl AsRef<Path>,
258 262 relative_to: impl AsRef<Path>,
259 263 ) -> Result<(), HgError> {
260 264 let from = self.join(relative_from);
261 265 let to = self.join(relative_to);
262 266 std::fs::rename(&from, &to)
263 267 .with_context(|| IoErrorContext::RenamingFile { from, to })
264 268 }
265 269 }
General Comments 0
You need to be logged in to leave comments. Login now