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