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