1 use crate::config::Config; |
1 use crate::config::{Config, ConfigError, ConfigParseError}; |
2 use crate::errors::{HgError, IoResultExt}; |
2 use crate::errors::{HgError, 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 memmap::{Mmap, MmapOptions}; |
5 use memmap::{Mmap, MmapOptions}; |
6 use std::collections::HashSet; |
6 use std::collections::HashSet; |
10 pub struct Repo { |
10 pub struct Repo { |
11 working_directory: PathBuf, |
11 working_directory: PathBuf, |
12 dot_hg: PathBuf, |
12 dot_hg: PathBuf, |
13 store: PathBuf, |
13 store: PathBuf, |
14 requirements: HashSet<String>, |
14 requirements: HashSet<String>, |
|
15 config: Config, |
15 } |
16 } |
16 |
17 |
17 #[derive(Debug, derive_more::From)] |
18 #[derive(Debug, derive_more::From)] |
18 pub enum RepoFindError { |
19 pub enum RepoError { |
19 NotFoundInCurrentDirectoryOrAncestors { |
20 NotFound { |
20 current_directory: PathBuf, |
21 current_directory: PathBuf, |
21 }, |
22 }, |
22 #[from] |
23 #[from] |
|
24 ConfigParseError(ConfigParseError), |
|
25 #[from] |
23 Other(HgError), |
26 Other(HgError), |
|
27 } |
|
28 |
|
29 impl From<ConfigError> for RepoError { |
|
30 fn from(error: ConfigError) -> Self { |
|
31 match error { |
|
32 ConfigError::Parse(error) => error.into(), |
|
33 ConfigError::Other(error) => error.into(), |
|
34 } |
|
35 } |
24 } |
36 } |
25 |
37 |
26 /// Filesystem access abstraction for the contents of a given "base" diretory |
38 /// Filesystem access abstraction for the contents of a given "base" diretory |
27 #[derive(Clone, Copy)] |
39 #[derive(Clone, Copy)] |
28 pub(crate) struct Vfs<'a> { |
40 pub(crate) struct Vfs<'a> { |
30 } |
42 } |
31 |
43 |
32 impl Repo { |
44 impl Repo { |
33 /// Search the current directory and its ancestores for a repository: |
45 /// Search the current directory and its ancestores for a repository: |
34 /// a working directory that contains a `.hg` sub-directory. |
46 /// a working directory that contains a `.hg` sub-directory. |
35 pub fn find(config: &Config) -> Result<Self, RepoFindError> { |
47 pub fn find(config: &Config) -> Result<Self, RepoError> { |
36 let current_directory = crate::utils::current_dir()?; |
48 let current_directory = crate::utils::current_dir()?; |
37 // ancestors() is inclusive: it first yields `current_directory` as-is. |
49 // ancestors() is inclusive: it first yields `current_directory` as-is. |
38 for ancestor in current_directory.ancestors() { |
50 for ancestor in current_directory.ancestors() { |
39 if ancestor.join(".hg").is_dir() { |
51 if ancestor.join(".hg").is_dir() { |
40 return Ok(Self::new_at_path(ancestor.to_owned(), config)?); |
52 return Ok(Self::new_at_path(ancestor.to_owned(), config)?); |
41 } |
53 } |
42 } |
54 } |
43 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors { |
55 Err(RepoError::NotFound { current_directory }) |
44 current_directory, |
|
45 }) |
|
46 } |
56 } |
47 |
57 |
48 /// To be called after checking that `.hg` is a sub-directory |
58 /// To be called after checking that `.hg` is a sub-directory |
49 fn new_at_path( |
59 fn new_at_path( |
50 working_directory: PathBuf, |
60 working_directory: PathBuf, |
51 config: &Config, |
61 config: &Config, |
52 ) -> Result<Self, HgError> { |
62 ) -> Result<Self, RepoError> { |
53 let dot_hg = working_directory.join(".hg"); |
63 let dot_hg = working_directory.join(".hg"); |
|
64 |
|
65 let mut repo_config_files = Vec::new(); |
|
66 repo_config_files.push(dot_hg.join("hgrc")); |
|
67 repo_config_files.push(dot_hg.join("hgrc-not-shared")); |
54 |
68 |
55 let hg_vfs = Vfs { base: &dot_hg }; |
69 let hg_vfs = Vfs { base: &dot_hg }; |
56 let mut reqs = requirements::load_if_exists(hg_vfs)?; |
70 let mut reqs = requirements::load_if_exists(hg_vfs)?; |
57 let relative = |
71 let relative = |
58 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); |
72 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); |
87 } |
101 } |
88 if !shared_path.is_dir() { |
102 if !shared_path.is_dir() { |
89 return Err(HgError::corrupted(format!( |
103 return Err(HgError::corrupted(format!( |
90 ".hg/sharedpath points to nonexistent directory {}", |
104 ".hg/sharedpath points to nonexistent directory {}", |
91 shared_path.display() |
105 shared_path.display() |
92 ))); |
106 )) |
|
107 .into()); |
93 } |
108 } |
94 |
109 |
95 store_path = shared_path.join("store"); |
110 store_path = shared_path.join("store"); |
96 |
111 |
97 let source_is_share_safe = |
112 let source_is_share_safe = |
98 requirements::load(Vfs { base: &shared_path })? |
113 requirements::load(Vfs { base: &shared_path })? |
99 .contains(requirements::SHARESAFE_REQUIREMENT); |
114 .contains(requirements::SHARESAFE_REQUIREMENT); |
100 |
115 |
101 if share_safe && !source_is_share_safe { |
116 if share_safe && !source_is_share_safe { |
102 return Err(match config.get(b"safe-mismatch", b"source-not-safe") { |
117 return Err(match config |
|
118 .get(b"safe-mismatch", b"source-not-safe") |
|
119 { |
103 Some(b"abort") | None => HgError::abort( |
120 Some(b"abort") | None => HgError::abort( |
104 "share source does not support share-safe requirement" |
121 "share source does not support share-safe requirement", |
105 ), |
122 ), |
106 _ => HgError::unsupported("share-safe downgrade") |
123 _ => HgError::unsupported("share-safe downgrade"), |
107 }); |
124 } |
|
125 .into()); |
108 } else if source_is_share_safe && !share_safe { |
126 } else if source_is_share_safe && !share_safe { |
109 return Err( |
127 return Err( |
110 match config.get(b"safe-mismatch", b"source-safe") { |
128 match config.get(b"safe-mismatch", b"source-safe") { |
111 Some(b"abort") | None => HgError::abort( |
129 Some(b"abort") | None => HgError::abort( |
112 "version mismatch: source uses share-safe \ |
130 "version mismatch: source uses share-safe \ |
113 functionality while the current share does not", |
131 functionality while the current share does not", |
114 ), |
132 ), |
115 _ => HgError::unsupported("share-safe upgrade"), |
133 _ => HgError::unsupported("share-safe upgrade"), |
116 }, |
134 } |
|
135 .into(), |
117 ); |
136 ); |
118 } |
137 } |
119 } |
138 |
|
139 if share_safe { |
|
140 repo_config_files.insert(0, shared_path.join("hgrc")) |
|
141 } |
|
142 } |
|
143 |
|
144 let repo_config = config.combine_with_repo(&repo_config_files)?; |
120 |
145 |
121 let repo = Self { |
146 let repo = Self { |
122 requirements: reqs, |
147 requirements: reqs, |
123 working_directory, |
148 working_directory, |
124 store: store_path, |
149 store: store_path, |
125 dot_hg, |
150 dot_hg, |
|
151 config: repo_config, |
126 }; |
152 }; |
127 |
153 |
128 requirements::check(&repo)?; |
154 requirements::check(&repo)?; |
129 |
155 |
130 Ok(repo) |
156 Ok(repo) |
134 &self.working_directory |
160 &self.working_directory |
135 } |
161 } |
136 |
162 |
137 pub fn requirements(&self) -> &HashSet<String> { |
163 pub fn requirements(&self) -> &HashSet<String> { |
138 &self.requirements |
164 &self.requirements |
|
165 } |
|
166 |
|
167 pub fn config(&self) -> &Config { |
|
168 &self.config |
139 } |
169 } |
140 |
170 |
141 /// For accessing repository files (in `.hg`), except for the store |
171 /// For accessing repository files (in `.hg`), except for the store |
142 /// (`.hg/store`). |
172 /// (`.hg/store`). |
143 pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
173 pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |