##// END OF EJS Templates
rhg: Propagate permission errors when finding a repository...
Simon Sapin -
r48584:cf5f8da2 stable
parent child Browse files
Show More
@@ -1,194 +1,199 b''
1 use crate::config::ConfigValueParseError;
1 use crate::config::ConfigValueParseError;
2 use crate::exit_codes;
2 use crate::exit_codes;
3 use std::fmt;
3 use std::fmt;
4
4
5 /// Common error cases that can happen in many different APIs
5 /// Common error cases that can happen in many different APIs
6 #[derive(Debug, derive_more::From)]
6 #[derive(Debug, derive_more::From)]
7 pub enum HgError {
7 pub enum HgError {
8 IoError {
8 IoError {
9 error: std::io::Error,
9 error: std::io::Error,
10 context: IoErrorContext,
10 context: IoErrorContext,
11 },
11 },
12
12
13 /// A file under `.hg/` normally only written by Mercurial is not in the
13 /// A file under `.hg/` normally only written by Mercurial is not in the
14 /// expected format. This indicates a bug in Mercurial, filesystem
14 /// expected format. This indicates a bug in Mercurial, filesystem
15 /// corruption, or hardware failure.
15 /// corruption, or hardware failure.
16 ///
16 ///
17 /// The given string is a short explanation for users, not intended to be
17 /// The given string is a short explanation for users, not intended to be
18 /// machine-readable.
18 /// machine-readable.
19 CorruptedRepository(String),
19 CorruptedRepository(String),
20
20
21 /// The respository or requested operation involves a feature not
21 /// The respository or requested operation involves a feature not
22 /// supported by the Rust implementation. Falling back to the Python
22 /// supported by the Rust implementation. Falling back to the Python
23 /// implementation may or may not work.
23 /// implementation may or may not work.
24 ///
24 ///
25 /// The given string is a short explanation for users, not intended to be
25 /// The given string is a short explanation for users, not intended to be
26 /// machine-readable.
26 /// machine-readable.
27 UnsupportedFeature(String),
27 UnsupportedFeature(String),
28
28
29 /// Operation cannot proceed for some other reason.
29 /// Operation cannot proceed for some other reason.
30 ///
30 ///
31 /// The message is a short explanation for users, not intended to be
31 /// The message is a short explanation for users, not intended to be
32 /// machine-readable.
32 /// machine-readable.
33 Abort {
33 Abort {
34 message: String,
34 message: String,
35 detailed_exit_code: exit_codes::ExitCode,
35 detailed_exit_code: exit_codes::ExitCode,
36 },
36 },
37
37
38 /// A configuration value is not in the expected syntax.
38 /// A configuration value is not in the expected syntax.
39 ///
39 ///
40 /// These errors can happen in many places in the code because values are
40 /// These errors can happen in many places in the code because values are
41 /// parsed lazily as the file-level parser does not know the expected type
41 /// parsed lazily as the file-level parser does not know the expected type
42 /// and syntax of each value.
42 /// and syntax of each value.
43 #[from]
43 #[from]
44 ConfigValueParseError(ConfigValueParseError),
44 ConfigValueParseError(ConfigValueParseError),
45 }
45 }
46
46
47 /// Details about where an I/O error happened
47 /// Details about where an I/O error happened
48 #[derive(Debug)]
48 #[derive(Debug)]
49 pub enum IoErrorContext {
49 pub enum IoErrorContext {
50 /// `std::fs::metadata`
51 ReadingMetadata(std::path::PathBuf),
50 ReadingFile(std::path::PathBuf),
52 ReadingFile(std::path::PathBuf),
51 WritingFile(std::path::PathBuf),
53 WritingFile(std::path::PathBuf),
52 RemovingFile(std::path::PathBuf),
54 RemovingFile(std::path::PathBuf),
53 RenamingFile {
55 RenamingFile {
54 from: std::path::PathBuf,
56 from: std::path::PathBuf,
55 to: std::path::PathBuf,
57 to: std::path::PathBuf,
56 },
58 },
57 /// `std::fs::canonicalize`
59 /// `std::fs::canonicalize`
58 CanonicalizingPath(std::path::PathBuf),
60 CanonicalizingPath(std::path::PathBuf),
59 /// `std::env::current_dir`
61 /// `std::env::current_dir`
60 CurrentDir,
62 CurrentDir,
61 /// `std::env::current_exe`
63 /// `std::env::current_exe`
62 CurrentExe,
64 CurrentExe,
63 }
65 }
64
66
65 impl HgError {
67 impl HgError {
66 pub fn corrupted(explanation: impl Into<String>) -> Self {
68 pub fn corrupted(explanation: impl Into<String>) -> Self {
67 // TODO: capture a backtrace here and keep it in the error value
69 // TODO: capture a backtrace here and keep it in the error value
68 // to aid debugging?
70 // to aid debugging?
69 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
71 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
70 HgError::CorruptedRepository(explanation.into())
72 HgError::CorruptedRepository(explanation.into())
71 }
73 }
72
74
73 pub fn unsupported(explanation: impl Into<String>) -> Self {
75 pub fn unsupported(explanation: impl Into<String>) -> Self {
74 HgError::UnsupportedFeature(explanation.into())
76 HgError::UnsupportedFeature(explanation.into())
75 }
77 }
76
78
77 pub fn abort(
79 pub fn abort(
78 explanation: impl Into<String>,
80 explanation: impl Into<String>,
79 exit_code: exit_codes::ExitCode,
81 exit_code: exit_codes::ExitCode,
80 ) -> Self {
82 ) -> Self {
81 HgError::Abort {
83 HgError::Abort {
82 message: explanation.into(),
84 message: explanation.into(),
83 detailed_exit_code: exit_code,
85 detailed_exit_code: exit_code,
84 }
86 }
85 }
87 }
86 }
88 }
87
89
88 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
90 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
89 impl fmt::Display for HgError {
91 impl fmt::Display for HgError {
90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 match self {
93 match self {
92 HgError::Abort { message, .. } => write!(f, "{}", message),
94 HgError::Abort { message, .. } => write!(f, "{}", message),
93 HgError::IoError { error, context } => {
95 HgError::IoError { error, context } => {
94 write!(f, "abort: {}: {}", context, error)
96 write!(f, "abort: {}: {}", context, error)
95 }
97 }
96 HgError::CorruptedRepository(explanation) => {
98 HgError::CorruptedRepository(explanation) => {
97 write!(f, "abort: {}", explanation)
99 write!(f, "abort: {}", explanation)
98 }
100 }
99 HgError::UnsupportedFeature(explanation) => {
101 HgError::UnsupportedFeature(explanation) => {
100 write!(f, "unsupported feature: {}", explanation)
102 write!(f, "unsupported feature: {}", explanation)
101 }
103 }
102 HgError::ConfigValueParseError(error) => error.fmt(f),
104 HgError::ConfigValueParseError(error) => error.fmt(f),
103 }
105 }
104 }
106 }
105 }
107 }
106
108
107 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
109 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
108 impl fmt::Display for IoErrorContext {
110 impl fmt::Display for IoErrorContext {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 match self {
112 match self {
113 IoErrorContext::ReadingMetadata(path) => {
114 write!(f, "when reading metadata of {}", path.display())
115 }
111 IoErrorContext::ReadingFile(path) => {
116 IoErrorContext::ReadingFile(path) => {
112 write!(f, "when reading {}", path.display())
117 write!(f, "when reading {}", path.display())
113 }
118 }
114 IoErrorContext::WritingFile(path) => {
119 IoErrorContext::WritingFile(path) => {
115 write!(f, "when writing {}", path.display())
120 write!(f, "when writing {}", path.display())
116 }
121 }
117 IoErrorContext::RemovingFile(path) => {
122 IoErrorContext::RemovingFile(path) => {
118 write!(f, "when removing {}", path.display())
123 write!(f, "when removing {}", path.display())
119 }
124 }
120 IoErrorContext::RenamingFile { from, to } => write!(
125 IoErrorContext::RenamingFile { from, to } => write!(
121 f,
126 f,
122 "when renaming {} to {}",
127 "when renaming {} to {}",
123 from.display(),
128 from.display(),
124 to.display()
129 to.display()
125 ),
130 ),
126 IoErrorContext::CanonicalizingPath(path) => {
131 IoErrorContext::CanonicalizingPath(path) => {
127 write!(f, "when canonicalizing {}", path.display())
132 write!(f, "when canonicalizing {}", path.display())
128 }
133 }
129 IoErrorContext::CurrentDir => {
134 IoErrorContext::CurrentDir => {
130 write!(f, "error getting current working directory")
135 write!(f, "error getting current working directory")
131 }
136 }
132 IoErrorContext::CurrentExe => {
137 IoErrorContext::CurrentExe => {
133 write!(f, "error getting current executable")
138 write!(f, "error getting current executable")
134 }
139 }
135 }
140 }
136 }
141 }
137 }
142 }
138
143
139 pub trait IoResultExt<T> {
144 pub trait IoResultExt<T> {
140 /// Annotate a possible I/O error as related to a reading a file at the
145 /// Annotate a possible I/O error as related to a reading a file at the
141 /// given path.
146 /// given path.
142 ///
147 ///
143 /// This allows printing something like β€œFile not found when reading
148 /// This allows printing something like β€œFile not found when reading
144 /// example.txt” instead of just β€œFile not found”.
149 /// example.txt” instead of just β€œFile not found”.
145 ///
150 ///
146 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
151 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
147 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
152 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
148
153
149 fn with_context(
154 fn with_context(
150 self,
155 self,
151 context: impl FnOnce() -> IoErrorContext,
156 context: impl FnOnce() -> IoErrorContext,
152 ) -> Result<T, HgError>;
157 ) -> Result<T, HgError>;
153 }
158 }
154
159
155 impl<T> IoResultExt<T> for std::io::Result<T> {
160 impl<T> IoResultExt<T> for std::io::Result<T> {
156 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
161 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
157 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
162 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
158 }
163 }
159
164
160 fn with_context(
165 fn with_context(
161 self,
166 self,
162 context: impl FnOnce() -> IoErrorContext,
167 context: impl FnOnce() -> IoErrorContext,
163 ) -> Result<T, HgError> {
168 ) -> Result<T, HgError> {
164 self.map_err(|error| HgError::IoError {
169 self.map_err(|error| HgError::IoError {
165 error,
170 error,
166 context: context(),
171 context: context(),
167 })
172 })
168 }
173 }
169 }
174 }
170
175
171 pub trait HgResultExt<T> {
176 pub trait HgResultExt<T> {
172 /// Handle missing files separately from other I/O error cases.
177 /// Handle missing files separately from other I/O error cases.
173 ///
178 ///
174 /// Wraps the `Ok` type in an `Option`:
179 /// Wraps the `Ok` type in an `Option`:
175 ///
180 ///
176 /// * `Ok(x)` becomes `Ok(Some(x))`
181 /// * `Ok(x)` becomes `Ok(Some(x))`
177 /// * An I/O "not found" error becomes `Ok(None)`
182 /// * An I/O "not found" error becomes `Ok(None)`
178 /// * Other errors are unchanged
183 /// * Other errors are unchanged
179 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
184 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
180 }
185 }
181
186
182 impl<T> HgResultExt<T> for Result<T, HgError> {
187 impl<T> HgResultExt<T> for Result<T, HgError> {
183 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
188 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
184 match self {
189 match self {
185 Ok(x) => Ok(Some(x)),
190 Ok(x) => Ok(Some(x)),
186 Err(HgError::IoError { error, .. })
191 Err(HgError::IoError { error, .. })
187 if error.kind() == std::io::ErrorKind::NotFound =>
192 if error.kind() == std::io::ErrorKind::NotFound =>
188 {
193 {
189 Ok(None)
194 Ok(None)
190 }
195 }
191 Err(other_error) => Err(other_error),
196 Err(other_error) => Err(other_error),
192 }
197 }
193 }
198 }
194 }
199 }
@@ -1,288 +1,315 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::exit_codes;
3 use crate::exit_codes;
4 use crate::requirements;
4 use crate::requirements;
5 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::files::get_path_from_bytes;
6 use crate::utils::SliceExt;
6 use crate::utils::SliceExt;
7 use memmap::{Mmap, MmapOptions};
7 use memmap::{Mmap, MmapOptions};
8 use std::collections::HashSet;
8 use std::collections::HashSet;
9 use std::io::ErrorKind;
9 use std::path::{Path, PathBuf};
10 use std::path::{Path, PathBuf};
10
11
11 /// A repository on disk
12 /// A repository on disk
12 pub struct Repo {
13 pub struct Repo {
13 working_directory: PathBuf,
14 working_directory: PathBuf,
14 dot_hg: PathBuf,
15 dot_hg: PathBuf,
15 store: PathBuf,
16 store: PathBuf,
16 requirements: HashSet<String>,
17 requirements: HashSet<String>,
17 config: Config,
18 config: Config,
18 }
19 }
19
20
20 #[derive(Debug, derive_more::From)]
21 #[derive(Debug, derive_more::From)]
21 pub enum RepoError {
22 pub enum RepoError {
22 NotFound {
23 NotFound {
23 at: PathBuf,
24 at: PathBuf,
24 },
25 },
25 #[from]
26 #[from]
26 ConfigParseError(ConfigParseError),
27 ConfigParseError(ConfigParseError),
27 #[from]
28 #[from]
28 Other(HgError),
29 Other(HgError),
29 }
30 }
30
31
31 impl From<ConfigError> for RepoError {
32 impl From<ConfigError> for RepoError {
32 fn from(error: ConfigError) -> Self {
33 fn from(error: ConfigError) -> Self {
33 match error {
34 match error {
34 ConfigError::Parse(error) => error.into(),
35 ConfigError::Parse(error) => error.into(),
35 ConfigError::Other(error) => error.into(),
36 ConfigError::Other(error) => error.into(),
36 }
37 }
37 }
38 }
38 }
39 }
39
40
40 /// Filesystem access abstraction for the contents of a given "base" diretory
41 /// Filesystem access abstraction for the contents of a given "base" diretory
41 #[derive(Clone, Copy)]
42 #[derive(Clone, Copy)]
42 pub struct Vfs<'a> {
43 pub struct Vfs<'a> {
43 pub(crate) base: &'a Path,
44 pub(crate) base: &'a Path,
44 }
45 }
45
46
46 impl Repo {
47 impl Repo {
47 /// tries to find nearest repository root in current working directory or
48 /// tries to find nearest repository root in current working directory or
48 /// its ancestors
49 /// its ancestors
49 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
50 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
50 let current_directory = crate::utils::current_dir()?;
51 let current_directory = crate::utils::current_dir()?;
51 // ancestors() is inclusive: it first yields `current_directory`
52 // ancestors() is inclusive: it first yields `current_directory`
52 // as-is.
53 // as-is.
53 for ancestor in current_directory.ancestors() {
54 for ancestor in current_directory.ancestors() {
54 if ancestor.join(".hg").is_dir() {
55 if is_dir(ancestor.join(".hg"))? {
55 return Ok(ancestor.to_path_buf());
56 return Ok(ancestor.to_path_buf());
56 }
57 }
57 }
58 }
58 return Err(RepoError::NotFound {
59 return Err(RepoError::NotFound {
59 at: current_directory,
60 at: current_directory,
60 });
61 });
61 }
62 }
62
63
63 /// Find a repository, either at the given path (which must contain a `.hg`
64 /// Find a repository, either at the given path (which must contain a `.hg`
64 /// sub-directory) or by searching the current directory and its
65 /// sub-directory) or by searching the current directory and its
65 /// ancestors.
66 /// ancestors.
66 ///
67 ///
67 /// A method with two very different "modes" like this usually a code smell
68 /// A method with two very different "modes" like this usually a code smell
68 /// to make two methods instead, but in this case an `Option` is what rhg
69 /// to make two methods instead, but in this case an `Option` is what rhg
69 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
70 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
70 /// Having two methods would just move that `if` to almost all callers.
71 /// Having two methods would just move that `if` to almost all callers.
71 pub fn find(
72 pub fn find(
72 config: &Config,
73 config: &Config,
73 explicit_path: Option<PathBuf>,
74 explicit_path: Option<PathBuf>,
74 ) -> Result<Self, RepoError> {
75 ) -> Result<Self, RepoError> {
75 if let Some(root) = explicit_path {
76 if let Some(root) = explicit_path {
76 if root.join(".hg").is_dir() {
77 if is_dir(root.join(".hg"))? {
77 Self::new_at_path(root.to_owned(), config)
78 Self::new_at_path(root.to_owned(), config)
78 } else if root.is_file() {
79 } else if is_file(&root)? {
79 Err(HgError::unsupported("bundle repository").into())
80 Err(HgError::unsupported("bundle repository").into())
80 } else {
81 } else {
81 Err(RepoError::NotFound {
82 Err(RepoError::NotFound {
82 at: root.to_owned(),
83 at: root.to_owned(),
83 })
84 })
84 }
85 }
85 } else {
86 } else {
86 let root = Self::find_repo_root()?;
87 let root = Self::find_repo_root()?;
87 Self::new_at_path(root, config)
88 Self::new_at_path(root, config)
88 }
89 }
89 }
90 }
90
91
91 /// To be called after checking that `.hg` is a sub-directory
92 /// To be called after checking that `.hg` is a sub-directory
92 fn new_at_path(
93 fn new_at_path(
93 working_directory: PathBuf,
94 working_directory: PathBuf,
94 config: &Config,
95 config: &Config,
95 ) -> Result<Self, RepoError> {
96 ) -> Result<Self, RepoError> {
96 let dot_hg = working_directory.join(".hg");
97 let dot_hg = working_directory.join(".hg");
97
98
98 let mut repo_config_files = Vec::new();
99 let mut repo_config_files = Vec::new();
99 repo_config_files.push(dot_hg.join("hgrc"));
100 repo_config_files.push(dot_hg.join("hgrc"));
100 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
101 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
101
102
102 let hg_vfs = Vfs { base: &dot_hg };
103 let hg_vfs = Vfs { base: &dot_hg };
103 let mut reqs = requirements::load_if_exists(hg_vfs)?;
104 let mut reqs = requirements::load_if_exists(hg_vfs)?;
104 let relative =
105 let relative =
105 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
106 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
106 let shared =
107 let shared =
107 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
108 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
108
109
109 // From `mercurial/localrepo.py`:
110 // From `mercurial/localrepo.py`:
110 //
111 //
111 // if .hg/requires contains the sharesafe requirement, it means
112 // if .hg/requires contains the sharesafe requirement, it means
112 // there exists a `.hg/store/requires` too and we should read it
113 // there exists a `.hg/store/requires` too and we should read it
113 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
114 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
114 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
115 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
115 // is not present, refer checkrequirementscompat() for that
116 // is not present, refer checkrequirementscompat() for that
116 //
117 //
117 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
118 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
118 // repository was shared the old way. We check the share source
119 // repository was shared the old way. We check the share source
119 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
120 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
120 // current repository needs to be reshared
121 // current repository needs to be reshared
121 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
122 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
122
123
123 let store_path;
124 let store_path;
124 if !shared {
125 if !shared {
125 store_path = dot_hg.join("store");
126 store_path = dot_hg.join("store");
126 } else {
127 } else {
127 let bytes = hg_vfs.read("sharedpath")?;
128 let bytes = hg_vfs.read("sharedpath")?;
128 let mut shared_path =
129 let mut shared_path =
129 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
130 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
130 if relative {
131 if relative {
131 shared_path = dot_hg.join(shared_path)
132 shared_path = dot_hg.join(shared_path)
132 }
133 }
133 if !shared_path.is_dir() {
134 if !is_dir(&shared_path)? {
134 return Err(HgError::corrupted(format!(
135 return Err(HgError::corrupted(format!(
135 ".hg/sharedpath points to nonexistent directory {}",
136 ".hg/sharedpath points to nonexistent directory {}",
136 shared_path.display()
137 shared_path.display()
137 ))
138 ))
138 .into());
139 .into());
139 }
140 }
140
141
141 store_path = shared_path.join("store");
142 store_path = shared_path.join("store");
142
143
143 let source_is_share_safe =
144 let source_is_share_safe =
144 requirements::load(Vfs { base: &shared_path })?
145 requirements::load(Vfs { base: &shared_path })?
145 .contains(requirements::SHARESAFE_REQUIREMENT);
146 .contains(requirements::SHARESAFE_REQUIREMENT);
146
147
147 if share_safe && !source_is_share_safe {
148 if share_safe && !source_is_share_safe {
148 return Err(match config
149 return Err(match config
149 .get(b"share", b"safe-mismatch.source-not-safe")
150 .get(b"share", b"safe-mismatch.source-not-safe")
150 {
151 {
151 Some(b"abort") | None => HgError::abort(
152 Some(b"abort") | None => HgError::abort(
152 "abort: share source does not support share-safe requirement\n\
153 "abort: share source does not support share-safe requirement\n\
153 (see `hg help config.format.use-share-safe` for more information)",
154 (see `hg help config.format.use-share-safe` for more information)",
154 exit_codes::ABORT,
155 exit_codes::ABORT,
155 ),
156 ),
156 _ => HgError::unsupported("share-safe downgrade"),
157 _ => HgError::unsupported("share-safe downgrade"),
157 }
158 }
158 .into());
159 .into());
159 } else if source_is_share_safe && !share_safe {
160 } else if source_is_share_safe && !share_safe {
160 return Err(
161 return Err(
161 match config.get(b"share", b"safe-mismatch.source-safe") {
162 match config.get(b"share", b"safe-mismatch.source-safe") {
162 Some(b"abort") | None => HgError::abort(
163 Some(b"abort") | None => HgError::abort(
163 "abort: version mismatch: source uses share-safe \
164 "abort: version mismatch: source uses share-safe \
164 functionality while the current share does not\n\
165 functionality while the current share does not\n\
165 (see `hg help config.format.use-share-safe` for more information)",
166 (see `hg help config.format.use-share-safe` for more information)",
166 exit_codes::ABORT,
167 exit_codes::ABORT,
167 ),
168 ),
168 _ => HgError::unsupported("share-safe upgrade"),
169 _ => HgError::unsupported("share-safe upgrade"),
169 }
170 }
170 .into(),
171 .into(),
171 );
172 );
172 }
173 }
173
174
174 if share_safe {
175 if share_safe {
175 repo_config_files.insert(0, shared_path.join("hgrc"))
176 repo_config_files.insert(0, shared_path.join("hgrc"))
176 }
177 }
177 }
178 }
178 if share_safe {
179 if share_safe {
179 reqs.extend(requirements::load(Vfs { base: &store_path })?);
180 reqs.extend(requirements::load(Vfs { base: &store_path })?);
180 }
181 }
181
182
182 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
183 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
183 config.combine_with_repo(&repo_config_files)?
184 config.combine_with_repo(&repo_config_files)?
184 } else {
185 } else {
185 config.clone()
186 config.clone()
186 };
187 };
187
188
188 let repo = Self {
189 let repo = Self {
189 requirements: reqs,
190 requirements: reqs,
190 working_directory,
191 working_directory,
191 store: store_path,
192 store: store_path,
192 dot_hg,
193 dot_hg,
193 config: repo_config,
194 config: repo_config,
194 };
195 };
195
196
196 requirements::check(&repo)?;
197 requirements::check(&repo)?;
197
198
198 Ok(repo)
199 Ok(repo)
199 }
200 }
200
201
201 pub fn working_directory_path(&self) -> &Path {
202 pub fn working_directory_path(&self) -> &Path {
202 &self.working_directory
203 &self.working_directory
203 }
204 }
204
205
205 pub fn requirements(&self) -> &HashSet<String> {
206 pub fn requirements(&self) -> &HashSet<String> {
206 &self.requirements
207 &self.requirements
207 }
208 }
208
209
209 pub fn config(&self) -> &Config {
210 pub fn config(&self) -> &Config {
210 &self.config
211 &self.config
211 }
212 }
212
213
213 /// For accessing repository files (in `.hg`), except for the store
214 /// For accessing repository files (in `.hg`), except for the store
214 /// (`.hg/store`).
215 /// (`.hg/store`).
215 pub fn hg_vfs(&self) -> Vfs<'_> {
216 pub fn hg_vfs(&self) -> Vfs<'_> {
216 Vfs { base: &self.dot_hg }
217 Vfs { base: &self.dot_hg }
217 }
218 }
218
219
219 /// For accessing repository store files (in `.hg/store`)
220 /// For accessing repository store files (in `.hg/store`)
220 pub fn store_vfs(&self) -> Vfs<'_> {
221 pub fn store_vfs(&self) -> Vfs<'_> {
221 Vfs { base: &self.store }
222 Vfs { base: &self.store }
222 }
223 }
223
224
224 /// For accessing the working copy
225 /// For accessing the working copy
225 pub fn working_directory_vfs(&self) -> Vfs<'_> {
226 pub fn working_directory_vfs(&self) -> Vfs<'_> {
226 Vfs {
227 Vfs {
227 base: &self.working_directory,
228 base: &self.working_directory,
228 }
229 }
229 }
230 }
230
231
231 pub fn has_dirstate_v2(&self) -> bool {
232 pub fn has_dirstate_v2(&self) -> bool {
232 self.requirements
233 self.requirements
233 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
234 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
234 }
235 }
235
236
236 pub fn dirstate_parents(
237 pub fn dirstate_parents(
237 &self,
238 &self,
238 ) -> Result<crate::dirstate::DirstateParents, HgError> {
239 ) -> Result<crate::dirstate::DirstateParents, HgError> {
239 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
240 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
240 if dirstate.is_empty() {
241 if dirstate.is_empty() {
241 return Ok(crate::dirstate::DirstateParents::NULL);
242 return Ok(crate::dirstate::DirstateParents::NULL);
242 }
243 }
243 let parents = if self.has_dirstate_v2() {
244 let parents = if self.has_dirstate_v2() {
244 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
245 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
245 } else {
246 } else {
246 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
247 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
247 .clone()
248 .clone()
248 };
249 };
249 Ok(parents)
250 Ok(parents)
250 }
251 }
251 }
252 }
252
253
253 impl Vfs<'_> {
254 impl Vfs<'_> {
254 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
255 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
255 self.base.join(relative_path)
256 self.base.join(relative_path)
256 }
257 }
257
258
258 pub fn read(
259 pub fn read(
259 &self,
260 &self,
260 relative_path: impl AsRef<Path>,
261 relative_path: impl AsRef<Path>,
261 ) -> Result<Vec<u8>, HgError> {
262 ) -> Result<Vec<u8>, HgError> {
262 let path = self.join(relative_path);
263 let path = self.join(relative_path);
263 std::fs::read(&path).when_reading_file(&path)
264 std::fs::read(&path).when_reading_file(&path)
264 }
265 }
265
266
266 pub fn mmap_open(
267 pub fn mmap_open(
267 &self,
268 &self,
268 relative_path: impl AsRef<Path>,
269 relative_path: impl AsRef<Path>,
269 ) -> Result<Mmap, HgError> {
270 ) -> Result<Mmap, HgError> {
270 let path = self.base.join(relative_path);
271 let path = self.base.join(relative_path);
271 let file = std::fs::File::open(&path).when_reading_file(&path)?;
272 let file = std::fs::File::open(&path).when_reading_file(&path)?;
272 // TODO: what are the safety requirements here?
273 // TODO: what are the safety requirements here?
273 let mmap = unsafe { MmapOptions::new().map(&file) }
274 let mmap = unsafe { MmapOptions::new().map(&file) }
274 .when_reading_file(&path)?;
275 .when_reading_file(&path)?;
275 Ok(mmap)
276 Ok(mmap)
276 }
277 }
277
278
278 pub fn rename(
279 pub fn rename(
279 &self,
280 &self,
280 relative_from: impl AsRef<Path>,
281 relative_from: impl AsRef<Path>,
281 relative_to: impl AsRef<Path>,
282 relative_to: impl AsRef<Path>,
282 ) -> Result<(), HgError> {
283 ) -> Result<(), HgError> {
283 let from = self.join(relative_from);
284 let from = self.join(relative_from);
284 let to = self.join(relative_to);
285 let to = self.join(relative_to);
285 std::fs::rename(&from, &to)
286 std::fs::rename(&from, &to)
286 .with_context(|| IoErrorContext::RenamingFile { from, to })
287 .with_context(|| IoErrorContext::RenamingFile { from, to })
287 }
288 }
288 }
289 }
290
291 fn fs_metadata(
292 path: impl AsRef<Path>,
293 ) -> Result<Option<std::fs::Metadata>, HgError> {
294 let path = path.as_ref();
295 match std::fs::metadata(path) {
296 Ok(meta) => Ok(Some(meta)),
297 Err(error) => match error.kind() {
298 // TODO: when we require a Rust version where `NotADirectory` is
299 // stable, invert this logic and return None for it and `NotFound`
300 // and propagate any other error.
301 ErrorKind::PermissionDenied => Err(error).with_context(|| {
302 IoErrorContext::ReadingMetadata(path.to_owned())
303 }),
304 _ => Ok(None),
305 },
306 }
307 }
308
309 fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
310 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
311 }
312
313 fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
314 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
315 }
General Comments 0
You need to be logged in to leave comments. Login now