##// END OF EJS Templates
rhg: Abort based on config on share-safe mismatch...
Simon Sapin -
r47214:f031fe1c default
parent child Browse files
Show More
@@ -1,121 +1,133 b''
1 use std::fmt;
1 use std::fmt;
2
2
3 /// Common error cases that can happen in many different APIs
3 /// Common error cases that can happen in many different APIs
4 #[derive(Debug)]
4 #[derive(Debug)]
5 pub enum HgError {
5 pub enum HgError {
6 IoError {
6 IoError {
7 error: std::io::Error,
7 error: std::io::Error,
8 context: IoErrorContext,
8 context: IoErrorContext,
9 },
9 },
10
10
11 /// A file under `.hg/` normally only written by Mercurial
11 /// A file under `.hg/` normally only written by Mercurial is not in the
12 /// expected format. This indicates a bug in Mercurial, filesystem
13 /// corruption, or hardware failure.
12 ///
14 ///
13 /// The given string is a short explanation for users, not intended to be
15 /// The given string is a short explanation for users, not intended to be
14 /// machine-readable.
16 /// machine-readable.
15 CorruptedRepository(String),
17 CorruptedRepository(String),
16
18
17 /// The respository or requested operation involves a feature not
19 /// The respository or requested operation involves a feature not
18 /// supported by the Rust implementation. Falling back to the Python
20 /// supported by the Rust implementation. Falling back to the Python
19 /// implementation may or may not work.
21 /// implementation may or may not work.
20 ///
22 ///
21 /// The given string is a short explanation for users, not intended to be
23 /// The given string is a short explanation for users, not intended to be
22 /// machine-readable.
24 /// machine-readable.
23 UnsupportedFeature(String),
25 UnsupportedFeature(String),
26
27 /// Operation cannot proceed for some other reason.
28 ///
29 /// The given string is a short explanation for users, not intended to be
30 /// machine-readable.
31 Abort(String),
24 }
32 }
25
33
26 /// Details about where an I/O error happened
34 /// Details about where an I/O error happened
27 #[derive(Debug, derive_more::From)]
35 #[derive(Debug, derive_more::From)]
28 pub enum IoErrorContext {
36 pub enum IoErrorContext {
29 /// A filesystem operation for the given file
37 /// A filesystem operation for the given file
30 #[from]
38 #[from]
31 File(std::path::PathBuf),
39 File(std::path::PathBuf),
32 /// `std::env::current_dir`
40 /// `std::env::current_dir`
33 CurrentDir,
41 CurrentDir,
34 /// `std::env::current_exe`
42 /// `std::env::current_exe`
35 CurrentExe,
43 CurrentExe,
36 }
44 }
37
45
38 impl HgError {
46 impl HgError {
39 pub fn corrupted(explanation: impl Into<String>) -> Self {
47 pub fn corrupted(explanation: impl Into<String>) -> Self {
40 // TODO: capture a backtrace here and keep it in the error value
48 // TODO: capture a backtrace here and keep it in the error value
41 // to aid debugging?
49 // to aid debugging?
42 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
50 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
43 HgError::CorruptedRepository(explanation.into())
51 HgError::CorruptedRepository(explanation.into())
44 }
52 }
45
53
46 pub fn unsupported(explanation: impl Into<String>) -> Self {
54 pub fn unsupported(explanation: impl Into<String>) -> Self {
47 HgError::UnsupportedFeature(explanation.into())
55 HgError::UnsupportedFeature(explanation.into())
48 }
56 }
57 pub fn abort(explanation: impl Into<String>) -> Self {
58 HgError::Abort(explanation.into())
59 }
49 }
60 }
50
61
51 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
62 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
52 impl fmt::Display for HgError {
63 impl fmt::Display for HgError {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 match self {
65 match self {
55 HgError::IoError { error, context } => {
66 HgError::IoError { error, context } => {
56 write!(f, "{}: {}", error, context)
67 write!(f, "{}: {}", error, context)
57 }
68 }
58 HgError::CorruptedRepository(explanation) => {
69 HgError::CorruptedRepository(explanation) => {
59 write!(f, "corrupted repository: {}", explanation)
70 write!(f, "corrupted repository: {}", explanation)
60 }
71 }
61 HgError::UnsupportedFeature(explanation) => {
72 HgError::UnsupportedFeature(explanation) => {
62 write!(f, "unsupported feature: {}", explanation)
73 write!(f, "unsupported feature: {}", explanation)
63 }
74 }
75 HgError::Abort(explanation) => explanation.fmt(f),
64 }
76 }
65 }
77 }
66 }
78 }
67
79
68 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
80 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
69 impl fmt::Display for IoErrorContext {
81 impl fmt::Display for IoErrorContext {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 match self {
83 match self {
72 IoErrorContext::File(path) => path.display().fmt(f),
84 IoErrorContext::File(path) => path.display().fmt(f),
73 IoErrorContext::CurrentDir => f.write_str("current directory"),
85 IoErrorContext::CurrentDir => f.write_str("current directory"),
74 IoErrorContext::CurrentExe => f.write_str("current executable"),
86 IoErrorContext::CurrentExe => f.write_str("current executable"),
75 }
87 }
76 }
88 }
77 }
89 }
78
90
79 pub trait IoResultExt<T> {
91 pub trait IoResultExt<T> {
80 /// Annotate a possible I/O error as related to a file at the given path.
92 /// Annotate a possible I/O error as related to a file at the given path.
81 ///
93 ///
82 /// This allows printing something like β€œFile not found: example.txt”
94 /// This allows printing something like β€œFile not found: example.txt”
83 /// instead of just β€œFile not found”.
95 /// instead of just β€œFile not found”.
84 ///
96 ///
85 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
97 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
86 fn for_file(self, path: &std::path::Path) -> Result<T, HgError>;
98 fn for_file(self, path: &std::path::Path) -> Result<T, HgError>;
87 }
99 }
88
100
89 impl<T> IoResultExt<T> for std::io::Result<T> {
101 impl<T> IoResultExt<T> for std::io::Result<T> {
90 fn for_file(self, path: &std::path::Path) -> Result<T, HgError> {
102 fn for_file(self, path: &std::path::Path) -> Result<T, HgError> {
91 self.map_err(|error| HgError::IoError {
103 self.map_err(|error| HgError::IoError {
92 error,
104 error,
93 context: IoErrorContext::File(path.to_owned()),
105 context: IoErrorContext::File(path.to_owned()),
94 })
106 })
95 }
107 }
96 }
108 }
97
109
98 pub trait HgResultExt<T> {
110 pub trait HgResultExt<T> {
99 /// Handle missing files separately from other I/O error cases.
111 /// Handle missing files separately from other I/O error cases.
100 ///
112 ///
101 /// Wraps the `Ok` type in an `Option`:
113 /// Wraps the `Ok` type in an `Option`:
102 ///
114 ///
103 /// * `Ok(x)` becomes `Ok(Some(x))`
115 /// * `Ok(x)` becomes `Ok(Some(x))`
104 /// * An I/O "not found" error becomes `Ok(None)`
116 /// * An I/O "not found" error becomes `Ok(None)`
105 /// * Other errors are unchanged
117 /// * Other errors are unchanged
106 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
118 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
107 }
119 }
108
120
109 impl<T> HgResultExt<T> for Result<T, HgError> {
121 impl<T> HgResultExt<T> for Result<T, HgError> {
110 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
122 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
111 match self {
123 match self {
112 Ok(x) => Ok(Some(x)),
124 Ok(x) => Ok(Some(x)),
113 Err(HgError::IoError { error, .. })
125 Err(HgError::IoError { error, .. })
114 if error.kind() == std::io::ErrorKind::NotFound =>
126 if error.kind() == std::io::ErrorKind::NotFound =>
115 {
127 {
116 Ok(None)
128 Ok(None)
117 }
129 }
118 Err(other_error) => Err(other_error),
130 Err(other_error) => Err(other_error),
119 }
131 }
120 }
132 }
121 }
133 }
@@ -1,171 +1,186 b''
1 use crate::config::Config;
1 use crate::config::Config;
2 use crate::errors::{HgError, IoResultExt};
2 use crate::errors::{HgError, 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 memmap::{Mmap, MmapOptions};
5 use memmap::{Mmap, MmapOptions};
6 use std::collections::HashSet;
6 use std::collections::HashSet;
7 use std::path::{Path, PathBuf};
7 use std::path::{Path, PathBuf};
8
8
9 /// A repository on disk
9 /// A repository on disk
10 pub struct Repo {
10 pub struct Repo {
11 working_directory: PathBuf,
11 working_directory: PathBuf,
12 dot_hg: PathBuf,
12 dot_hg: PathBuf,
13 store: PathBuf,
13 store: PathBuf,
14 requirements: HashSet<String>,
14 requirements: HashSet<String>,
15 }
15 }
16
16
17 #[derive(Debug, derive_more::From)]
17 #[derive(Debug, derive_more::From)]
18 pub enum RepoFindError {
18 pub enum RepoFindError {
19 NotFoundInCurrentDirectoryOrAncestors {
19 NotFoundInCurrentDirectoryOrAncestors {
20 current_directory: PathBuf,
20 current_directory: PathBuf,
21 },
21 },
22 #[from]
22 #[from]
23 Other(HgError),
23 Other(HgError),
24 }
24 }
25
25
26 /// Filesystem access abstraction for the contents of a given "base" diretory
26 /// Filesystem access abstraction for the contents of a given "base" diretory
27 #[derive(Clone, Copy)]
27 #[derive(Clone, Copy)]
28 pub(crate) struct Vfs<'a> {
28 pub(crate) struct Vfs<'a> {
29 base: &'a Path,
29 base: &'a Path,
30 }
30 }
31
31
32 impl Repo {
32 impl Repo {
33 /// Search the current directory and its ancestores for a repository:
33 /// Search the current directory and its ancestores for a repository:
34 /// a working directory that contains a `.hg` sub-directory.
34 /// a working directory that contains a `.hg` sub-directory.
35 pub fn find(_config: &Config) -> Result<Self, RepoFindError> {
35 pub fn find(config: &Config) -> Result<Self, RepoFindError> {
36 let current_directory = crate::utils::current_dir()?;
36 let current_directory = crate::utils::current_dir()?;
37 // ancestors() is inclusive: it first yields `current_directory` as-is.
37 // ancestors() is inclusive: it first yields `current_directory` as-is.
38 for ancestor in current_directory.ancestors() {
38 for ancestor in current_directory.ancestors() {
39 if ancestor.join(".hg").is_dir() {
39 if ancestor.join(".hg").is_dir() {
40 return Ok(Self::new_at_path(ancestor.to_owned())?);
40 return Ok(Self::new_at_path(ancestor.to_owned(), config)?);
41 }
41 }
42 }
42 }
43 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
43 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
44 current_directory,
44 current_directory,
45 })
45 })
46 }
46 }
47
47
48 /// To be called after checking that `.hg` is a sub-directory
48 /// To be called after checking that `.hg` is a sub-directory
49 fn new_at_path(working_directory: PathBuf) -> Result<Self, HgError> {
49 fn new_at_path(
50 working_directory: PathBuf,
51 config: &Config,
52 ) -> Result<Self, HgError> {
50 let dot_hg = working_directory.join(".hg");
53 let dot_hg = working_directory.join(".hg");
51
54
52 let hg_vfs = Vfs { base: &dot_hg };
55 let hg_vfs = Vfs { base: &dot_hg };
53 let mut reqs = requirements::load_if_exists(hg_vfs)?;
56 let mut reqs = requirements::load_if_exists(hg_vfs)?;
54 let relative =
57 let relative =
55 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
58 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
56 let shared =
59 let shared =
57 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
60 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
58
61
59 // From `mercurial/localrepo.py`:
62 // From `mercurial/localrepo.py`:
60 //
63 //
61 // if .hg/requires contains the sharesafe requirement, it means
64 // if .hg/requires contains the sharesafe requirement, it means
62 // there exists a `.hg/store/requires` too and we should read it
65 // there exists a `.hg/store/requires` too and we should read it
63 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
66 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
64 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
67 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
65 // is not present, refer checkrequirementscompat() for that
68 // is not present, refer checkrequirementscompat() for that
66 //
69 //
67 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
70 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
68 // repository was shared the old way. We check the share source
71 // repository was shared the old way. We check the share source
69 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
72 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
70 // current repository needs to be reshared
73 // current repository needs to be reshared
71 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
74 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
72
75
73 let store_path;
76 let store_path;
74 if !shared {
77 if !shared {
75 store_path = dot_hg.join("store");
78 store_path = dot_hg.join("store");
76 if share_safe {
79 if share_safe {
77 reqs.extend(requirements::load(Vfs { base: &store_path })?);
80 reqs.extend(requirements::load(Vfs { base: &store_path })?);
78 }
81 }
79 } else {
82 } else {
80 let bytes = hg_vfs.read("sharedpath")?;
83 let bytes = hg_vfs.read("sharedpath")?;
81 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
84 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
82 if relative {
85 if relative {
83 shared_path = dot_hg.join(shared_path)
86 shared_path = dot_hg.join(shared_path)
84 }
87 }
85 if !shared_path.is_dir() {
88 if !shared_path.is_dir() {
86 return Err(HgError::corrupted(format!(
89 return Err(HgError::corrupted(format!(
87 ".hg/sharedpath points to nonexistent directory {}",
90 ".hg/sharedpath points to nonexistent directory {}",
88 shared_path.display()
91 shared_path.display()
89 )));
92 )));
90 }
93 }
91
94
92 store_path = shared_path.join("store");
95 store_path = shared_path.join("store");
93
96
94 let source_is_share_safe =
97 let source_is_share_safe =
95 requirements::load(Vfs { base: &shared_path })?
98 requirements::load(Vfs { base: &shared_path })?
96 .contains(requirements::SHARESAFE_REQUIREMENT);
99 .contains(requirements::SHARESAFE_REQUIREMENT);
97
100
98 // TODO: support for `share.safe-mismatch.*` config
99 if share_safe && !source_is_share_safe {
101 if share_safe && !source_is_share_safe {
100 return Err(HgError::unsupported("share-safe downgrade"));
102 return Err(match config.get(b"safe-mismatch", b"source-not-safe") {
103 Some(b"abort") | None => HgError::abort(
104 "share source does not support share-safe requirement"
105 ),
106 _ => HgError::unsupported("share-safe downgrade")
107 });
101 } else if source_is_share_safe && !share_safe {
108 } else if source_is_share_safe && !share_safe {
102 return Err(HgError::unsupported("share-safe upgrade"));
109 return Err(
110 match config.get(b"safe-mismatch", b"source-safe") {
111 Some(b"abort") | None => HgError::abort(
112 "version mismatch: source uses share-safe \
113 functionality while the current share does not",
114 ),
115 _ => HgError::unsupported("share-safe upgrade"),
116 },
117 );
103 }
118 }
104 }
119 }
105
120
106 let repo = Self {
121 let repo = Self {
107 requirements: reqs,
122 requirements: reqs,
108 working_directory,
123 working_directory,
109 store: store_path,
124 store: store_path,
110 dot_hg,
125 dot_hg,
111 };
126 };
112
127
113 requirements::check(&repo)?;
128 requirements::check(&repo)?;
114
129
115 Ok(repo)
130 Ok(repo)
116 }
131 }
117
132
118 pub fn working_directory_path(&self) -> &Path {
133 pub fn working_directory_path(&self) -> &Path {
119 &self.working_directory
134 &self.working_directory
120 }
135 }
121
136
122 pub fn requirements(&self) -> &HashSet<String> {
137 pub fn requirements(&self) -> &HashSet<String> {
123 &self.requirements
138 &self.requirements
124 }
139 }
125
140
126 /// For accessing repository files (in `.hg`), except for the store
141 /// For accessing repository files (in `.hg`), except for the store
127 /// (`.hg/store`).
142 /// (`.hg/store`).
128 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
143 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
129 Vfs { base: &self.dot_hg }
144 Vfs { base: &self.dot_hg }
130 }
145 }
131
146
132 /// For accessing repository store files (in `.hg/store`)
147 /// For accessing repository store files (in `.hg/store`)
133 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
148 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
134 Vfs { base: &self.store }
149 Vfs { base: &self.store }
135 }
150 }
136
151
137 /// For accessing the working copy
152 /// For accessing the working copy
138
153
139 // The undescore prefix silences the "never used" warning. Remove before
154 // The undescore prefix silences the "never used" warning. Remove before
140 // using.
155 // using.
141 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
156 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
142 Vfs {
157 Vfs {
143 base: &self.working_directory,
158 base: &self.working_directory,
144 }
159 }
145 }
160 }
146 }
161 }
147
162
148 impl Vfs<'_> {
163 impl Vfs<'_> {
149 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
164 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
150 self.base.join(relative_path)
165 self.base.join(relative_path)
151 }
166 }
152
167
153 pub(crate) fn read(
168 pub(crate) fn read(
154 &self,
169 &self,
155 relative_path: impl AsRef<Path>,
170 relative_path: impl AsRef<Path>,
156 ) -> Result<Vec<u8>, HgError> {
171 ) -> Result<Vec<u8>, HgError> {
157 let path = self.join(relative_path);
172 let path = self.join(relative_path);
158 std::fs::read(&path).for_file(&path)
173 std::fs::read(&path).for_file(&path)
159 }
174 }
160
175
161 pub(crate) fn mmap_open(
176 pub(crate) fn mmap_open(
162 &self,
177 &self,
163 relative_path: impl AsRef<Path>,
178 relative_path: impl AsRef<Path>,
164 ) -> Result<Mmap, HgError> {
179 ) -> Result<Mmap, HgError> {
165 let path = self.base.join(relative_path);
180 let path = self.base.join(relative_path);
166 let file = std::fs::File::open(&path).for_file(&path)?;
181 let file = std::fs::File::open(&path).for_file(&path)?;
167 // TODO: what are the safety requirements here?
182 // TODO: what are the safety requirements here?
168 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
183 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
169 Ok(mmap)
184 Ok(mmap)
170 }
185 }
171 }
186 }
General Comments 0
You need to be logged in to leave comments. Login now