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