Show More
@@ -40,6 +40,10 b' impl HgError {' | |||||
40 | // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html |
|
40 | // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html | |
41 | HgError::CorruptedRepository(explanation.into()) |
|
41 | HgError::CorruptedRepository(explanation.into()) | |
42 | } |
|
42 | } | |
|
43 | ||||
|
44 | pub fn unsupported(explanation: impl Into<String>) -> Self { | |||
|
45 | HgError::UnsupportedFeature(explanation.into()) | |||
|
46 | } | |||
43 | } |
|
47 | } | |
44 |
|
48 | |||
45 | // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? |
|
49 | // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? |
@@ -1,6 +1,8 b'' | |||||
1 | use crate::errors::{HgError, IoResultExt}; |
|
1 | use crate::errors::{HgError, IoResultExt}; | |
2 | use crate::requirements; |
|
2 | use crate::requirements; | |
|
3 | use crate::utils::files::get_path_from_bytes; | |||
3 | use memmap::{Mmap, MmapOptions}; |
|
4 | use memmap::{Mmap, MmapOptions}; | |
|
5 | use std::collections::HashSet; | |||
4 | use std::path::{Path, PathBuf}; |
|
6 | use std::path::{Path, PathBuf}; | |
5 |
|
7 | |||
6 | /// A repository on disk |
|
8 | /// A repository on disk | |
@@ -8,6 +10,7 b' pub struct Repo {' | |||||
8 | working_directory: PathBuf, |
|
10 | working_directory: PathBuf, | |
9 | dot_hg: PathBuf, |
|
11 | dot_hg: PathBuf, | |
10 | store: PathBuf, |
|
12 | store: PathBuf, | |
|
13 | requirements: HashSet<String>, | |||
11 | } |
|
14 | } | |
12 |
|
15 | |||
13 | #[derive(Debug, derive_more::From)] |
|
16 | #[derive(Debug, derive_more::From)] | |
@@ -32,15 +35,8 b' impl Repo {' | |||||
32 | let current_directory = crate::utils::current_dir()?; |
|
35 | let current_directory = crate::utils::current_dir()?; | |
33 | // ancestors() is inclusive: it first yields `current_directory` as-is. |
|
36 | // ancestors() is inclusive: it first yields `current_directory` as-is. | |
34 | for ancestor in current_directory.ancestors() { |
|
37 | for ancestor in current_directory.ancestors() { | |
35 |
|
|
38 | if ancestor.join(".hg").is_dir() { | |
36 | if dot_hg.is_dir() { |
|
39 | return Ok(Self::new_at_path(ancestor.to_owned())?); | |
37 | let repo = Self { |
|
|||
38 | store: dot_hg.join("store"), |
|
|||
39 | dot_hg, |
|
|||
40 | working_directory: ancestor.to_owned(), |
|
|||
41 | }; |
|
|||
42 | requirements::check(&repo)?; |
|
|||
43 | return Ok(repo); |
|
|||
44 | } |
|
40 | } | |
45 | } |
|
41 | } | |
46 | Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors { |
|
42 | Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors { | |
@@ -48,10 +44,54 b' impl Repo {' | |||||
48 | }) |
|
44 | }) | |
49 | } |
|
45 | } | |
50 |
|
46 | |||
|
47 | /// To be called after checking that `.hg` is a sub-directory | |||
|
48 | fn new_at_path(working_directory: PathBuf) -> Result<Self, HgError> { | |||
|
49 | let dot_hg = working_directory.join(".hg"); | |||
|
50 | let hg_vfs = Vfs { base: &dot_hg }; | |||
|
51 | let reqs = requirements::load_if_exists(hg_vfs)?; | |||
|
52 | let relative = | |||
|
53 | reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); | |||
|
54 | let shared = | |||
|
55 | reqs.contains(requirements::SHARED_REQUIREMENT) || relative; | |||
|
56 | let store_path; | |||
|
57 | if !shared { | |||
|
58 | store_path = dot_hg.join("store"); | |||
|
59 | } else { | |||
|
60 | let bytes = hg_vfs.read("sharedpath")?; | |||
|
61 | let mut shared_path = get_path_from_bytes(&bytes).to_owned(); | |||
|
62 | if relative { | |||
|
63 | shared_path = dot_hg.join(shared_path) | |||
|
64 | } | |||
|
65 | if !shared_path.is_dir() { | |||
|
66 | return Err(HgError::corrupted(format!( | |||
|
67 | ".hg/sharedpath points to nonexistent directory {}", | |||
|
68 | shared_path.display() | |||
|
69 | ))); | |||
|
70 | } | |||
|
71 | ||||
|
72 | store_path = shared_path.join("store"); | |||
|
73 | } | |||
|
74 | ||||
|
75 | let repo = Self { | |||
|
76 | requirements: reqs, | |||
|
77 | working_directory, | |||
|
78 | store: store_path, | |||
|
79 | dot_hg, | |||
|
80 | }; | |||
|
81 | ||||
|
82 | requirements::check(&repo)?; | |||
|
83 | ||||
|
84 | Ok(repo) | |||
|
85 | } | |||
|
86 | ||||
51 | pub fn working_directory_path(&self) -> &Path { |
|
87 | pub fn working_directory_path(&self) -> &Path { | |
52 | &self.working_directory |
|
88 | &self.working_directory | |
53 | } |
|
89 | } | |
54 |
|
90 | |||
|
91 | pub fn requirements(&self) -> &HashSet<String> { | |||
|
92 | &self.requirements | |||
|
93 | } | |||
|
94 | ||||
55 | /// For accessing repository files (in `.hg`), except for the store |
|
95 | /// For accessing repository files (in `.hg`), except for the store | |
56 | /// (`.hg/store`). |
|
96 | /// (`.hg/store`). | |
57 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
|
97 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
@@ -1,7 +1,8 b'' | |||||
1 | use crate::errors::{HgError, HgResultExt}; |
|
1 | use crate::errors::{HgError, HgResultExt}; | |
2 | use crate::repo::Repo; |
|
2 | use crate::repo::{Repo, Vfs}; | |
|
3 | use std::collections::HashSet; | |||
3 |
|
4 | |||
4 |
fn parse(bytes: &[u8]) -> Result< |
|
5 | fn parse(bytes: &[u8]) -> Result<HashSet<String>, HgError> { | |
5 | // The Python code reading this file uses `str.splitlines` |
|
6 | // The Python code reading this file uses `str.splitlines` | |
6 | // which looks for a number of line separators (even including a couple of |
|
7 | // which looks for a number of line separators (even including a couple of | |
7 | // non-ASCII ones), but Python code writing it always uses `\n`. |
|
8 | // non-ASCII ones), but Python code writing it always uses `\n`. | |
@@ -21,10 +22,8 b' fn parse(bytes: &[u8]) -> Result<Vec<Str' | |||||
21 | .collect() |
|
22 | .collect() | |
22 | } |
|
23 | } | |
23 |
|
24 | |||
24 |
pub fn load( |
|
25 | pub(crate) fn load_if_exists(hg_vfs: Vfs) -> Result<HashSet<String>, HgError> { | |
25 | if let Some(bytes) = |
|
26 | if let Some(bytes) = hg_vfs.read("requires").io_not_found_as_none()? { | |
26 | repo.hg_vfs().read("requires").io_not_found_as_none()? |
|
|||
27 | { |
|
|||
28 | parse(&bytes) |
|
27 | parse(&bytes) | |
29 | } else { |
|
28 | } else { | |
30 | // Treat a missing file the same as an empty file. |
|
29 | // Treat a missing file the same as an empty file. | |
@@ -34,13 +33,13 b' pub fn load(repo: &Repo) -> Result<Vec<S' | |||||
34 | // > the repository. This file was introduced in Mercurial 0.9.2, |
|
33 | // > the repository. This file was introduced in Mercurial 0.9.2, | |
35 | // > which means very old repositories may not have one. We assume |
|
34 | // > which means very old repositories may not have one. We assume | |
36 | // > a missing file translates to no requirements. |
|
35 | // > a missing file translates to no requirements. | |
37 |
Ok( |
|
36 | Ok(HashSet::new()) | |
38 | } |
|
37 | } | |
39 | } |
|
38 | } | |
40 |
|
39 | |||
41 | pub fn check(repo: &Repo) -> Result<(), HgError> { |
|
40 | pub(crate) fn check(repo: &Repo) -> Result<(), HgError> { | |
42 |
for feature in |
|
41 | for feature in repo.requirements() { | |
43 |
if !SUPPORTED.contains(& |
|
42 | if !SUPPORTED.contains(&feature.as_str()) { | |
44 | // TODO: collect and all unknown features and include them in the |
|
43 | // TODO: collect and all unknown features and include them in the | |
45 | // error message? |
|
44 | // error message? | |
46 | return Err(HgError::UnsupportedFeature(format!( |
|
45 | return Err(HgError::UnsupportedFeature(format!( | |
@@ -58,10 +57,77 b' const SUPPORTED: &[&str] = &[' | |||||
58 | "fncache", |
|
57 | "fncache", | |
59 | "generaldelta", |
|
58 | "generaldelta", | |
60 | "revlogv1", |
|
59 | "revlogv1", | |
61 | "sparserevlog", |
|
60 | SHARED_REQUIREMENT, | |
|
61 | SPARSEREVLOG_REQUIREMENT, | |||
|
62 | RELATIVE_SHARED_REQUIREMENT, | |||
62 | "store", |
|
63 | "store", | |
63 | // As of this writing everything rhg does is read-only. |
|
64 | // As of this writing everything rhg does is read-only. | |
64 | // When it starts writing to the repository, it’ll need to either keep the |
|
65 | // When it starts writing to the repository, it’ll need to either keep the | |
65 | // persistent nodemap up to date or remove this entry: |
|
66 | // persistent nodemap up to date or remove this entry: | |
66 | "persistent-nodemap", |
|
67 | "persistent-nodemap", | |
67 | ]; |
|
68 | ]; | |
|
69 | ||||
|
70 | // Copied from mercurial/requirements.py: | |||
|
71 | ||||
|
72 | /// When narrowing is finalized and no longer subject to format changes, | |||
|
73 | /// we should move this to just "narrow" or similar. | |||
|
74 | #[allow(unused)] | |||
|
75 | pub(crate) const NARROW_REQUIREMENT: &str = "narrowhg-experimental"; | |||
|
76 | ||||
|
77 | /// Enables sparse working directory usage | |||
|
78 | #[allow(unused)] | |||
|
79 | pub(crate) const SPARSE_REQUIREMENT: &str = "exp-sparse"; | |||
|
80 | ||||
|
81 | /// Enables the internal phase which is used to hide changesets instead | |||
|
82 | /// of stripping them | |||
|
83 | #[allow(unused)] | |||
|
84 | pub(crate) const INTERNAL_PHASE_REQUIREMENT: &str = "internal-phase"; | |||
|
85 | ||||
|
86 | /// Stores manifest in Tree structure | |||
|
87 | #[allow(unused)] | |||
|
88 | pub(crate) const TREEMANIFEST_REQUIREMENT: &str = "treemanifest"; | |||
|
89 | ||||
|
90 | /// Increment the sub-version when the revlog v2 format changes to lock out old | |||
|
91 | /// clients. | |||
|
92 | #[allow(unused)] | |||
|
93 | pub(crate) const REVLOGV2_REQUIREMENT: &str = "exp-revlogv2.1"; | |||
|
94 | ||||
|
95 | /// A repository with the sparserevlog feature will have delta chains that | |||
|
96 | /// can spread over a larger span. Sparse reading cuts these large spans into | |||
|
97 | /// pieces, so that each piece isn't too big. | |||
|
98 | /// Without the sparserevlog capability, reading from the repository could use | |||
|
99 | /// huge amounts of memory, because the whole span would be read at once, | |||
|
100 | /// including all the intermediate revisions that aren't pertinent for the | |||
|
101 | /// chain. This is why once a repository has enabled sparse-read, it becomes | |||
|
102 | /// required. | |||
|
103 | #[allow(unused)] | |||
|
104 | pub(crate) const SPARSEREVLOG_REQUIREMENT: &str = "sparserevlog"; | |||
|
105 | ||||
|
106 | /// A repository with the sidedataflag requirement will allow to store extra | |||
|
107 | /// information for revision without altering their original hashes. | |||
|
108 | #[allow(unused)] | |||
|
109 | pub(crate) const SIDEDATA_REQUIREMENT: &str = "exp-sidedata-flag"; | |||
|
110 | ||||
|
111 | /// A repository with the the copies-sidedata-changeset requirement will store | |||
|
112 | /// copies related information in changeset's sidedata. | |||
|
113 | #[allow(unused)] | |||
|
114 | pub(crate) const COPIESSDC_REQUIREMENT: &str = "exp-copies-sidedata-changeset"; | |||
|
115 | ||||
|
116 | /// The repository use persistent nodemap for the changelog and the manifest. | |||
|
117 | #[allow(unused)] | |||
|
118 | pub(crate) const NODEMAP_REQUIREMENT: &str = "persistent-nodemap"; | |||
|
119 | ||||
|
120 | /// Denotes that the current repository is a share | |||
|
121 | #[allow(unused)] | |||
|
122 | pub(crate) const SHARED_REQUIREMENT: &str = "shared"; | |||
|
123 | ||||
|
124 | /// Denotes that current repository is a share and the shared source path is | |||
|
125 | /// relative to the current repository root path | |||
|
126 | #[allow(unused)] | |||
|
127 | pub(crate) const RELATIVE_SHARED_REQUIREMENT: &str = "relshared"; | |||
|
128 | ||||
|
129 | /// A repository with share implemented safely. The repository has different | |||
|
130 | /// store and working copy requirements i.e. both `.hg/requires` and | |||
|
131 | /// `.hg/store/requires` are present. | |||
|
132 | #[allow(unused)] | |||
|
133 | pub(crate) const SHARESAFE_REQUIREMENT: &str = "exp-sharesafe"; |
@@ -2,7 +2,6 b' use crate::commands::Command;' | |||||
2 | use crate::error::CommandError; |
|
2 | use crate::error::CommandError; | |
3 | use crate::ui::Ui; |
|
3 | use crate::ui::Ui; | |
4 | use hg::repo::Repo; |
|
4 | use hg::repo::Repo; | |
5 | use hg::requirements; |
|
|||
6 |
|
5 | |||
7 | pub const HELP_TEXT: &str = " |
|
6 | pub const HELP_TEXT: &str = " | |
8 | Print the current repo requirements. |
|
7 | Print the current repo requirements. | |
@@ -20,8 +19,10 b' impl Command for DebugRequirementsComman' | |||||
20 | fn run(&self, ui: &Ui) -> Result<(), CommandError> { |
|
19 | fn run(&self, ui: &Ui) -> Result<(), CommandError> { | |
21 | let repo = Repo::find()?; |
|
20 | let repo = Repo::find()?; | |
22 | let mut output = String::new(); |
|
21 | let mut output = String::new(); | |
23 | for req in requirements::load(&repo)? { |
|
22 | let mut requirements: Vec<_> = repo.requirements().iter().collect(); | |
24 | output.push_str(&req); |
|
23 | requirements.sort(); | |
|
24 | for req in requirements { | |||
|
25 | output.push_str(req); | |||
25 | output.push('\n'); |
|
26 | output.push('\n'); | |
26 | } |
|
27 | } | |
27 | ui.write_stdout(output.as_bytes())?; |
|
28 | ui.write_stdout(output.as_bytes())?; |
@@ -218,9 +218,9 b' And check that basic rhg commands work w' | |||||
218 |
|
218 | |||
219 | $ cd repo2 |
|
219 | $ cd repo2 | |
220 | $ rhg files |
|
220 | $ rhg files | |
221 | [252] |
|
221 | a | |
222 | $ rhg cat -r 0 a |
|
222 | $ rhg cat -r 0 a | |
223 | [252] |
|
223 | a | |
224 |
|
224 | |||
225 | Same with relative sharing |
|
225 | Same with relative sharing | |
226 |
|
226 | |||
@@ -231,9 +231,9 b' Same with relative sharing' | |||||
231 |
|
231 | |||
232 | $ cd repo3 |
|
232 | $ cd repo3 | |
233 | $ rhg files |
|
233 | $ rhg files | |
234 | [252] |
|
234 | a | |
235 | $ rhg cat -r 0 a |
|
235 | $ rhg cat -r 0 a | |
236 | [252] |
|
236 | a | |
237 |
|
237 | |||
238 | Same with share-safe |
|
238 | Same with share-safe | |
239 |
|
239 |
General Comments 0
You need to be logged in to leave comments.
Login now