##// 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 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::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 crate::utils::{current_dir, SliceExt};
5 use crate::utils::{current_dir, SliceExt};
6 use memmap::{Mmap, MmapOptions};
6 use memmap::{Mmap, MmapOptions};
7 use std::collections::HashSet;
7 use std::collections::HashSet;
8 use std::path::{Path, PathBuf};
8 use std::path::{Path, PathBuf};
9
9
10 /// A repository on disk
10 /// A repository on disk
11 pub struct Repo {
11 pub struct Repo {
12 working_directory: PathBuf,
12 working_directory: PathBuf,
13 dot_hg: PathBuf,
13 dot_hg: PathBuf,
14 store: PathBuf,
14 store: PathBuf,
15 requirements: HashSet<String>,
15 requirements: HashSet<String>,
16 config: Config,
16 config: Config,
17 }
17 }
18
18
19 #[derive(Debug, derive_more::From)]
19 #[derive(Debug, derive_more::From)]
20 pub enum RepoError {
20 pub enum RepoError {
21 NotFound {
21 NotFound {
22 at: PathBuf,
22 at: PathBuf,
23 },
23 },
24 #[from]
24 #[from]
25 ConfigParseError(ConfigParseError),
25 ConfigParseError(ConfigParseError),
26 #[from]
26 #[from]
27 Other(HgError),
27 Other(HgError),
28 }
28 }
29
29
30 impl From<ConfigError> for RepoError {
30 impl From<ConfigError> for RepoError {
31 fn from(error: ConfigError) -> Self {
31 fn from(error: ConfigError) -> Self {
32 match error {
32 match error {
33 ConfigError::Parse(error) => error.into(),
33 ConfigError::Parse(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 #[derive(Clone, Copy)]
40 #[derive(Clone, Copy)]
41 pub struct Vfs<'a> {
41 pub struct Vfs<'a> {
42 pub(crate) base: &'a Path,
42 pub(crate) base: &'a Path,
43 }
43 }
44
44
45 impl Repo {
45 impl Repo {
46 /// Find a repository, either at the given path (which must contain a `.hg`
46 /// Find a repository, either at the given path (which must contain a `.hg`
47 /// sub-directory) or by searching the current directory and its
47 /// sub-directory) or by searching the current directory and its
48 /// ancestors.
48 /// ancestors.
49 ///
49 ///
50 /// A method with two very different "modes" like this usually a code smell
50 /// A method with two very different "modes" like this usually a code smell
51 /// to make two methods instead, but in this case an `Option` is what rhg
51 /// to make two methods instead, but in this case an `Option` is what rhg
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 /// Having two methods would just move that `if` to almost all callers.
53 /// Having two methods would just move that `if` to almost all callers.
54 pub fn find(
54 pub fn find(
55 config: &Config,
55 config: &Config,
56 explicit_path: Option<&Path>,
56 explicit_path: Option<&Path>,
57 ) -> Result<Self, RepoError> {
57 ) -> Result<Self, RepoError> {
58 if let Some(root) = explicit_path {
58 if let Some(root) = explicit_path {
59 // Having an absolute path isn’t necessary here but can help code
59 // Having an absolute path isn’t necessary here but can help code
60 // elsewhere
60 // elsewhere
61 let absolute_root = current_dir()?.join(root);
61 let absolute_root = current_dir()?.join(root);
62 if absolute_root.join(".hg").is_dir() {
62 if absolute_root.join(".hg").is_dir() {
63 Self::new_at_path(absolute_root, config)
63 Self::new_at_path(absolute_root, config)
64 } else if absolute_root.is_file() {
65 Err(HgError::unsupported("bundle repository").into())
64 } else {
66 } else {
65 Err(RepoError::NotFound {
67 Err(RepoError::NotFound {
66 at: root.to_owned(),
68 at: root.to_owned(),
67 })
69 })
68 }
70 }
69 } else {
71 } else {
70 let current_directory = crate::utils::current_dir()?;
72 let current_directory = crate::utils::current_dir()?;
71 // ancestors() is inclusive: it first yields `current_directory`
73 // ancestors() is inclusive: it first yields `current_directory`
72 // as-is.
74 // as-is.
73 for ancestor in current_directory.ancestors() {
75 for ancestor in current_directory.ancestors() {
74 if ancestor.join(".hg").is_dir() {
76 if ancestor.join(".hg").is_dir() {
75 return Self::new_at_path(ancestor.to_owned(), config);
77 return Self::new_at_path(ancestor.to_owned(), config);
76 }
78 }
77 }
79 }
78 Err(RepoError::NotFound {
80 Err(RepoError::NotFound {
79 at: current_directory,
81 at: current_directory,
80 })
82 })
81 }
83 }
82 }
84 }
83
85
84 /// To be called after checking that `.hg` is a sub-directory
86 /// To be called after checking that `.hg` is a sub-directory
85 fn new_at_path(
87 fn new_at_path(
86 working_directory: PathBuf,
88 working_directory: PathBuf,
87 config: &Config,
89 config: &Config,
88 ) -> Result<Self, RepoError> {
90 ) -> Result<Self, RepoError> {
89 let dot_hg = working_directory.join(".hg");
91 let dot_hg = working_directory.join(".hg");
90
92
91 let mut repo_config_files = Vec::new();
93 let mut repo_config_files = Vec::new();
92 repo_config_files.push(dot_hg.join("hgrc"));
94 repo_config_files.push(dot_hg.join("hgrc"));
93 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
95 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
94
96
95 let hg_vfs = Vfs { base: &dot_hg };
97 let hg_vfs = Vfs { base: &dot_hg };
96 let mut reqs = requirements::load_if_exists(hg_vfs)?;
98 let mut reqs = requirements::load_if_exists(hg_vfs)?;
97 let relative =
99 let relative =
98 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
100 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
99 let shared =
101 let shared =
100 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
102 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
101
103
102 // From `mercurial/localrepo.py`:
104 // From `mercurial/localrepo.py`:
103 //
105 //
104 // if .hg/requires contains the sharesafe requirement, it means
106 // if .hg/requires contains the sharesafe requirement, it means
105 // there exists a `.hg/store/requires` too and we should read it
107 // there exists a `.hg/store/requires` too and we should read it
106 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
108 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
107 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
109 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
108 // is not present, refer checkrequirementscompat() for that
110 // is not present, refer checkrequirementscompat() for that
109 //
111 //
110 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
112 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
111 // repository was shared the old way. We check the share source
113 // repository was shared the old way. We check the share source
112 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
114 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
113 // current repository needs to be reshared
115 // current repository needs to be reshared
114 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
116 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
115
117
116 let store_path;
118 let store_path;
117 if !shared {
119 if !shared {
118 store_path = dot_hg.join("store");
120 store_path = dot_hg.join("store");
119 } else {
121 } else {
120 let bytes = hg_vfs.read("sharedpath")?;
122 let bytes = hg_vfs.read("sharedpath")?;
121 let mut shared_path =
123 let mut shared_path =
122 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
124 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
123 if relative {
125 if relative {
124 shared_path = dot_hg.join(shared_path)
126 shared_path = dot_hg.join(shared_path)
125 }
127 }
126 if !shared_path.is_dir() {
128 if !shared_path.is_dir() {
127 return Err(HgError::corrupted(format!(
129 return Err(HgError::corrupted(format!(
128 ".hg/sharedpath points to nonexistent directory {}",
130 ".hg/sharedpath points to nonexistent directory {}",
129 shared_path.display()
131 shared_path.display()
130 ))
132 ))
131 .into());
133 .into());
132 }
134 }
133
135
134 store_path = shared_path.join("store");
136 store_path = shared_path.join("store");
135
137
136 let source_is_share_safe =
138 let source_is_share_safe =
137 requirements::load(Vfs { base: &shared_path })?
139 requirements::load(Vfs { base: &shared_path })?
138 .contains(requirements::SHARESAFE_REQUIREMENT);
140 .contains(requirements::SHARESAFE_REQUIREMENT);
139
141
140 if share_safe && !source_is_share_safe {
142 if share_safe && !source_is_share_safe {
141 return Err(match config
143 return Err(match config
142 .get(b"safe-mismatch", b"source-not-safe")
144 .get(b"safe-mismatch", b"source-not-safe")
143 {
145 {
144 Some(b"abort") | None => HgError::abort(
146 Some(b"abort") | None => HgError::abort(
145 "share source does not support share-safe requirement",
147 "share source does not support share-safe requirement",
146 ),
148 ),
147 _ => HgError::unsupported("share-safe downgrade"),
149 _ => HgError::unsupported("share-safe downgrade"),
148 }
150 }
149 .into());
151 .into());
150 } else if source_is_share_safe && !share_safe {
152 } else if source_is_share_safe && !share_safe {
151 return Err(
153 return Err(
152 match config.get(b"safe-mismatch", b"source-safe") {
154 match config.get(b"safe-mismatch", b"source-safe") {
153 Some(b"abort") | None => HgError::abort(
155 Some(b"abort") | None => HgError::abort(
154 "version mismatch: source uses share-safe \
156 "version mismatch: source uses share-safe \
155 functionality while the current share does not",
157 functionality while the current share does not",
156 ),
158 ),
157 _ => HgError::unsupported("share-safe upgrade"),
159 _ => HgError::unsupported("share-safe upgrade"),
158 }
160 }
159 .into(),
161 .into(),
160 );
162 );
161 }
163 }
162
164
163 if share_safe {
165 if share_safe {
164 repo_config_files.insert(0, shared_path.join("hgrc"))
166 repo_config_files.insert(0, shared_path.join("hgrc"))
165 }
167 }
166 }
168 }
167 if share_safe {
169 if share_safe {
168 reqs.extend(requirements::load(Vfs { base: &store_path })?);
170 reqs.extend(requirements::load(Vfs { base: &store_path })?);
169 }
171 }
170
172
171 let repo_config = config.combine_with_repo(&repo_config_files)?;
173 let repo_config = config.combine_with_repo(&repo_config_files)?;
172
174
173 let repo = Self {
175 let repo = Self {
174 requirements: reqs,
176 requirements: reqs,
175 working_directory,
177 working_directory,
176 store: store_path,
178 store: store_path,
177 dot_hg,
179 dot_hg,
178 config: repo_config,
180 config: repo_config,
179 };
181 };
180
182
181 requirements::check(&repo)?;
183 requirements::check(&repo)?;
182
184
183 Ok(repo)
185 Ok(repo)
184 }
186 }
185
187
186 pub fn working_directory_path(&self) -> &Path {
188 pub fn working_directory_path(&self) -> &Path {
187 &self.working_directory
189 &self.working_directory
188 }
190 }
189
191
190 pub fn requirements(&self) -> &HashSet<String> {
192 pub fn requirements(&self) -> &HashSet<String> {
191 &self.requirements
193 &self.requirements
192 }
194 }
193
195
194 pub fn config(&self) -> &Config {
196 pub fn config(&self) -> &Config {
195 &self.config
197 &self.config
196 }
198 }
197
199
198 /// For accessing repository files (in `.hg`), except for the store
200 /// For accessing repository files (in `.hg`), except for the store
199 /// (`.hg/store`).
201 /// (`.hg/store`).
200 pub fn hg_vfs(&self) -> Vfs<'_> {
202 pub fn hg_vfs(&self) -> Vfs<'_> {
201 Vfs { base: &self.dot_hg }
203 Vfs { base: &self.dot_hg }
202 }
204 }
203
205
204 /// For accessing repository store files (in `.hg/store`)
206 /// For accessing repository store files (in `.hg/store`)
205 pub fn store_vfs(&self) -> Vfs<'_> {
207 pub fn store_vfs(&self) -> Vfs<'_> {
206 Vfs { base: &self.store }
208 Vfs { base: &self.store }
207 }
209 }
208
210
209 /// For accessing the working copy
211 /// For accessing the working copy
210
212
211 // The undescore prefix silences the "never used" warning. Remove before
213 // The undescore prefix silences the "never used" warning. Remove before
212 // using.
214 // using.
213 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
215 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
214 Vfs {
216 Vfs {
215 base: &self.working_directory,
217 base: &self.working_directory,
216 }
218 }
217 }
219 }
218
220
219 pub fn dirstate_parents(
221 pub fn dirstate_parents(
220 &self,
222 &self,
221 ) -> Result<crate::dirstate::DirstateParents, HgError> {
223 ) -> Result<crate::dirstate::DirstateParents, HgError> {
222 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
224 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
223 let parents =
225 let parents =
224 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
226 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
225 Ok(parents.clone())
227 Ok(parents.clone())
226 }
228 }
227 }
229 }
228
230
229 impl Vfs<'_> {
231 impl Vfs<'_> {
230 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
232 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
231 self.base.join(relative_path)
233 self.base.join(relative_path)
232 }
234 }
233
235
234 pub fn read(
236 pub fn read(
235 &self,
237 &self,
236 relative_path: impl AsRef<Path>,
238 relative_path: impl AsRef<Path>,
237 ) -> Result<Vec<u8>, HgError> {
239 ) -> Result<Vec<u8>, HgError> {
238 let path = self.join(relative_path);
240 let path = self.join(relative_path);
239 std::fs::read(&path).when_reading_file(&path)
241 std::fs::read(&path).when_reading_file(&path)
240 }
242 }
241
243
242 pub fn mmap_open(
244 pub fn mmap_open(
243 &self,
245 &self,
244 relative_path: impl AsRef<Path>,
246 relative_path: impl AsRef<Path>,
245 ) -> Result<Mmap, HgError> {
247 ) -> Result<Mmap, HgError> {
246 let path = self.base.join(relative_path);
248 let path = self.base.join(relative_path);
247 let file = std::fs::File::open(&path).when_reading_file(&path)?;
249 let file = std::fs::File::open(&path).when_reading_file(&path)?;
248 // TODO: what are the safety requirements here?
250 // TODO: what are the safety requirements here?
249 let mmap = unsafe { MmapOptions::new().map(&file) }
251 let mmap = unsafe { MmapOptions::new().map(&file) }
250 .when_reading_file(&path)?;
252 .when_reading_file(&path)?;
251 Ok(mmap)
253 Ok(mmap)
252 }
254 }
253
255
254 pub fn rename(
256 pub fn rename(
255 &self,
257 &self,
256 relative_from: impl AsRef<Path>,
258 relative_from: impl AsRef<Path>,
257 relative_to: impl AsRef<Path>,
259 relative_to: impl AsRef<Path>,
258 ) -> Result<(), HgError> {
260 ) -> Result<(), HgError> {
259 let from = self.join(relative_from);
261 let from = self.join(relative_from);
260 let to = self.join(relative_to);
262 let to = self.join(relative_to);
261 std::fs::rename(&from, &to)
263 std::fs::rename(&from, &to)
262 .with_context(|| IoErrorContext::RenamingFile { from, to })
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