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_ |
|
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(§ion, &item) { |
|
242 | match self.get_inner(§ion, &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(§ion, &item) { |
|
281 | if let Some(v) = layer.get(§ion, &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(§ion, &item) { |
|
294 | if let Some(v) = layer.get(§ion, &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 |
|
|
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