##// END OF EJS Templates
rust: Keep lazily-initialized Changelog and Manifest log on the Repo object...
Simon Sapin -
r48773:21d25e9e default
parent child Browse files
Show More
@@ -1,105 +1,103 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use std::path::PathBuf;
9 9
10 10 use crate::repo::Repo;
11 use crate::revlog::changelog::Changelog;
12 use crate::revlog::manifest::Manifestlog;
13 11 use crate::revlog::path_encode::path_encode;
14 12 use crate::revlog::revlog::Revlog;
15 13 use crate::revlog::revlog::RevlogError;
16 14 use crate::revlog::Node;
17 15 use crate::utils::files::get_path_from_bytes;
18 16 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 17
20 18 pub struct CatOutput {
21 19 /// Whether any file in the manifest matched the paths given as CLI
22 20 /// arguments
23 21 pub found_any: bool,
24 22 /// The contents of matching files, in manifest order
25 23 pub concatenated: Vec<u8>,
26 24 /// Which of the CLI arguments did not match any manifest file
27 25 pub missing: Vec<HgPathBuf>,
28 26 /// The node ID that the given revset was resolved to
29 27 pub node: Node,
30 28 }
31 29
32 30 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
33 31
34 32 /// Output the given revision of files
35 33 ///
36 34 /// * `root`: Repository root
37 35 /// * `rev`: The revision to cat the files from.
38 36 /// * `files`: The files to output.
39 37 pub fn cat<'a>(
40 38 repo: &Repo,
41 39 revset: &str,
42 40 files: &'a [HgPathBuf],
43 41 ) -> Result<CatOutput, RevlogError> {
44 42 let rev = crate::revset::resolve_single(revset, repo)?;
45 let changelog = Changelog::open(repo)?;
46 let manifest = Manifestlog::open(repo)?;
43 let changelog = repo.changelog()?;
44 let manifest = repo.manifestlog()?;
47 45 let changelog_entry = changelog.get_rev(rev)?;
48 46 let node = *changelog
49 47 .node_from_rev(rev)
50 48 .expect("should succeed when changelog.get_rev did");
51 49 let manifest_node =
52 50 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
53 51 let manifest_entry = manifest.get_node(manifest_node.into())?;
54 52 let mut bytes = vec![];
55 53 let mut matched = vec![false; files.len()];
56 54 let mut found_any = false;
57 55
58 56 for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
59 57 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
60 58 if cat_file.as_bytes() == manifest_file.as_bytes() {
61 59 *is_matched = true;
62 60 found_any = true;
63 61 let index_path = store_path(manifest_file, b".i");
64 62 let data_path = store_path(manifest_file, b".d");
65 63
66 64 let file_log =
67 65 Revlog::open(repo, &index_path, Some(&data_path))?;
68 66 let file_node = Node::from_hex_for_repo(node_bytes)?;
69 67 let file_rev = file_log.get_node_rev(file_node.into())?;
70 68 let data = file_log.get_rev_data(file_rev)?;
71 69 if data.starts_with(&METADATA_DELIMITER) {
72 70 let end_delimiter_position = data
73 71 [METADATA_DELIMITER.len()..]
74 72 .windows(METADATA_DELIMITER.len())
75 73 .position(|bytes| bytes == METADATA_DELIMITER);
76 74 if let Some(position) = end_delimiter_position {
77 75 let offset = METADATA_DELIMITER.len() * 2;
78 76 bytes.extend(data[position + offset..].iter());
79 77 }
80 78 } else {
81 79 bytes.extend(data);
82 80 }
83 81 }
84 82 }
85 83 }
86 84
87 85 let missing: Vec<_> = files
88 86 .iter()
89 87 .zip(&matched)
90 88 .filter(|pair| !*pair.1)
91 89 .map(|pair| pair.0.clone())
92 90 .collect();
93 91 Ok(CatOutput {
94 92 found_any,
95 93 concatenated: bytes,
96 94 missing,
97 95 node,
98 96 })
99 97 }
100 98
101 99 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
102 100 let encoded_bytes =
103 101 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
104 102 get_path_from_bytes(&encoded_bytes).into()
105 103 }
@@ -1,90 +1,89 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate_entries;
9 9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 10 use crate::errors::HgError;
11 11 use crate::repo::Repo;
12 use crate::revlog::changelog::Changelog;
13 use crate::revlog::manifest::{Manifest, Manifestlog};
12 use crate::revlog::manifest::Manifest;
14 13 use crate::revlog::node::Node;
15 14 use crate::revlog::revlog::RevlogError;
16 15 use crate::utils::hg_path::HgPath;
17 16 use crate::DirstateError;
18 17 use rayon::prelude::*;
19 18
20 19 /// List files under Mercurial control in the working directory
21 20 /// by reading the dirstate
22 21 pub struct Dirstate {
23 22 /// The `dirstate` content.
24 23 content: Vec<u8>,
25 24 v2_metadata: Option<Vec<u8>>,
26 25 }
27 26
28 27 impl Dirstate {
29 28 pub fn new(repo: &Repo) -> Result<Self, HgError> {
30 29 let mut content = repo.hg_vfs().read("dirstate")?;
31 30 let v2_metadata = if repo.has_dirstate_v2() {
32 31 let docket = read_docket(&content)?;
33 32 let meta = docket.tree_metadata().to_vec();
34 33 content = repo.hg_vfs().read(docket.data_filename())?;
35 34 Some(meta)
36 35 } else {
37 36 None
38 37 };
39 38 Ok(Self {
40 39 content,
41 40 v2_metadata,
42 41 })
43 42 }
44 43
45 44 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
46 45 let mut files = Vec::new();
47 46 if !self.content.is_empty() {
48 47 if let Some(meta) = &self.v2_metadata {
49 48 for_each_tracked_path(&self.content, meta, |path| {
50 49 files.push(path)
51 50 })?
52 51 } else {
53 52 let _parents = parse_dirstate_entries(
54 53 &self.content,
55 54 |path, entry, _copy_source| {
56 55 if entry.state.is_tracked() {
57 56 files.push(path)
58 57 }
59 58 Ok(())
60 59 },
61 60 )?;
62 61 }
63 62 }
64 63 files.par_sort_unstable();
65 64 Ok(files)
66 65 }
67 66 }
68 67
69 68 /// List files under Mercurial control at a given revision.
70 69 pub fn list_rev_tracked_files(
71 70 repo: &Repo,
72 71 revset: &str,
73 72 ) -> Result<FilesForRev, RevlogError> {
74 73 let rev = crate::revset::resolve_single(revset, repo)?;
75 let changelog = Changelog::open(repo)?;
76 let manifest = Manifestlog::open(repo)?;
74 let changelog = repo.changelog()?;
75 let manifest = repo.manifestlog()?;
77 76 let changelog_entry = changelog.get_rev(rev)?;
78 77 let manifest_node =
79 78 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
80 79 let manifest_entry = manifest.get_node(manifest_node.into())?;
81 80 Ok(FilesForRev(manifest_entry))
82 81 }
83 82
84 83 pub struct FilesForRev(Manifest);
85 84
86 85 impl FilesForRev {
87 86 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
88 87 self.0.files()
89 88 }
90 89 }
@@ -1,367 +1,390 b''
1 use crate::changelog::Changelog;
1 2 use crate::config::{Config, ConfigError, ConfigParseError};
2 3 use crate::dirstate::DirstateParents;
3 4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 6 use crate::errors::HgError;
6 7 use crate::errors::HgResultExt;
7 8 use crate::exit_codes;
9 use crate::manifest::Manifestlog;
8 10 use crate::requirements;
11 use crate::revlog::revlog::RevlogError;
9 12 use crate::utils::files::get_path_from_bytes;
10 13 use crate::utils::SliceExt;
11 14 use crate::vfs::{is_dir, is_file, Vfs};
12 15 use crate::DirstateError;
13 16 use std::cell::{Cell, Ref, RefCell, RefMut};
14 17 use std::collections::HashSet;
15 18 use std::path::{Path, PathBuf};
16 19
17 20 /// A repository on disk
18 21 pub struct Repo {
19 22 working_directory: PathBuf,
20 23 dot_hg: PathBuf,
21 24 store: PathBuf,
22 25 requirements: HashSet<String>,
23 26 config: Config,
24 27 // None means not known/initialized yet
25 28 dirstate_parents: Cell<Option<DirstateParents>>,
26 29 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
30 changelog: LazyCell<Changelog, RevlogError>,
31 manifestlog: LazyCell<Manifestlog, RevlogError>,
27 32 }
28 33
29 34 #[derive(Debug, derive_more::From)]
30 35 pub enum RepoError {
31 36 NotFound {
32 37 at: PathBuf,
33 38 },
34 39 #[from]
35 40 ConfigParseError(ConfigParseError),
36 41 #[from]
37 42 Other(HgError),
38 43 }
39 44
40 45 impl From<ConfigError> for RepoError {
41 46 fn from(error: ConfigError) -> Self {
42 47 match error {
43 48 ConfigError::Parse(error) => error.into(),
44 49 ConfigError::Other(error) => error.into(),
45 50 }
46 51 }
47 52 }
48 53
49 54 impl Repo {
50 55 /// tries to find nearest repository root in current working directory or
51 56 /// its ancestors
52 57 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
53 58 let current_directory = crate::utils::current_dir()?;
54 59 // ancestors() is inclusive: it first yields `current_directory`
55 60 // as-is.
56 61 for ancestor in current_directory.ancestors() {
57 62 if is_dir(ancestor.join(".hg"))? {
58 63 return Ok(ancestor.to_path_buf());
59 64 }
60 65 }
61 66 return Err(RepoError::NotFound {
62 67 at: current_directory,
63 68 });
64 69 }
65 70
66 71 /// Find a repository, either at the given path (which must contain a `.hg`
67 72 /// sub-directory) or by searching the current directory and its
68 73 /// ancestors.
69 74 ///
70 75 /// A method with two very different "modes" like this usually a code smell
71 76 /// to make two methods instead, but in this case an `Option` is what rhg
72 77 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
73 78 /// Having two methods would just move that `if` to almost all callers.
74 79 pub fn find(
75 80 config: &Config,
76 81 explicit_path: Option<PathBuf>,
77 82 ) -> Result<Self, RepoError> {
78 83 if let Some(root) = explicit_path {
79 84 if is_dir(root.join(".hg"))? {
80 85 Self::new_at_path(root.to_owned(), config)
81 86 } else if is_file(&root)? {
82 87 Err(HgError::unsupported("bundle repository").into())
83 88 } else {
84 89 Err(RepoError::NotFound {
85 90 at: root.to_owned(),
86 91 })
87 92 }
88 93 } else {
89 94 let root = Self::find_repo_root()?;
90 95 Self::new_at_path(root, config)
91 96 }
92 97 }
93 98
94 99 /// To be called after checking that `.hg` is a sub-directory
95 100 fn new_at_path(
96 101 working_directory: PathBuf,
97 102 config: &Config,
98 103 ) -> Result<Self, RepoError> {
99 104 let dot_hg = working_directory.join(".hg");
100 105
101 106 let mut repo_config_files = Vec::new();
102 107 repo_config_files.push(dot_hg.join("hgrc"));
103 108 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
104 109
105 110 let hg_vfs = Vfs { base: &dot_hg };
106 111 let mut reqs = requirements::load_if_exists(hg_vfs)?;
107 112 let relative =
108 113 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
109 114 let shared =
110 115 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
111 116
112 117 // From `mercurial/localrepo.py`:
113 118 //
114 119 // if .hg/requires contains the sharesafe requirement, it means
115 120 // there exists a `.hg/store/requires` too and we should read it
116 121 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
117 122 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
118 123 // is not present, refer checkrequirementscompat() for that
119 124 //
120 125 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
121 126 // repository was shared the old way. We check the share source
122 127 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
123 128 // current repository needs to be reshared
124 129 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
125 130
126 131 let store_path;
127 132 if !shared {
128 133 store_path = dot_hg.join("store");
129 134 } else {
130 135 let bytes = hg_vfs.read("sharedpath")?;
131 136 let mut shared_path =
132 137 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
133 138 .to_owned();
134 139 if relative {
135 140 shared_path = dot_hg.join(shared_path)
136 141 }
137 142 if !is_dir(&shared_path)? {
138 143 return Err(HgError::corrupted(format!(
139 144 ".hg/sharedpath points to nonexistent directory {}",
140 145 shared_path.display()
141 146 ))
142 147 .into());
143 148 }
144 149
145 150 store_path = shared_path.join("store");
146 151
147 152 let source_is_share_safe =
148 153 requirements::load(Vfs { base: &shared_path })?
149 154 .contains(requirements::SHARESAFE_REQUIREMENT);
150 155
151 156 if share_safe && !source_is_share_safe {
152 157 return Err(match config
153 158 .get(b"share", b"safe-mismatch.source-not-safe")
154 159 {
155 160 Some(b"abort") | None => HgError::abort(
156 161 "abort: share source does not support share-safe requirement\n\
157 162 (see `hg help config.format.use-share-safe` for more information)",
158 163 exit_codes::ABORT,
159 164 ),
160 165 _ => HgError::unsupported("share-safe downgrade"),
161 166 }
162 167 .into());
163 168 } else if source_is_share_safe && !share_safe {
164 169 return Err(
165 170 match config.get(b"share", b"safe-mismatch.source-safe") {
166 171 Some(b"abort") | None => HgError::abort(
167 172 "abort: version mismatch: source uses share-safe \
168 173 functionality while the current share does not\n\
169 174 (see `hg help config.format.use-share-safe` for more information)",
170 175 exit_codes::ABORT,
171 176 ),
172 177 _ => HgError::unsupported("share-safe upgrade"),
173 178 }
174 179 .into(),
175 180 );
176 181 }
177 182
178 183 if share_safe {
179 184 repo_config_files.insert(0, shared_path.join("hgrc"))
180 185 }
181 186 }
182 187 if share_safe {
183 188 reqs.extend(requirements::load(Vfs { base: &store_path })?);
184 189 }
185 190
186 191 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
187 192 config.combine_with_repo(&repo_config_files)?
188 193 } else {
189 194 config.clone()
190 195 };
191 196
192 197 let repo = Self {
193 198 requirements: reqs,
194 199 working_directory,
195 200 store: store_path,
196 201 dot_hg,
197 202 config: repo_config,
198 203 dirstate_parents: Cell::new(None),
199 204 dirstate_map: LazyCell::new(Self::new_dirstate_map),
205 changelog: LazyCell::new(Changelog::open),
206 manifestlog: LazyCell::new(Manifestlog::open),
200 207 };
201 208
202 209 requirements::check(&repo)?;
203 210
204 211 Ok(repo)
205 212 }
206 213
207 214 pub fn working_directory_path(&self) -> &Path {
208 215 &self.working_directory
209 216 }
210 217
211 218 pub fn requirements(&self) -> &HashSet<String> {
212 219 &self.requirements
213 220 }
214 221
215 222 pub fn config(&self) -> &Config {
216 223 &self.config
217 224 }
218 225
219 226 /// For accessing repository files (in `.hg`), except for the store
220 227 /// (`.hg/store`).
221 228 pub fn hg_vfs(&self) -> Vfs<'_> {
222 229 Vfs { base: &self.dot_hg }
223 230 }
224 231
225 232 /// For accessing repository store files (in `.hg/store`)
226 233 pub fn store_vfs(&self) -> Vfs<'_> {
227 234 Vfs { base: &self.store }
228 235 }
229 236
230 237 /// For accessing the working copy
231 238 pub fn working_directory_vfs(&self) -> Vfs<'_> {
232 239 Vfs {
233 240 base: &self.working_directory,
234 241 }
235 242 }
236 243
237 244 pub fn has_dirstate_v2(&self) -> bool {
238 245 self.requirements
239 246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
240 247 }
241 248
242 249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
243 250 Ok(self
244 251 .hg_vfs()
245 252 .read("dirstate")
246 253 .io_not_found_as_none()?
247 254 .unwrap_or(Vec::new()))
248 255 }
249 256
250 257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
251 258 if let Some(parents) = self.dirstate_parents.get() {
252 259 return Ok(parents);
253 260 }
254 261 let dirstate = self.dirstate_file_contents()?;
255 262 let parents = if dirstate.is_empty() {
256 263 DirstateParents::NULL
257 264 } else if self.has_dirstate_v2() {
258 265 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
259 266 } else {
260 267 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
261 268 .clone()
262 269 };
263 270 self.dirstate_parents.set(Some(parents));
264 271 Ok(parents)
265 272 }
266 273
267 274 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
268 275 let dirstate_file_contents = self.dirstate_file_contents()?;
269 276 if dirstate_file_contents.is_empty() {
270 277 self.dirstate_parents.set(Some(DirstateParents::NULL));
271 278 Ok(OwningDirstateMap::new_empty(Vec::new()))
272 279 } else if self.has_dirstate_v2() {
273 280 let docket = crate::dirstate_tree::on_disk::read_docket(
274 281 &dirstate_file_contents,
275 282 )?;
276 283 self.dirstate_parents.set(Some(docket.parents()));
277 284 let data_size = docket.data_size();
278 285 let metadata = docket.tree_metadata();
279 286 let mut map = if let Some(data_mmap) = self
280 287 .hg_vfs()
281 288 .mmap_open(docket.data_filename())
282 289 .io_not_found_as_none()?
283 290 {
284 291 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
285 292 } else {
286 293 OwningDirstateMap::new_empty(Vec::new())
287 294 };
288 295 let (on_disk, placeholder) = map.get_mut_pair();
289 296 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
290 297 Ok(map)
291 298 } else {
292 299 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
293 300 let (on_disk, placeholder) = map.get_mut_pair();
294 301 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
295 302 self.dirstate_parents
296 303 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
297 304 *placeholder = inner;
298 305 Ok(map)
299 306 }
300 307 }
301 308
302 309 pub fn dirstate_map(
303 310 &self,
304 311 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
305 312 self.dirstate_map.get_or_init(self)
306 313 }
307 314
308 315 pub fn dirstate_map_mut(
309 316 &self,
310 317 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
311 318 self.dirstate_map.get_mut_or_init(self)
312 319 }
320
321 pub fn changelog(&self) -> Result<Ref<Changelog>, RevlogError> {
322 self.changelog.get_or_init(self)
323 }
324
325 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, RevlogError> {
326 self.changelog.get_mut_or_init(self)
327 }
328
329 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, RevlogError> {
330 self.manifestlog.get_or_init(self)
331 }
332
333 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, RevlogError> {
334 self.manifestlog.get_mut_or_init(self)
335 }
313 336 }
314 337
315 338 /// Lazily-initialized component of `Repo` with interior mutability
316 339 ///
317 340 /// This differs from `OnceCell` in that the value can still be "deinitialized"
318 341 /// later by setting its inner `Option` to `None`.
319 342 struct LazyCell<T, E> {
320 343 value: RefCell<Option<T>>,
321 344 // `Fn`s that don’t capture environment are zero-size, so this box does
322 345 // not allocate:
323 346 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
324 347 }
325 348
326 349 impl<T, E> LazyCell<T, E> {
327 350 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
328 351 Self {
329 352 value: RefCell::new(None),
330 353 init: Box::new(init),
331 354 }
332 355 }
333 356
334 357 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
335 358 let mut borrowed = self.value.borrow();
336 359 if borrowed.is_none() {
337 360 drop(borrowed);
338 361 // Only use `borrow_mut` if it is really needed to avoid panic in
339 362 // case there is another outstanding borrow but mutation is not
340 363 // needed.
341 364 *self.value.borrow_mut() = Some((self.init)(repo)?);
342 365 borrowed = self.value.borrow()
343 366 }
344 367 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
345 368 }
346 369
347 370 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
348 371 let mut borrowed = self.value.borrow_mut();
349 372 if borrowed.is_none() {
350 373 *borrowed = Some((self.init)(repo)?);
351 374 }
352 375 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
353 376 }
354 377 }
355 378
356 379 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
357 380 struct MmapWrapper(memmap2::Mmap);
358 381
359 382 impl std::ops::Deref for MmapWrapper {
360 383 type Target = [u8];
361 384
362 385 fn deref(&self) -> &[u8] {
363 386 self.0.deref()
364 387 }
365 388 }
366 389
367 390 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
@@ -1,62 +1,61 b''
1 1 //! The revset query language
2 2 //!
3 3 //! <https://www.mercurial-scm.org/repo/hg/help/revsets>
4 4
5 5 use crate::errors::HgError;
6 6 use crate::repo::Repo;
7 use crate::revlog::changelog::Changelog;
8 7 use crate::revlog::revlog::{Revlog, RevlogError};
9 8 use crate::revlog::NodePrefix;
10 9 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
11 10 use crate::Node;
12 11
13 12 /// Resolve a query string into a single revision.
14 13 ///
15 14 /// Only some of the revset language is implemented yet.
16 15 pub fn resolve_single(
17 16 input: &str,
18 17 repo: &Repo,
19 18 ) -> Result<Revision, RevlogError> {
20 let changelog = Changelog::open(repo)?;
19 let changelog = repo.changelog()?;
21 20
22 21 match resolve_rev_number_or_hex_prefix(input, &changelog.revlog) {
23 22 Err(RevlogError::InvalidRevision) => {} // Try other syntax
24 23 result => return result,
25 24 }
26 25
27 26 if input == "null" {
28 27 return Ok(NULL_REVISION);
29 28 }
30 29
31 30 // TODO: support for the rest of the language here.
32 31
33 32 Err(
34 33 HgError::unsupported(format!("cannot parse revset '{}'", input))
35 34 .into(),
36 35 )
37 36 }
38 37
39 38 /// Resolve the small subset of the language suitable for revlogs other than
40 39 /// the changelog, such as in `hg debugdata --manifest` CLI argument.
41 40 ///
42 41 /// * A non-negative decimal integer for a revision number, or
43 42 /// * An hexadecimal string, for the unique node ID that starts with this
44 43 /// prefix
45 44 pub fn resolve_rev_number_or_hex_prefix(
46 45 input: &str,
47 46 revlog: &Revlog,
48 47 ) -> Result<Revision, RevlogError> {
49 48 if let Ok(integer) = input.parse::<i32>() {
50 49 if integer >= 0 && revlog.has_rev(integer) {
51 50 return Ok(integer);
52 51 }
53 52 }
54 53 if let Ok(prefix) = NodePrefix::from_hex(input) {
55 54 if prefix.is_prefix_of(&Node::from_hex(WORKING_DIRECTORY_HEX).unwrap())
56 55 {
57 56 return Err(RevlogError::WDirUnsupported);
58 57 }
59 58 return revlog.get_node_rev(prefix);
60 59 }
61 60 Err(RevlogError::InvalidRevision)
62 61 }
General Comments 0
You need to be logged in to leave comments. Login now