##// END OF EJS Templates
rhg: Bug fix: with share-safe, always read store requirements...
Simon Sapin -
r47357:f64b6953 default
parent child Browse files
Show More
@@ -1,263 +1,263
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::current_dir;
5 5 use crate::utils::files::get_path_from_bytes;
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 // Having an absolute path isn’t necessary here but can help code
60 60 // elsewhere
61 61 let root = current_dir()?.join(root);
62 62 if root.join(".hg").is_dir() {
63 63 Self::new_at_path(root, config)
64 64 } else {
65 65 Err(RepoError::NotFound {
66 66 at: root.to_owned(),
67 67 })
68 68 }
69 69 } else {
70 70 let current_directory = crate::utils::current_dir()?;
71 71 // ancestors() is inclusive: it first yields `current_directory`
72 72 // as-is.
73 73 for ancestor in current_directory.ancestors() {
74 74 if ancestor.join(".hg").is_dir() {
75 75 return Self::new_at_path(ancestor.to_owned(), config);
76 76 }
77 77 }
78 78 Err(RepoError::NotFound {
79 79 at: current_directory,
80 80 })
81 81 }
82 82 }
83 83
84 84 /// To be called after checking that `.hg` is a sub-directory
85 85 fn new_at_path(
86 86 working_directory: PathBuf,
87 87 config: &Config,
88 88 ) -> Result<Self, RepoError> {
89 89 let dot_hg = working_directory.join(".hg");
90 90
91 91 let mut repo_config_files = Vec::new();
92 92 repo_config_files.push(dot_hg.join("hgrc"));
93 93 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
94 94
95 95 let hg_vfs = Vfs { base: &dot_hg };
96 96 let mut reqs = requirements::load_if_exists(hg_vfs)?;
97 97 let relative =
98 98 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
99 99 let shared =
100 100 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
101 101
102 102 // From `mercurial/localrepo.py`:
103 103 //
104 104 // if .hg/requires contains the sharesafe requirement, it means
105 105 // there exists a `.hg/store/requires` too and we should read it
106 106 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
107 107 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
108 108 // is not present, refer checkrequirementscompat() for that
109 109 //
110 110 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
111 111 // repository was shared the old way. We check the share source
112 112 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
113 113 // current repository needs to be reshared
114 114 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
115 115
116 116 let store_path;
117 117 if !shared {
118 118 store_path = dot_hg.join("store");
119 if share_safe {
120 reqs.extend(requirements::load(Vfs { base: &store_path })?);
121 }
122 119 } else {
123 120 let bytes = hg_vfs.read("sharedpath")?;
124 121 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
125 122 if relative {
126 123 shared_path = dot_hg.join(shared_path)
127 124 }
128 125 if !shared_path.is_dir() {
129 126 return Err(HgError::corrupted(format!(
130 127 ".hg/sharedpath points to nonexistent directory {}",
131 128 shared_path.display()
132 129 ))
133 130 .into());
134 131 }
135 132
136 133 store_path = shared_path.join("store");
137 134
138 135 let source_is_share_safe =
139 136 requirements::load(Vfs { base: &shared_path })?
140 137 .contains(requirements::SHARESAFE_REQUIREMENT);
141 138
142 139 if share_safe && !source_is_share_safe {
143 140 return Err(match config
144 141 .get(b"safe-mismatch", b"source-not-safe")
145 142 {
146 143 Some(b"abort") | None => HgError::abort(
147 144 "share source does not support share-safe requirement",
148 145 ),
149 146 _ => HgError::unsupported("share-safe downgrade"),
150 147 }
151 148 .into());
152 149 } else if source_is_share_safe && !share_safe {
153 150 return Err(
154 151 match config.get(b"safe-mismatch", b"source-safe") {
155 152 Some(b"abort") | None => HgError::abort(
156 153 "version mismatch: source uses share-safe \
157 154 functionality while the current share does not",
158 155 ),
159 156 _ => HgError::unsupported("share-safe upgrade"),
160 157 }
161 158 .into(),
162 159 );
163 160 }
164 161
165 162 if share_safe {
166 163 repo_config_files.insert(0, shared_path.join("hgrc"))
167 164 }
168 165 }
166 if share_safe {
167 reqs.extend(requirements::load(Vfs { base: &store_path })?);
168 }
169 169
170 170 let repo_config = config.combine_with_repo(&repo_config_files)?;
171 171
172 172 let repo = Self {
173 173 requirements: reqs,
174 174 working_directory,
175 175 store: store_path,
176 176 dot_hg,
177 177 config: repo_config,
178 178 };
179 179
180 180 requirements::check(&repo)?;
181 181
182 182 Ok(repo)
183 183 }
184 184
185 185 pub fn working_directory_path(&self) -> &Path {
186 186 &self.working_directory
187 187 }
188 188
189 189 pub fn requirements(&self) -> &HashSet<String> {
190 190 &self.requirements
191 191 }
192 192
193 193 pub fn config(&self) -> &Config {
194 194 &self.config
195 195 }
196 196
197 197 /// For accessing repository files (in `.hg`), except for the store
198 198 /// (`.hg/store`).
199 199 pub fn hg_vfs(&self) -> Vfs<'_> {
200 200 Vfs { base: &self.dot_hg }
201 201 }
202 202
203 203 /// For accessing repository store files (in `.hg/store`)
204 204 pub fn store_vfs(&self) -> Vfs<'_> {
205 205 Vfs { base: &self.store }
206 206 }
207 207
208 208 /// For accessing the working copy
209 209
210 210 // The undescore prefix silences the "never used" warning. Remove before
211 211 // using.
212 212 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
213 213 Vfs {
214 214 base: &self.working_directory,
215 215 }
216 216 }
217 217
218 218 pub fn dirstate_parents(
219 219 &self,
220 220 ) -> Result<crate::dirstate::DirstateParents, HgError> {
221 221 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
222 222 let parents =
223 223 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
224 224 Ok(parents.clone())
225 225 }
226 226 }
227 227
228 228 impl Vfs<'_> {
229 229 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
230 230 self.base.join(relative_path)
231 231 }
232 232
233 233 pub fn read(
234 234 &self,
235 235 relative_path: impl AsRef<Path>,
236 236 ) -> Result<Vec<u8>, HgError> {
237 237 let path = self.join(relative_path);
238 238 std::fs::read(&path).when_reading_file(&path)
239 239 }
240 240
241 241 pub fn mmap_open(
242 242 &self,
243 243 relative_path: impl AsRef<Path>,
244 244 ) -> Result<Mmap, HgError> {
245 245 let path = self.base.join(relative_path);
246 246 let file = std::fs::File::open(&path).when_reading_file(&path)?;
247 247 // TODO: what are the safety requirements here?
248 248 let mmap = unsafe { MmapOptions::new().map(&file) }
249 249 .when_reading_file(&path)?;
250 250 Ok(mmap)
251 251 }
252 252
253 253 pub fn rename(
254 254 &self,
255 255 relative_from: impl AsRef<Path>,
256 256 relative_to: impl AsRef<Path>,
257 257 ) -> Result<(), HgError> {
258 258 let from = self.join(relative_from);
259 259 let to = self.join(relative_to);
260 260 std::fs::rename(&from, &to)
261 261 .with_context(|| IoErrorContext::RenamingFile { from, to })
262 262 }
263 263 }
General Comments 0
You need to be logged in to leave comments. Login now