##// END OF EJS Templates
rust: Introduce a get_bytes_from_os_str utility function...
Simon Sapin -
r47338:d2e61f00 default
parent child Browse files
Show More
@@ -1,344 +1,343 b''
1 // config.rs
1 // config.rs
2 //
2 //
3 // Copyright 2020
3 // Copyright 2020
4 // Valentin Gatien-Baron,
4 // Valentin Gatien-Baron,
5 // Raphaël Gomès <rgomes@octobus.net>
5 // Raphaël Gomès <rgomes@octobus.net>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 use super::layer;
10 use super::layer;
11 use crate::config::layer::{
11 use crate::config::layer::{
12 ConfigError, ConfigLayer, ConfigParseError, ConfigValue,
12 ConfigError, ConfigLayer, ConfigParseError, ConfigValue,
13 };
13 };
14 use crate::utils::files::get_bytes_from_path;
14 use crate::utils::files::get_bytes_from_os_str;
15 use format_bytes::{write_bytes, DisplayBytes};
15 use format_bytes::{write_bytes, DisplayBytes};
16 use std::env;
16 use std::env;
17 use std::path::{Path, PathBuf};
17 use std::path::{Path, PathBuf};
18
18
19 use crate::errors::{HgResultExt, IoResultExt};
19 use crate::errors::{HgResultExt, IoResultExt};
20
20
21 /// Holds the config values for the current repository
21 /// Holds the config values for the current repository
22 /// TODO update this docstring once we support more sources
22 /// TODO update this docstring once we support more sources
23 pub struct Config {
23 pub struct Config {
24 layers: Vec<layer::ConfigLayer>,
24 layers: Vec<layer::ConfigLayer>,
25 }
25 }
26
26
27 impl DisplayBytes for Config {
27 impl DisplayBytes for Config {
28 fn display_bytes(
28 fn display_bytes(
29 &self,
29 &self,
30 out: &mut dyn std::io::Write,
30 out: &mut dyn std::io::Write,
31 ) -> std::io::Result<()> {
31 ) -> std::io::Result<()> {
32 for (index, layer) in self.layers.iter().rev().enumerate() {
32 for (index, layer) in self.layers.iter().rev().enumerate() {
33 write_bytes!(
33 write_bytes!(
34 out,
34 out,
35 b"==== Layer {} (trusted: {}) ====\n{}",
35 b"==== Layer {} (trusted: {}) ====\n{}",
36 index,
36 index,
37 if layer.trusted {
37 if layer.trusted {
38 &b"yes"[..]
38 &b"yes"[..]
39 } else {
39 } else {
40 &b"no"[..]
40 &b"no"[..]
41 },
41 },
42 layer
42 layer
43 )?;
43 )?;
44 }
44 }
45 Ok(())
45 Ok(())
46 }
46 }
47 }
47 }
48
48
49 pub enum ConfigSource {
49 pub enum ConfigSource {
50 /// Absolute path to a config file
50 /// Absolute path to a config file
51 AbsPath(PathBuf),
51 AbsPath(PathBuf),
52 /// Already parsed (from the CLI, env, Python resources, etc.)
52 /// Already parsed (from the CLI, env, Python resources, etc.)
53 Parsed(layer::ConfigLayer),
53 Parsed(layer::ConfigLayer),
54 }
54 }
55
55
56 pub fn parse_bool(v: &[u8]) -> Option<bool> {
56 pub fn parse_bool(v: &[u8]) -> Option<bool> {
57 match v.to_ascii_lowercase().as_slice() {
57 match v.to_ascii_lowercase().as_slice() {
58 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
58 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
59 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
59 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
60 _ => None,
60 _ => None,
61 }
61 }
62 }
62 }
63
63
64 impl Config {
64 impl Config {
65 /// Load system and user configuration from various files.
65 /// Load system and user configuration from various files.
66 ///
66 ///
67 /// This is also affected by some environment variables.
67 /// This is also affected by some environment variables.
68 pub fn load(
68 pub fn load(
69 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
69 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
70 ) -> Result<Self, ConfigError> {
70 ) -> Result<Self, ConfigError> {
71 let mut config = Self { layers: Vec::new() };
71 let mut config = Self { layers: Vec::new() };
72 let opt_rc_path = env::var_os("HGRCPATH");
72 let opt_rc_path = env::var_os("HGRCPATH");
73 // HGRCPATH replaces system config
73 // HGRCPATH replaces system config
74 if opt_rc_path.is_none() {
74 if opt_rc_path.is_none() {
75 config.add_system_config()?
75 config.add_system_config()?
76 }
76 }
77 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
77 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
78 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
78 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
79 config.add_for_environment_variable("PAGER", b"pager", b"pager");
79 config.add_for_environment_variable("PAGER", b"pager", b"pager");
80 // HGRCPATH replaces user config
80 // HGRCPATH replaces user config
81 if opt_rc_path.is_none() {
81 if opt_rc_path.is_none() {
82 config.add_user_config()?
82 config.add_user_config()?
83 }
83 }
84 if let Some(rc_path) = &opt_rc_path {
84 if let Some(rc_path) = &opt_rc_path {
85 for path in env::split_paths(rc_path) {
85 for path in env::split_paths(rc_path) {
86 if !path.as_os_str().is_empty() {
86 if !path.as_os_str().is_empty() {
87 if path.is_dir() {
87 if path.is_dir() {
88 config.add_trusted_dir(&path)?
88 config.add_trusted_dir(&path)?
89 } else {
89 } else {
90 config.add_trusted_file(&path)?
90 config.add_trusted_file(&path)?
91 }
91 }
92 }
92 }
93 }
93 }
94 }
94 }
95 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
95 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
96 config.layers.push(layer)
96 config.layers.push(layer)
97 }
97 }
98 Ok(config)
98 Ok(config)
99 }
99 }
100
100
101 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
101 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
102 if let Some(entries) = std::fs::read_dir(path)
102 if let Some(entries) = std::fs::read_dir(path)
103 .for_file(path)
103 .for_file(path)
104 .io_not_found_as_none()?
104 .io_not_found_as_none()?
105 {
105 {
106 for entry in entries {
106 for entry in entries {
107 let file_path = entry.for_file(path)?.path();
107 let file_path = entry.for_file(path)?.path();
108 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
108 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
109 self.add_trusted_file(&file_path)?
109 self.add_trusted_file(&file_path)?
110 }
110 }
111 }
111 }
112 }
112 }
113 Ok(())
113 Ok(())
114 }
114 }
115
115
116 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
116 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
117 if let Some(data) =
117 if let Some(data) =
118 std::fs::read(path).for_file(path).io_not_found_as_none()?
118 std::fs::read(path).for_file(path).io_not_found_as_none()?
119 {
119 {
120 self.layers.extend(ConfigLayer::parse(path, &data)?)
120 self.layers.extend(ConfigLayer::parse(path, &data)?)
121 }
121 }
122 Ok(())
122 Ok(())
123 }
123 }
124
124
125 fn add_for_environment_variable(
125 fn add_for_environment_variable(
126 &mut self,
126 &mut self,
127 var: &str,
127 var: &str,
128 section: &[u8],
128 section: &[u8],
129 key: &[u8],
129 key: &[u8],
130 ) {
130 ) {
131 if let Some(value) = env::var_os(var) {
131 if let Some(value) = env::var_os(var) {
132 let origin = layer::ConfigOrigin::Environment(var.into());
132 let origin = layer::ConfigOrigin::Environment(var.into());
133 let mut layer = ConfigLayer::new(origin);
133 let mut layer = ConfigLayer::new(origin);
134 layer.add(
134 layer.add(
135 section.to_owned(),
135 section.to_owned(),
136 key.to_owned(),
136 key.to_owned(),
137 // `value` is not a path but this works for any `OsStr`:
137 get_bytes_from_os_str(value),
138 get_bytes_from_path(value),
139 None,
138 None,
140 );
139 );
141 self.layers.push(layer)
140 self.layers.push(layer)
142 }
141 }
143 }
142 }
144
143
145 #[cfg(unix)] // TODO: other platforms
144 #[cfg(unix)] // TODO: other platforms
146 fn add_system_config(&mut self) -> Result<(), ConfigError> {
145 fn add_system_config(&mut self) -> Result<(), ConfigError> {
147 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
146 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
148 let etc = prefix.join("etc").join("mercurial");
147 let etc = prefix.join("etc").join("mercurial");
149 self.add_trusted_file(&etc.join("hgrc"))?;
148 self.add_trusted_file(&etc.join("hgrc"))?;
150 self.add_trusted_dir(&etc.join("hgrc.d"))
149 self.add_trusted_dir(&etc.join("hgrc.d"))
151 };
150 };
152 let root = Path::new("/");
151 let root = Path::new("/");
153 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
152 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
154 // instead? TODO: can this be a relative path?
153 // instead? TODO: can this be a relative path?
155 let hg = crate::utils::current_exe()?;
154 let hg = crate::utils::current_exe()?;
156 // TODO: this order (per-installation then per-system) matches
155 // TODO: this order (per-installation then per-system) matches
157 // `systemrcpath()` in `mercurial/scmposix.py`, but
156 // `systemrcpath()` in `mercurial/scmposix.py`, but
158 // `mercurial/helptext/config.txt` suggests it should be reversed
157 // `mercurial/helptext/config.txt` suggests it should be reversed
159 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
158 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
160 if installation_prefix != root {
159 if installation_prefix != root {
161 add_for_prefix(&installation_prefix)?
160 add_for_prefix(&installation_prefix)?
162 }
161 }
163 }
162 }
164 add_for_prefix(root)?;
163 add_for_prefix(root)?;
165 Ok(())
164 Ok(())
166 }
165 }
167
166
168 #[cfg(unix)] // TODO: other plateforms
167 #[cfg(unix)] // TODO: other plateforms
169 fn add_user_config(&mut self) -> Result<(), ConfigError> {
168 fn add_user_config(&mut self) -> Result<(), ConfigError> {
170 let opt_home = home::home_dir();
169 let opt_home = home::home_dir();
171 if let Some(home) = &opt_home {
170 if let Some(home) = &opt_home {
172 self.add_trusted_file(&home.join(".hgrc"))?
171 self.add_trusted_file(&home.join(".hgrc"))?
173 }
172 }
174 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
173 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
175 if !darwin {
174 if !darwin {
176 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
175 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
177 .map(PathBuf::from)
176 .map(PathBuf::from)
178 .or_else(|| opt_home.map(|home| home.join(".config")))
177 .or_else(|| opt_home.map(|home| home.join(".config")))
179 {
178 {
180 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
179 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
181 }
180 }
182 }
181 }
183 Ok(())
182 Ok(())
184 }
183 }
185
184
186 /// Loads in order, which means that the precedence is the same
185 /// Loads in order, which means that the precedence is the same
187 /// as the order of `sources`.
186 /// as the order of `sources`.
188 pub fn load_from_explicit_sources(
187 pub fn load_from_explicit_sources(
189 sources: Vec<ConfigSource>,
188 sources: Vec<ConfigSource>,
190 ) -> Result<Self, ConfigError> {
189 ) -> Result<Self, ConfigError> {
191 let mut layers = vec![];
190 let mut layers = vec![];
192
191
193 for source in sources.into_iter() {
192 for source in sources.into_iter() {
194 match source {
193 match source {
195 ConfigSource::Parsed(c) => layers.push(c),
194 ConfigSource::Parsed(c) => layers.push(c),
196 ConfigSource::AbsPath(c) => {
195 ConfigSource::AbsPath(c) => {
197 // TODO check if it should be trusted
196 // TODO check if it should be trusted
198 // mercurial/ui.py:427
197 // mercurial/ui.py:427
199 let data = match std::fs::read(&c) {
198 let data = match std::fs::read(&c) {
200 Err(_) => continue, // same as the python code
199 Err(_) => continue, // same as the python code
201 Ok(data) => data,
200 Ok(data) => data,
202 };
201 };
203 layers.extend(ConfigLayer::parse(&c, &data)?)
202 layers.extend(ConfigLayer::parse(&c, &data)?)
204 }
203 }
205 }
204 }
206 }
205 }
207
206
208 Ok(Config { layers })
207 Ok(Config { layers })
209 }
208 }
210
209
211 /// Loads the per-repository config into a new `Config` which is combined
210 /// Loads the per-repository config into a new `Config` which is combined
212 /// with `self`.
211 /// with `self`.
213 pub(crate) fn combine_with_repo(
212 pub(crate) fn combine_with_repo(
214 &self,
213 &self,
215 repo_config_files: &[PathBuf],
214 repo_config_files: &[PathBuf],
216 ) -> Result<Self, ConfigError> {
215 ) -> Result<Self, ConfigError> {
217 let (cli_layers, other_layers) = self
216 let (cli_layers, other_layers) = self
218 .layers
217 .layers
219 .iter()
218 .iter()
220 .cloned()
219 .cloned()
221 .partition(ConfigLayer::is_from_command_line);
220 .partition(ConfigLayer::is_from_command_line);
222
221
223 let mut repo_config = Self {
222 let mut repo_config = Self {
224 layers: other_layers,
223 layers: other_layers,
225 };
224 };
226 for path in repo_config_files {
225 for path in repo_config_files {
227 // TODO: check if this file should be trusted:
226 // TODO: check if this file should be trusted:
228 // `mercurial/ui.py:427`
227 // `mercurial/ui.py:427`
229 repo_config.add_trusted_file(path)?;
228 repo_config.add_trusted_file(path)?;
230 }
229 }
231 repo_config.layers.extend(cli_layers);
230 repo_config.layers.extend(cli_layers);
232 Ok(repo_config)
231 Ok(repo_config)
233 }
232 }
234
233
235 /// Returns an `Err` if the first value found is not a valid boolean.
234 /// Returns an `Err` if the first value found is not a valid boolean.
236 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
235 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
237 /// found, or `None`.
236 /// found, or `None`.
238 pub fn get_option(
237 pub fn get_option(
239 &self,
238 &self,
240 section: &[u8],
239 section: &[u8],
241 item: &[u8],
240 item: &[u8],
242 ) -> Result<Option<bool>, ConfigParseError> {
241 ) -> Result<Option<bool>, ConfigParseError> {
243 match self.get_inner(&section, &item) {
242 match self.get_inner(&section, &item) {
244 Some((layer, v)) => match parse_bool(&v.bytes) {
243 Some((layer, v)) => match parse_bool(&v.bytes) {
245 Some(b) => Ok(Some(b)),
244 Some(b) => Ok(Some(b)),
246 None => Err(ConfigParseError {
245 None => Err(ConfigParseError {
247 origin: layer.origin.to_owned(),
246 origin: layer.origin.to_owned(),
248 line: v.line,
247 line: v.line,
249 bytes: v.bytes.to_owned(),
248 bytes: v.bytes.to_owned(),
250 }),
249 }),
251 },
250 },
252 None => Ok(None),
251 None => Ok(None),
253 }
252 }
254 }
253 }
255
254
256 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
255 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
257 /// if the value is not found, an `Err` if it's not a valid boolean.
256 /// if the value is not found, an `Err` if it's not a valid boolean.
258 pub fn get_bool(
257 pub fn get_bool(
259 &self,
258 &self,
260 section: &[u8],
259 section: &[u8],
261 item: &[u8],
260 item: &[u8],
262 ) -> Result<bool, ConfigError> {
261 ) -> Result<bool, ConfigError> {
263 Ok(self.get_option(section, item)?.unwrap_or(false))
262 Ok(self.get_option(section, item)?.unwrap_or(false))
264 }
263 }
265
264
266 /// Returns the raw value bytes of the first one found, or `None`.
265 /// Returns the raw value bytes of the first one found, or `None`.
267 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
266 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
268 self.get_inner(section, item)
267 self.get_inner(section, item)
269 .map(|(_, value)| value.bytes.as_ref())
268 .map(|(_, value)| value.bytes.as_ref())
270 }
269 }
271
270
272 /// Returns the layer and the value of the first one found, or `None`.
271 /// Returns the layer and the value of the first one found, or `None`.
273 fn get_inner(
272 fn get_inner(
274 &self,
273 &self,
275 section: &[u8],
274 section: &[u8],
276 item: &[u8],
275 item: &[u8],
277 ) -> Option<(&ConfigLayer, &ConfigValue)> {
276 ) -> Option<(&ConfigLayer, &ConfigValue)> {
278 for layer in self.layers.iter().rev() {
277 for layer in self.layers.iter().rev() {
279 if !layer.trusted {
278 if !layer.trusted {
280 continue;
279 continue;
281 }
280 }
282 if let Some(v) = layer.get(&section, &item) {
281 if let Some(v) = layer.get(&section, &item) {
283 return Some((&layer, v));
282 return Some((&layer, v));
284 }
283 }
285 }
284 }
286 None
285 None
287 }
286 }
288
287
289 /// Get raw values bytes from all layers (even untrusted ones) in order
288 /// Get raw values bytes from all layers (even untrusted ones) in order
290 /// of precedence.
289 /// of precedence.
291 #[cfg(test)]
290 #[cfg(test)]
292 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
291 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
293 let mut res = vec![];
292 let mut res = vec![];
294 for layer in self.layers.iter().rev() {
293 for layer in self.layers.iter().rev() {
295 if let Some(v) = layer.get(&section, &item) {
294 if let Some(v) = layer.get(&section, &item) {
296 res.push(v.bytes.as_ref());
295 res.push(v.bytes.as_ref());
297 }
296 }
298 }
297 }
299 res
298 res
300 }
299 }
301 }
300 }
302
301
303 #[cfg(test)]
302 #[cfg(test)]
304 mod tests {
303 mod tests {
305 use super::*;
304 use super::*;
306 use pretty_assertions::assert_eq;
305 use pretty_assertions::assert_eq;
307 use std::fs::File;
306 use std::fs::File;
308 use std::io::Write;
307 use std::io::Write;
309
308
310 #[test]
309 #[test]
311 fn test_include_layer_ordering() {
310 fn test_include_layer_ordering() {
312 let tmpdir = tempfile::tempdir().unwrap();
311 let tmpdir = tempfile::tempdir().unwrap();
313 let tmpdir_path = tmpdir.path();
312 let tmpdir_path = tmpdir.path();
314 let mut included_file =
313 let mut included_file =
315 File::create(&tmpdir_path.join("included.rc")).unwrap();
314 File::create(&tmpdir_path.join("included.rc")).unwrap();
316
315
317 included_file.write_all(b"[section]\nitem=value1").unwrap();
316 included_file.write_all(b"[section]\nitem=value1").unwrap();
318 let base_config_path = tmpdir_path.join("base.rc");
317 let base_config_path = tmpdir_path.join("base.rc");
319 let mut config_file = File::create(&base_config_path).unwrap();
318 let mut config_file = File::create(&base_config_path).unwrap();
320 let data =
319 let data =
321 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
320 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
322 config_file.write_all(data).unwrap();
321 config_file.write_all(data).unwrap();
323
322
324 let sources = vec![ConfigSource::AbsPath(base_config_path)];
323 let sources = vec![ConfigSource::AbsPath(base_config_path)];
325 let config = Config::load_from_explicit_sources(sources)
324 let config = Config::load_from_explicit_sources(sources)
326 .expect("expected valid config");
325 .expect("expected valid config");
327
326
328 let (_, value) = config.get_inner(b"section", b"item").unwrap();
327 let (_, value) = config.get_inner(b"section", b"item").unwrap();
329 assert_eq!(
328 assert_eq!(
330 value,
329 value,
331 &ConfigValue {
330 &ConfigValue {
332 bytes: b"value2".to_vec(),
331 bytes: b"value2".to_vec(),
333 line: Some(4)
332 line: Some(4)
334 }
333 }
335 );
334 );
336
335
337 let value = config.get(b"section", b"item").unwrap();
336 let value = config.get(b"section", b"item").unwrap();
338 assert_eq!(value, b"value2",);
337 assert_eq!(value, b"value2",);
339 assert_eq!(
338 assert_eq!(
340 config.get_all(b"section", b"item"),
339 config.get_all(b"section", b"item"),
341 [b"value2", b"value1", b"value0"]
340 [b"value2", b"value1", b"value0"]
342 );
341 );
343 }
342 }
344 }
343 }
@@ -1,442 +1,448 b''
1 // files.rs
1 // files.rs
2 //
2 //
3 // Copyright 2019
3 // Copyright 2019
4 // Raphaël Gomès <rgomes@octobus.net>,
4 // Raphaël Gomès <rgomes@octobus.net>,
5 // Yuya Nishihara <yuya@tcha.org>
5 // Yuya Nishihara <yuya@tcha.org>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Functions for fiddling with files.
10 //! Functions for fiddling with files.
11
11
12 use crate::utils::{
12 use crate::utils::{
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
14 path_auditor::PathAuditor,
14 path_auditor::PathAuditor,
15 replace_slice,
15 replace_slice,
16 };
16 };
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 use same_file::is_same_file;
18 use same_file::is_same_file;
19 use std::borrow::{Cow, ToOwned};
19 use std::borrow::{Cow, ToOwned};
20 use std::ffi::OsStr;
20 use std::fs::Metadata;
21 use std::fs::Metadata;
21 use std::iter::FusedIterator;
22 use std::iter::FusedIterator;
22 use std::ops::Deref;
23 use std::ops::Deref;
23 use std::path::{Path, PathBuf};
24 use std::path::{Path, PathBuf};
24
25
25 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
26 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
26 let os_str;
27 let os_str;
27 #[cfg(unix)]
28 #[cfg(unix)]
28 {
29 {
29 use std::os::unix::ffi::OsStrExt;
30 use std::os::unix::ffi::OsStrExt;
30 os_str = std::ffi::OsStr::from_bytes(bytes);
31 os_str = std::ffi::OsStr::from_bytes(bytes);
31 }
32 }
32 // TODO Handle other platforms
33 // TODO Handle other platforms
33 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
34 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
34 // Perhaps, the return type would have to be Result<PathBuf>.
35 // Perhaps, the return type would have to be Result<PathBuf>.
35
36
36 Path::new(os_str)
37 Path::new(os_str)
37 }
38 }
38
39
39 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
40 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
40 // that's why Vec<u8> is returned.
41 // that's why Vec<u8> is returned.
41 #[cfg(unix)]
42 #[cfg(unix)]
42 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
43 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
44 get_bytes_from_os_str(path.as_ref())
45 }
46
47 #[cfg(unix)]
48 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
43 use std::os::unix::ffi::OsStrExt;
49 use std::os::unix::ffi::OsStrExt;
44 path.as_ref().as_os_str().as_bytes().to_vec()
50 str.as_ref().as_bytes().to_vec()
45 }
51 }
46
52
47 /// An iterator over repository path yielding itself and its ancestors.
53 /// An iterator over repository path yielding itself and its ancestors.
48 #[derive(Copy, Clone, Debug)]
54 #[derive(Copy, Clone, Debug)]
49 pub struct Ancestors<'a> {
55 pub struct Ancestors<'a> {
50 next: Option<&'a HgPath>,
56 next: Option<&'a HgPath>,
51 }
57 }
52
58
53 impl<'a> Iterator for Ancestors<'a> {
59 impl<'a> Iterator for Ancestors<'a> {
54 type Item = &'a HgPath;
60 type Item = &'a HgPath;
55
61
56 fn next(&mut self) -> Option<Self::Item> {
62 fn next(&mut self) -> Option<Self::Item> {
57 let next = self.next;
63 let next = self.next;
58 self.next = match self.next {
64 self.next = match self.next {
59 Some(s) if s.is_empty() => None,
65 Some(s) if s.is_empty() => None,
60 Some(s) => {
66 Some(s) => {
61 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
67 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
62 Some(HgPath::new(&s.as_bytes()[..p]))
68 Some(HgPath::new(&s.as_bytes()[..p]))
63 }
69 }
64 None => None,
70 None => None,
65 };
71 };
66 next
72 next
67 }
73 }
68 }
74 }
69
75
70 impl<'a> FusedIterator for Ancestors<'a> {}
76 impl<'a> FusedIterator for Ancestors<'a> {}
71
77
72 /// An iterator over repository path yielding itself and its ancestors.
78 /// An iterator over repository path yielding itself and its ancestors.
73 #[derive(Copy, Clone, Debug)]
79 #[derive(Copy, Clone, Debug)]
74 pub(crate) struct AncestorsWithBase<'a> {
80 pub(crate) struct AncestorsWithBase<'a> {
75 next: Option<(&'a HgPath, &'a HgPath)>,
81 next: Option<(&'a HgPath, &'a HgPath)>,
76 }
82 }
77
83
78 impl<'a> Iterator for AncestorsWithBase<'a> {
84 impl<'a> Iterator for AncestorsWithBase<'a> {
79 type Item = (&'a HgPath, &'a HgPath);
85 type Item = (&'a HgPath, &'a HgPath);
80
86
81 fn next(&mut self) -> Option<Self::Item> {
87 fn next(&mut self) -> Option<Self::Item> {
82 let next = self.next;
88 let next = self.next;
83 self.next = match self.next {
89 self.next = match self.next {
84 Some((s, _)) if s.is_empty() => None,
90 Some((s, _)) if s.is_empty() => None,
85 Some((s, _)) => Some(s.split_filename()),
91 Some((s, _)) => Some(s.split_filename()),
86 None => None,
92 None => None,
87 };
93 };
88 next
94 next
89 }
95 }
90 }
96 }
91
97
92 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
98 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
93
99
94 /// Returns an iterator yielding ancestor directories of the given repository
100 /// Returns an iterator yielding ancestor directories of the given repository
95 /// path.
101 /// path.
96 ///
102 ///
97 /// The path is separated by '/', and must not start with '/'.
103 /// The path is separated by '/', and must not start with '/'.
98 ///
104 ///
99 /// The path itself isn't included unless it is b"" (meaning the root
105 /// The path itself isn't included unless it is b"" (meaning the root
100 /// directory.)
106 /// directory.)
101 pub fn find_dirs(path: &HgPath) -> Ancestors {
107 pub fn find_dirs(path: &HgPath) -> Ancestors {
102 let mut dirs = Ancestors { next: Some(path) };
108 let mut dirs = Ancestors { next: Some(path) };
103 if !path.is_empty() {
109 if !path.is_empty() {
104 dirs.next(); // skip itself
110 dirs.next(); // skip itself
105 }
111 }
106 dirs
112 dirs
107 }
113 }
108
114
109 /// Returns an iterator yielding ancestor directories of the given repository
115 /// Returns an iterator yielding ancestor directories of the given repository
110 /// path.
116 /// path.
111 ///
117 ///
112 /// The path is separated by '/', and must not start with '/'.
118 /// The path is separated by '/', and must not start with '/'.
113 ///
119 ///
114 /// The path itself isn't included unless it is b"" (meaning the root
120 /// The path itself isn't included unless it is b"" (meaning the root
115 /// directory.)
121 /// directory.)
116 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
122 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
117 let mut dirs = AncestorsWithBase {
123 let mut dirs = AncestorsWithBase {
118 next: Some((path, HgPath::new(b""))),
124 next: Some((path, HgPath::new(b""))),
119 };
125 };
120 if !path.is_empty() {
126 if !path.is_empty() {
121 dirs.next(); // skip itself
127 dirs.next(); // skip itself
122 }
128 }
123 dirs
129 dirs
124 }
130 }
125
131
126 /// TODO more than ASCII?
132 /// TODO more than ASCII?
127 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
133 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
128 #[cfg(windows)] // NTFS compares via upper()
134 #[cfg(windows)] // NTFS compares via upper()
129 return path.to_ascii_uppercase();
135 return path.to_ascii_uppercase();
130 #[cfg(unix)]
136 #[cfg(unix)]
131 path.to_ascii_lowercase()
137 path.to_ascii_lowercase()
132 }
138 }
133
139
134 lazy_static! {
140 lazy_static! {
135 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
141 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
136 [
142 [
137 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
143 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
138 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
144 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
139 ]
145 ]
140 .iter()
146 .iter()
141 .map(|code| {
147 .map(|code| {
142 std::char::from_u32(*code)
148 std::char::from_u32(*code)
143 .unwrap()
149 .unwrap()
144 .encode_utf8(&mut [0; 3])
150 .encode_utf8(&mut [0; 3])
145 .bytes()
151 .bytes()
146 .collect()
152 .collect()
147 })
153 })
148 .collect()
154 .collect()
149 };
155 };
150 }
156 }
151
157
152 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
158 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
153 let mut buf = bytes.to_owned();
159 let mut buf = bytes.to_owned();
154 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
160 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
155 if needs_escaping {
161 if needs_escaping {
156 for forbidden in IGNORED_CHARS.iter() {
162 for forbidden in IGNORED_CHARS.iter() {
157 replace_slice(&mut buf, forbidden, &[])
163 replace_slice(&mut buf, forbidden, &[])
158 }
164 }
159 buf
165 buf
160 } else {
166 } else {
161 buf
167 buf
162 }
168 }
163 }
169 }
164
170
165 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
171 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
166 hfs_ignore_clean(&bytes.to_ascii_lowercase())
172 hfs_ignore_clean(&bytes.to_ascii_lowercase())
167 }
173 }
168
174
169 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
175 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
170 pub struct HgMetadata {
176 pub struct HgMetadata {
171 pub st_dev: u64,
177 pub st_dev: u64,
172 pub st_mode: u32,
178 pub st_mode: u32,
173 pub st_nlink: u64,
179 pub st_nlink: u64,
174 pub st_size: u64,
180 pub st_size: u64,
175 pub st_mtime: i64,
181 pub st_mtime: i64,
176 pub st_ctime: i64,
182 pub st_ctime: i64,
177 }
183 }
178
184
179 // TODO support other plaforms
185 // TODO support other plaforms
180 #[cfg(unix)]
186 #[cfg(unix)]
181 impl HgMetadata {
187 impl HgMetadata {
182 pub fn from_metadata(metadata: Metadata) -> Self {
188 pub fn from_metadata(metadata: Metadata) -> Self {
183 use std::os::unix::fs::MetadataExt;
189 use std::os::unix::fs::MetadataExt;
184 Self {
190 Self {
185 st_dev: metadata.dev(),
191 st_dev: metadata.dev(),
186 st_mode: metadata.mode(),
192 st_mode: metadata.mode(),
187 st_nlink: metadata.nlink(),
193 st_nlink: metadata.nlink(),
188 st_size: metadata.size(),
194 st_size: metadata.size(),
189 st_mtime: metadata.mtime(),
195 st_mtime: metadata.mtime(),
190 st_ctime: metadata.ctime(),
196 st_ctime: metadata.ctime(),
191 }
197 }
192 }
198 }
193 }
199 }
194
200
195 /// Returns the canonical path of `name`, given `cwd` and `root`
201 /// Returns the canonical path of `name`, given `cwd` and `root`
196 pub fn canonical_path(
202 pub fn canonical_path(
197 root: impl AsRef<Path>,
203 root: impl AsRef<Path>,
198 cwd: impl AsRef<Path>,
204 cwd: impl AsRef<Path>,
199 name: impl AsRef<Path>,
205 name: impl AsRef<Path>,
200 ) -> Result<PathBuf, HgPathError> {
206 ) -> Result<PathBuf, HgPathError> {
201 // TODO add missing normalization for other platforms
207 // TODO add missing normalization for other platforms
202 let root = root.as_ref();
208 let root = root.as_ref();
203 let cwd = cwd.as_ref();
209 let cwd = cwd.as_ref();
204 let name = name.as_ref();
210 let name = name.as_ref();
205
211
206 let name = if !name.is_absolute() {
212 let name = if !name.is_absolute() {
207 root.join(&cwd).join(&name)
213 root.join(&cwd).join(&name)
208 } else {
214 } else {
209 name.to_owned()
215 name.to_owned()
210 };
216 };
211 let auditor = PathAuditor::new(&root);
217 let auditor = PathAuditor::new(&root);
212 if name != root && name.starts_with(&root) {
218 if name != root && name.starts_with(&root) {
213 let name = name.strip_prefix(&root).unwrap();
219 let name = name.strip_prefix(&root).unwrap();
214 auditor.audit_path(path_to_hg_path_buf(name)?)?;
220 auditor.audit_path(path_to_hg_path_buf(name)?)?;
215 Ok(name.to_owned())
221 Ok(name.to_owned())
216 } else if name == root {
222 } else if name == root {
217 Ok("".into())
223 Ok("".into())
218 } else {
224 } else {
219 // Determine whether `name' is in the hierarchy at or beneath `root',
225 // Determine whether `name' is in the hierarchy at or beneath `root',
220 // by iterating name=name.parent() until it returns `None` (can't
226 // by iterating name=name.parent() until it returns `None` (can't
221 // check name == '/', because that doesn't work on windows).
227 // check name == '/', because that doesn't work on windows).
222 let mut name = name.deref();
228 let mut name = name.deref();
223 let original_name = name.to_owned();
229 let original_name = name.to_owned();
224 loop {
230 loop {
225 let same = is_same_file(&name, &root).unwrap_or(false);
231 let same = is_same_file(&name, &root).unwrap_or(false);
226 if same {
232 if same {
227 if name == original_name {
233 if name == original_name {
228 // `name` was actually the same as root (maybe a symlink)
234 // `name` was actually the same as root (maybe a symlink)
229 return Ok("".into());
235 return Ok("".into());
230 }
236 }
231 // `name` is a symlink to root, so `original_name` is under
237 // `name` is a symlink to root, so `original_name` is under
232 // root
238 // root
233 let rel_path = original_name.strip_prefix(&name).unwrap();
239 let rel_path = original_name.strip_prefix(&name).unwrap();
234 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
240 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
235 return Ok(rel_path.to_owned());
241 return Ok(rel_path.to_owned());
236 }
242 }
237 name = match name.parent() {
243 name = match name.parent() {
238 None => break,
244 None => break,
239 Some(p) => p,
245 Some(p) => p,
240 };
246 };
241 }
247 }
242 // TODO hint to the user about using --cwd
248 // TODO hint to the user about using --cwd
243 // Bubble up the responsibility to Python for now
249 // Bubble up the responsibility to Python for now
244 Err(HgPathError::NotUnderRoot {
250 Err(HgPathError::NotUnderRoot {
245 path: original_name.to_owned(),
251 path: original_name.to_owned(),
246 root: root.to_owned(),
252 root: root.to_owned(),
247 })
253 })
248 }
254 }
249 }
255 }
250
256
251 /// Returns the representation of the path relative to the current working
257 /// Returns the representation of the path relative to the current working
252 /// directory for display purposes.
258 /// directory for display purposes.
253 ///
259 ///
254 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
260 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
255 /// of the repository.
261 /// of the repository.
256 ///
262 ///
257 /// # Examples
263 /// # Examples
258 ///
264 ///
259 /// ```
265 /// ```
260 /// use hg::utils::hg_path::HgPath;
266 /// use hg::utils::hg_path::HgPath;
261 /// use hg::utils::files::relativize_path;
267 /// use hg::utils::files::relativize_path;
262 /// use std::borrow::Cow;
268 /// use std::borrow::Cow;
263 ///
269 ///
264 /// let file = HgPath::new(b"nested/file");
270 /// let file = HgPath::new(b"nested/file");
265 /// let cwd = HgPath::new(b"");
271 /// let cwd = HgPath::new(b"");
266 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
272 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
267 ///
273 ///
268 /// let cwd = HgPath::new(b"nested");
274 /// let cwd = HgPath::new(b"nested");
269 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
275 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
270 ///
276 ///
271 /// let cwd = HgPath::new(b"other");
277 /// let cwd = HgPath::new(b"other");
272 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
278 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
273 /// ```
279 /// ```
274 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
280 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
275 if cwd.as_ref().is_empty() {
281 if cwd.as_ref().is_empty() {
276 Cow::Borrowed(path.as_bytes())
282 Cow::Borrowed(path.as_bytes())
277 } else {
283 } else {
278 let mut res: Vec<u8> = Vec::new();
284 let mut res: Vec<u8> = Vec::new();
279 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
285 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
280 let mut cwd_iter =
286 let mut cwd_iter =
281 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
287 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
282 loop {
288 loop {
283 match (path_iter.peek(), cwd_iter.peek()) {
289 match (path_iter.peek(), cwd_iter.peek()) {
284 (Some(a), Some(b)) if a == b => (),
290 (Some(a), Some(b)) if a == b => (),
285 _ => break,
291 _ => break,
286 }
292 }
287 path_iter.next();
293 path_iter.next();
288 cwd_iter.next();
294 cwd_iter.next();
289 }
295 }
290 let mut need_sep = false;
296 let mut need_sep = false;
291 for _ in cwd_iter {
297 for _ in cwd_iter {
292 if need_sep {
298 if need_sep {
293 res.extend(b"/")
299 res.extend(b"/")
294 } else {
300 } else {
295 need_sep = true
301 need_sep = true
296 };
302 };
297 res.extend(b"..");
303 res.extend(b"..");
298 }
304 }
299 for c in path_iter {
305 for c in path_iter {
300 if need_sep {
306 if need_sep {
301 res.extend(b"/")
307 res.extend(b"/")
302 } else {
308 } else {
303 need_sep = true
309 need_sep = true
304 };
310 };
305 res.extend(c);
311 res.extend(c);
306 }
312 }
307 Cow::Owned(res)
313 Cow::Owned(res)
308 }
314 }
309 }
315 }
310
316
311 #[cfg(test)]
317 #[cfg(test)]
312 mod tests {
318 mod tests {
313 use super::*;
319 use super::*;
314 use pretty_assertions::assert_eq;
320 use pretty_assertions::assert_eq;
315
321
316 #[test]
322 #[test]
317 fn find_dirs_some() {
323 fn find_dirs_some() {
318 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
324 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
319 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
325 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
320 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
326 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
321 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
327 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
322 assert_eq!(dirs.next(), None);
328 assert_eq!(dirs.next(), None);
323 assert_eq!(dirs.next(), None);
329 assert_eq!(dirs.next(), None);
324 }
330 }
325
331
326 #[test]
332 #[test]
327 fn find_dirs_empty() {
333 fn find_dirs_empty() {
328 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
334 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
329 let mut dirs = super::find_dirs(HgPath::new(b""));
335 let mut dirs = super::find_dirs(HgPath::new(b""));
330 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
336 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
331 assert_eq!(dirs.next(), None);
337 assert_eq!(dirs.next(), None);
332 assert_eq!(dirs.next(), None);
338 assert_eq!(dirs.next(), None);
333 }
339 }
334
340
335 #[test]
341 #[test]
336 fn test_find_dirs_with_base_some() {
342 fn test_find_dirs_with_base_some() {
337 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
343 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
338 assert_eq!(
344 assert_eq!(
339 dirs.next(),
345 dirs.next(),
340 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
346 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
341 );
347 );
342 assert_eq!(
348 assert_eq!(
343 dirs.next(),
349 dirs.next(),
344 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
350 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
345 );
351 );
346 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
352 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
347 assert_eq!(dirs.next(), None);
353 assert_eq!(dirs.next(), None);
348 assert_eq!(dirs.next(), None);
354 assert_eq!(dirs.next(), None);
349 }
355 }
350
356
351 #[test]
357 #[test]
352 fn test_find_dirs_with_base_empty() {
358 fn test_find_dirs_with_base_empty() {
353 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
359 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
354 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
360 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
355 assert_eq!(dirs.next(), None);
361 assert_eq!(dirs.next(), None);
356 assert_eq!(dirs.next(), None);
362 assert_eq!(dirs.next(), None);
357 }
363 }
358
364
359 #[test]
365 #[test]
360 fn test_canonical_path() {
366 fn test_canonical_path() {
361 let root = Path::new("/repo");
367 let root = Path::new("/repo");
362 let cwd = Path::new("/dir");
368 let cwd = Path::new("/dir");
363 let name = Path::new("filename");
369 let name = Path::new("filename");
364 assert_eq!(
370 assert_eq!(
365 canonical_path(root, cwd, name),
371 canonical_path(root, cwd, name),
366 Err(HgPathError::NotUnderRoot {
372 Err(HgPathError::NotUnderRoot {
367 path: PathBuf::from("/dir/filename"),
373 path: PathBuf::from("/dir/filename"),
368 root: root.to_path_buf()
374 root: root.to_path_buf()
369 })
375 })
370 );
376 );
371
377
372 let root = Path::new("/repo");
378 let root = Path::new("/repo");
373 let cwd = Path::new("/");
379 let cwd = Path::new("/");
374 let name = Path::new("filename");
380 let name = Path::new("filename");
375 assert_eq!(
381 assert_eq!(
376 canonical_path(root, cwd, name),
382 canonical_path(root, cwd, name),
377 Err(HgPathError::NotUnderRoot {
383 Err(HgPathError::NotUnderRoot {
378 path: PathBuf::from("/filename"),
384 path: PathBuf::from("/filename"),
379 root: root.to_path_buf()
385 root: root.to_path_buf()
380 })
386 })
381 );
387 );
382
388
383 let root = Path::new("/repo");
389 let root = Path::new("/repo");
384 let cwd = Path::new("/");
390 let cwd = Path::new("/");
385 let name = Path::new("repo/filename");
391 let name = Path::new("repo/filename");
386 assert_eq!(
392 assert_eq!(
387 canonical_path(root, cwd, name),
393 canonical_path(root, cwd, name),
388 Ok(PathBuf::from("filename"))
394 Ok(PathBuf::from("filename"))
389 );
395 );
390
396
391 let root = Path::new("/repo");
397 let root = Path::new("/repo");
392 let cwd = Path::new("/repo");
398 let cwd = Path::new("/repo");
393 let name = Path::new("filename");
399 let name = Path::new("filename");
394 assert_eq!(
400 assert_eq!(
395 canonical_path(root, cwd, name),
401 canonical_path(root, cwd, name),
396 Ok(PathBuf::from("filename"))
402 Ok(PathBuf::from("filename"))
397 );
403 );
398
404
399 let root = Path::new("/repo");
405 let root = Path::new("/repo");
400 let cwd = Path::new("/repo/subdir");
406 let cwd = Path::new("/repo/subdir");
401 let name = Path::new("filename");
407 let name = Path::new("filename");
402 assert_eq!(
408 assert_eq!(
403 canonical_path(root, cwd, name),
409 canonical_path(root, cwd, name),
404 Ok(PathBuf::from("subdir/filename"))
410 Ok(PathBuf::from("subdir/filename"))
405 );
411 );
406 }
412 }
407
413
408 #[test]
414 #[test]
409 fn test_canonical_path_not_rooted() {
415 fn test_canonical_path_not_rooted() {
410 use std::fs::create_dir;
416 use std::fs::create_dir;
411 use tempfile::tempdir;
417 use tempfile::tempdir;
412
418
413 let base_dir = tempdir().unwrap();
419 let base_dir = tempdir().unwrap();
414 let base_dir_path = base_dir.path();
420 let base_dir_path = base_dir.path();
415 let beneath_repo = base_dir_path.join("a");
421 let beneath_repo = base_dir_path.join("a");
416 let root = base_dir_path.join("a/b");
422 let root = base_dir_path.join("a/b");
417 let out_of_repo = base_dir_path.join("c");
423 let out_of_repo = base_dir_path.join("c");
418 let under_repo_symlink = out_of_repo.join("d");
424 let under_repo_symlink = out_of_repo.join("d");
419
425
420 create_dir(&beneath_repo).unwrap();
426 create_dir(&beneath_repo).unwrap();
421 create_dir(&root).unwrap();
427 create_dir(&root).unwrap();
422
428
423 // TODO make portable
429 // TODO make portable
424 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
430 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
425
431
426 assert_eq!(
432 assert_eq!(
427 canonical_path(&root, Path::new(""), out_of_repo),
433 canonical_path(&root, Path::new(""), out_of_repo),
428 Ok(PathBuf::from(""))
434 Ok(PathBuf::from(""))
429 );
435 );
430 assert_eq!(
436 assert_eq!(
431 canonical_path(&root, Path::new(""), &beneath_repo),
437 canonical_path(&root, Path::new(""), &beneath_repo),
432 Err(HgPathError::NotUnderRoot {
438 Err(HgPathError::NotUnderRoot {
433 path: beneath_repo.to_owned(),
439 path: beneath_repo.to_owned(),
434 root: root.to_owned()
440 root: root.to_owned()
435 })
441 })
436 );
442 );
437 assert_eq!(
443 assert_eq!(
438 canonical_path(&root, Path::new(""), &under_repo_symlink),
444 canonical_path(&root, Path::new(""), &under_repo_symlink),
439 Ok(PathBuf::from("d"))
445 Ok(PathBuf::from("d"))
440 );
446 );
441 }
447 }
442 }
448 }
@@ -1,176 +1,175 b''
1 extern crate log;
1 extern crate log;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::App;
3 use clap::App;
4 use clap::AppSettings;
4 use clap::AppSettings;
5 use clap::Arg;
5 use clap::Arg;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use format_bytes::format_bytes;
7 use format_bytes::format_bytes;
8 use hg::config::Config;
8 use hg::config::Config;
9 use hg::repo::{Repo, RepoError};
9 use hg::repo::{Repo, RepoError};
10 use std::path::{Path, PathBuf};
10 use std::path::{Path, PathBuf};
11
11
12 mod error;
12 mod error;
13 mod exitcode;
13 mod exitcode;
14 mod ui;
14 mod ui;
15 use error::CommandError;
15 use error::CommandError;
16
16
17 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
17 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
18 app.arg(
18 app.arg(
19 Arg::with_name("repository")
19 Arg::with_name("repository")
20 .help("repository root directory")
20 .help("repository root directory")
21 .short("-R")
21 .short("-R")
22 .long("--repository")
22 .long("--repository")
23 .value_name("REPO")
23 .value_name("REPO")
24 .takes_value(true),
24 .takes_value(true),
25 )
25 )
26 .arg(
26 .arg(
27 Arg::with_name("config")
27 Arg::with_name("config")
28 .help("set/override config option (use 'section.name=value')")
28 .help("set/override config option (use 'section.name=value')")
29 .long("--config")
29 .long("--config")
30 .value_name("CONFIG")
30 .value_name("CONFIG")
31 .takes_value(true)
31 .takes_value(true)
32 // Ok: `--config section.key1=val --config section.key2=val2`
32 // Ok: `--config section.key1=val --config section.key2=val2`
33 .multiple(true)
33 .multiple(true)
34 // Not ok: `--config section.key1=val section.key2=val2`
34 // Not ok: `--config section.key1=val section.key2=val2`
35 .number_of_values(1),
35 .number_of_values(1),
36 )
36 )
37 }
37 }
38
38
39 fn main_with_result(ui: &ui::Ui) -> Result<(), CommandError> {
39 fn main_with_result(ui: &ui::Ui) -> Result<(), CommandError> {
40 env_logger::init();
40 env_logger::init();
41 let app = App::new("rhg")
41 let app = App::new("rhg")
42 .setting(AppSettings::AllowInvalidUtf8)
42 .setting(AppSettings::AllowInvalidUtf8)
43 .setting(AppSettings::SubcommandRequired)
43 .setting(AppSettings::SubcommandRequired)
44 .setting(AppSettings::VersionlessSubcommands)
44 .setting(AppSettings::VersionlessSubcommands)
45 .version("0.0.1");
45 .version("0.0.1");
46 let app = add_global_args(app);
46 let app = add_global_args(app);
47 let app = add_subcommand_args(app);
47 let app = add_subcommand_args(app);
48
48
49 let matches = app.clone().get_matches_safe()?;
49 let matches = app.clone().get_matches_safe()?;
50
50
51 let (subcommand_name, subcommand_matches) = matches.subcommand();
51 let (subcommand_name, subcommand_matches) = matches.subcommand();
52 let run = subcommand_run_fn(subcommand_name)
52 let run = subcommand_run_fn(subcommand_name)
53 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
53 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
54 let subcommand_args = subcommand_matches
54 let subcommand_args = subcommand_matches
55 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
55 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
56
56
57 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
57 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
58 // `hg log -R ./foo`
58 // `hg log -R ./foo`
59 let value_of_global_arg = |name| {
59 let value_of_global_arg = |name| {
60 subcommand_args
60 subcommand_args
61 .value_of_os(name)
61 .value_of_os(name)
62 .or_else(|| matches.value_of_os(name))
62 .or_else(|| matches.value_of_os(name))
63 };
63 };
64 // For arguments where multiple occurences are allowed, return a
64 // For arguments where multiple occurences are allowed, return a
65 // possibly-iterator of all values.
65 // possibly-iterator of all values.
66 let values_of_global_arg = |name: &str| {
66 let values_of_global_arg = |name: &str| {
67 let a = matches.values_of_os(name).into_iter().flatten();
67 let a = matches.values_of_os(name).into_iter().flatten();
68 let b = subcommand_args.values_of_os(name).into_iter().flatten();
68 let b = subcommand_args.values_of_os(name).into_iter().flatten();
69 a.chain(b)
69 a.chain(b)
70 };
70 };
71
71
72 let config_args = values_of_global_arg("config")
72 let config_args = values_of_global_arg("config")
73 // `get_bytes_from_path` works for OsStr the same as for Path
73 .map(hg::utils::files::get_bytes_from_os_str);
74 .map(hg::utils::files::get_bytes_from_path);
75 let non_repo_config = &hg::config::Config::load(config_args)?;
74 let non_repo_config = &hg::config::Config::load(config_args)?;
76
75
77 let repo_path = value_of_global_arg("repository").map(Path::new);
76 let repo_path = value_of_global_arg("repository").map(Path::new);
78 let repo = match Repo::find(non_repo_config, repo_path) {
77 let repo = match Repo::find(non_repo_config, repo_path) {
79 Ok(repo) => Ok(repo),
78 Ok(repo) => Ok(repo),
80 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
79 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
81 // Not finding a repo is not fatal yet, if `-R` was not given
80 // Not finding a repo is not fatal yet, if `-R` was not given
82 Err(NoRepoInCwdError { cwd: at })
81 Err(NoRepoInCwdError { cwd: at })
83 }
82 }
84 Err(error) => return Err(error.into()),
83 Err(error) => return Err(error.into()),
85 };
84 };
86
85
87 run(&CliInvocation {
86 run(&CliInvocation {
88 ui,
87 ui,
89 subcommand_args,
88 subcommand_args,
90 non_repo_config,
89 non_repo_config,
91 repo: repo.as_ref(),
90 repo: repo.as_ref(),
92 })
91 })
93 }
92 }
94
93
95 fn main() {
94 fn main() {
96 let ui = ui::Ui::new();
95 let ui = ui::Ui::new();
97
96
98 let exit_code = match main_with_result(&ui) {
97 let exit_code = match main_with_result(&ui) {
99 Ok(()) => exitcode::OK,
98 Ok(()) => exitcode::OK,
100
99
101 // Exit with a specific code and no error message to let a potential
100 // Exit with a specific code and no error message to let a potential
102 // wrapper script fallback to Python-based Mercurial.
101 // wrapper script fallback to Python-based Mercurial.
103 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
102 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
104
103
105 Err(CommandError::Abort { message }) => {
104 Err(CommandError::Abort { message }) => {
106 if !message.is_empty() {
105 if !message.is_empty() {
107 // Ignore errors when writing to stderr, we’re already exiting
106 // Ignore errors when writing to stderr, we’re already exiting
108 // with failure code so there’s not much more we can do.
107 // with failure code so there’s not much more we can do.
109 let _ =
108 let _ =
110 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
109 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
111 }
110 }
112 exitcode::ABORT
111 exitcode::ABORT
113 }
112 }
114 };
113 };
115 std::process::exit(exit_code)
114 std::process::exit(exit_code)
116 }
115 }
117
116
118 macro_rules! subcommands {
117 macro_rules! subcommands {
119 ($( $command: ident )+) => {
118 ($( $command: ident )+) => {
120 mod commands {
119 mod commands {
121 $(
120 $(
122 pub mod $command;
121 pub mod $command;
123 )+
122 )+
124 }
123 }
125
124
126 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
125 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
127 app
126 app
128 $(
127 $(
129 .subcommand(add_global_args(commands::$command::args()))
128 .subcommand(add_global_args(commands::$command::args()))
130 )+
129 )+
131 }
130 }
132
131
133 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
132 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
134
133
135 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
134 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
136 match name {
135 match name {
137 $(
136 $(
138 stringify!($command) => Some(commands::$command::run),
137 stringify!($command) => Some(commands::$command::run),
139 )+
138 )+
140 _ => None,
139 _ => None,
141 }
140 }
142 }
141 }
143 };
142 };
144 }
143 }
145
144
146 subcommands! {
145 subcommands! {
147 cat
146 cat
148 debugdata
147 debugdata
149 debugrequirements
148 debugrequirements
150 files
149 files
151 root
150 root
152 config
151 config
153 }
152 }
154 pub struct CliInvocation<'a> {
153 pub struct CliInvocation<'a> {
155 ui: &'a Ui,
154 ui: &'a Ui,
156 subcommand_args: &'a ArgMatches<'a>,
155 subcommand_args: &'a ArgMatches<'a>,
157 non_repo_config: &'a Config,
156 non_repo_config: &'a Config,
158 /// References inside `Result` is a bit peculiar but allow
157 /// References inside `Result` is a bit peculiar but allow
159 /// `invocation.repo?` to work out with `&CliInvocation` since this
158 /// `invocation.repo?` to work out with `&CliInvocation` since this
160 /// `Result` type is `Copy`.
159 /// `Result` type is `Copy`.
161 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
160 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
162 }
161 }
163
162
164 struct NoRepoInCwdError {
163 struct NoRepoInCwdError {
165 cwd: PathBuf,
164 cwd: PathBuf,
166 }
165 }
167
166
168 impl CliInvocation<'_> {
167 impl CliInvocation<'_> {
169 fn config(&self) -> &Config {
168 fn config(&self) -> &Config {
170 if let Ok(repo) = self.repo {
169 if let Ok(repo) = self.repo {
171 repo.config()
170 repo.config()
172 } else {
171 } else {
173 self.non_repo_config
172 self.non_repo_config
174 }
173 }
175 }
174 }
176 }
175 }
General Comments 0
You need to be logged in to leave comments. Login now