Show More
@@ -1,310 +1,311 | |||||
1 | // layer.rs |
|
1 | // layer.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 crate::errors::HgError; |
|
10 | use crate::errors::HgError; | |
11 | use crate::utils::files::{get_bytes_from_path, get_path_from_bytes}; |
|
11 | use crate::utils::files::{get_bytes_from_path, get_path_from_bytes}; | |
12 | use format_bytes::{format_bytes, write_bytes, DisplayBytes}; |
|
12 | use format_bytes::{format_bytes, write_bytes, DisplayBytes}; | |
13 | use lazy_static::lazy_static; |
|
13 | use lazy_static::lazy_static; | |
14 | use regex::bytes::Regex; |
|
14 | use regex::bytes::Regex; | |
15 | use std::collections::HashMap; |
|
15 | use std::collections::HashMap; | |
16 | use std::path::{Path, PathBuf}; |
|
16 | use std::path::{Path, PathBuf}; | |
17 |
|
17 | |||
18 | lazy_static! { |
|
18 | lazy_static! { | |
19 | static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]"); |
|
19 | static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]"); | |
20 | static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)"); |
|
20 | static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)"); | |
21 | /// Continuation whitespace |
|
21 | /// Continuation whitespace | |
22 | static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$"); |
|
22 | static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$"); | |
23 | static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)"); |
|
23 | static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)"); | |
24 | static ref COMMENT_RE: Regex = make_regex(r"^(;|#)"); |
|
24 | static ref COMMENT_RE: Regex = make_regex(r"^(;|#)"); | |
25 | /// A directive that allows for removing previous entries |
|
25 | /// A directive that allows for removing previous entries | |
26 | static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)"); |
|
26 | static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)"); | |
27 | /// A directive that allows for including other config files |
|
27 | /// A directive that allows for including other config files | |
28 | static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$"); |
|
28 | static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$"); | |
29 | } |
|
29 | } | |
30 |
|
30 | |||
31 | /// All config values separated by layers of precedence. |
|
31 | /// All config values separated by layers of precedence. | |
32 | /// Each config source may be split in multiple layers if `%include` directives |
|
32 | /// Each config source may be split in multiple layers if `%include` directives | |
33 | /// are used. |
|
33 | /// are used. | |
34 | /// TODO detail the general precedence |
|
34 | /// TODO detail the general precedence | |
35 | #[derive(Clone)] |
|
35 | #[derive(Clone)] | |
36 | pub struct ConfigLayer { |
|
36 | pub struct ConfigLayer { | |
37 | /// Mapping of the sections to their items |
|
37 | /// Mapping of the sections to their items | |
38 | sections: HashMap<Vec<u8>, ConfigItem>, |
|
38 | sections: HashMap<Vec<u8>, ConfigItem>, | |
39 | /// All sections (and their items/values) in a layer share the same origin |
|
39 | /// All sections (and their items/values) in a layer share the same origin | |
40 | pub origin: ConfigOrigin, |
|
40 | pub origin: ConfigOrigin, | |
41 | /// Whether this layer comes from a trusted user or group |
|
41 | /// Whether this layer comes from a trusted user or group | |
42 | pub trusted: bool, |
|
42 | pub trusted: bool, | |
43 | } |
|
43 | } | |
44 |
|
44 | |||
45 | impl ConfigLayer { |
|
45 | impl ConfigLayer { | |
46 | pub fn new(origin: ConfigOrigin) -> Self { |
|
46 | pub fn new(origin: ConfigOrigin) -> Self { | |
47 | ConfigLayer { |
|
47 | ConfigLayer { | |
48 | sections: HashMap::new(), |
|
48 | sections: HashMap::new(), | |
49 | trusted: true, // TODO check |
|
49 | trusted: true, // TODO check | |
50 | origin, |
|
50 | origin, | |
51 | } |
|
51 | } | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
54 | /// Parse `--config` CLI arguments and return a layer if thereβs any |
|
54 | /// Parse `--config` CLI arguments and return a layer if thereβs any | |
55 | pub(crate) fn parse_cli_args( |
|
55 | pub(crate) fn parse_cli_args( | |
56 | cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>, |
|
56 | cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>, | |
57 | ) -> Result<Option<Self>, ConfigError> { |
|
57 | ) -> Result<Option<Self>, ConfigError> { | |
58 | fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> { |
|
58 | fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> { | |
59 | use crate::utils::SliceExt; |
|
59 | use crate::utils::SliceExt; | |
60 |
|
60 | |||
61 | let (section_and_item, value) = arg.split_2(b'=')?; |
|
61 | let (section_and_item, value) = arg.split_2(b'=')?; | |
62 | let (section, item) = section_and_item.trim().split_2(b'.')?; |
|
62 | let (section, item) = section_and_item.trim().split_2(b'.')?; | |
63 | Some(( |
|
63 | Some(( | |
64 | section.to_owned(), |
|
64 | section.to_owned(), | |
65 | item.to_owned(), |
|
65 | item.to_owned(), | |
66 | value.trim().to_owned(), |
|
66 | value.trim().to_owned(), | |
67 | )) |
|
67 | )) | |
68 | } |
|
68 | } | |
69 |
|
69 | |||
70 | let mut layer = Self::new(ConfigOrigin::CommandLine); |
|
70 | let mut layer = Self::new(ConfigOrigin::CommandLine); | |
71 | for arg in cli_config_args { |
|
71 | for arg in cli_config_args { | |
72 | let arg = arg.as_ref(); |
|
72 | let arg = arg.as_ref(); | |
73 | if let Some((section, item, value)) = parse_one(arg) { |
|
73 | if let Some((section, item, value)) = parse_one(arg) { | |
74 | layer.add(section, item, value, None); |
|
74 | layer.add(section, item, value, None); | |
75 | } else { |
|
75 | } else { | |
76 | Err(HgError::abort(format!( |
|
76 | Err(HgError::abort(format!( | |
77 | "abort: malformed --config option: '{}' \ |
|
77 | "abort: malformed --config option: '{}' \ | |
78 | (use --config section.name=value)", |
|
78 | (use --config section.name=value)", | |
79 | String::from_utf8_lossy(arg), |
|
79 | String::from_utf8_lossy(arg), | |
80 | )))? |
|
80 | )))? | |
81 | } |
|
81 | } | |
82 | } |
|
82 | } | |
83 | if layer.sections.is_empty() { |
|
83 | if layer.sections.is_empty() { | |
84 | Ok(None) |
|
84 | Ok(None) | |
85 | } else { |
|
85 | } else { | |
86 | Ok(Some(layer)) |
|
86 | Ok(Some(layer)) | |
87 | } |
|
87 | } | |
88 | } |
|
88 | } | |
89 |
|
89 | |||
90 | /// Returns whether this layer comes from `--config` CLI arguments |
|
90 | /// Returns whether this layer comes from `--config` CLI arguments | |
91 | pub(crate) fn is_from_command_line(&self) -> bool { |
|
91 | pub(crate) fn is_from_command_line(&self) -> bool { | |
92 | if let ConfigOrigin::CommandLine = self.origin { |
|
92 | if let ConfigOrigin::CommandLine = self.origin { | |
93 | true |
|
93 | true | |
94 | } else { |
|
94 | } else { | |
95 | false |
|
95 | false | |
96 | } |
|
96 | } | |
97 | } |
|
97 | } | |
98 |
|
98 | |||
99 | /// Add an entry to the config, overwriting the old one if already present. |
|
99 | /// Add an entry to the config, overwriting the old one if already present. | |
100 | pub fn add( |
|
100 | pub fn add( | |
101 | &mut self, |
|
101 | &mut self, | |
102 | section: Vec<u8>, |
|
102 | section: Vec<u8>, | |
103 | item: Vec<u8>, |
|
103 | item: Vec<u8>, | |
104 | value: Vec<u8>, |
|
104 | value: Vec<u8>, | |
105 | line: Option<usize>, |
|
105 | line: Option<usize>, | |
106 | ) { |
|
106 | ) { | |
107 | self.sections |
|
107 | self.sections | |
108 | .entry(section) |
|
108 | .entry(section) | |
109 | .or_insert_with(|| HashMap::new()) |
|
109 | .or_insert_with(|| HashMap::new()) | |
110 | .insert(item, ConfigValue { bytes: value, line }); |
|
110 | .insert(item, ConfigValue { bytes: value, line }); | |
111 | } |
|
111 | } | |
112 |
|
112 | |||
113 | /// Returns the config value in `<section>.<item>` if it exists |
|
113 | /// Returns the config value in `<section>.<item>` if it exists | |
114 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> { |
|
114 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> { | |
115 | Some(self.sections.get(section)?.get(item)?) |
|
115 | Some(self.sections.get(section)?.get(item)?) | |
116 | } |
|
116 | } | |
117 |
|
117 | |||
118 | /// Returns the keys defined in the given section |
|
118 | /// Returns the keys defined in the given section | |
119 | pub fn iter_keys(&self, section: &[u8]) -> impl Iterator<Item = &[u8]> { |
|
119 | pub fn iter_keys(&self, section: &[u8]) -> impl Iterator<Item = &[u8]> { | |
120 | self.sections |
|
120 | self.sections | |
121 | .get(section) |
|
121 | .get(section) | |
122 | .into_iter() |
|
122 | .into_iter() | |
123 | .flat_map(|section| section.keys().map(|vec| &**vec)) |
|
123 | .flat_map(|section| section.keys().map(|vec| &**vec)) | |
124 | } |
|
124 | } | |
125 |
|
125 | |||
126 | pub fn is_empty(&self) -> bool { |
|
126 | pub fn is_empty(&self) -> bool { | |
127 | self.sections.is_empty() |
|
127 | self.sections.is_empty() | |
128 | } |
|
128 | } | |
129 |
|
129 | |||
130 | /// Returns a `Vec` of layers in order of precedence (so, in read order), |
|
130 | /// Returns a `Vec` of layers in order of precedence (so, in read order), | |
131 | /// recursively parsing the `%include` directives if any. |
|
131 | /// recursively parsing the `%include` directives if any. | |
132 | pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> { |
|
132 | pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> { | |
133 | let mut layers = vec![]; |
|
133 | let mut layers = vec![]; | |
134 |
|
134 | |||
135 | // Discard byte order mark if any |
|
135 | // Discard byte order mark if any | |
136 | let data = if data.starts_with(b"\xef\xbb\xbf") { |
|
136 | let data = if data.starts_with(b"\xef\xbb\xbf") { | |
137 | &data[3..] |
|
137 | &data[3..] | |
138 | } else { |
|
138 | } else { | |
139 | data |
|
139 | data | |
140 | }; |
|
140 | }; | |
141 |
|
141 | |||
142 | // TODO check if it's trusted |
|
142 | // TODO check if it's trusted | |
143 | let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned())); |
|
143 | let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned())); | |
144 |
|
144 | |||
145 | let mut lines_iter = |
|
145 | let mut lines_iter = | |
146 | data.split(|b| *b == b'\n').enumerate().peekable(); |
|
146 | data.split(|b| *b == b'\n').enumerate().peekable(); | |
147 | let mut section = b"".to_vec(); |
|
147 | let mut section = b"".to_vec(); | |
148 |
|
148 | |||
149 | while let Some((index, bytes)) = lines_iter.next() { |
|
149 | while let Some((index, bytes)) = lines_iter.next() { | |
150 | let line = Some(index + 1); |
|
150 | let line = Some(index + 1); | |
151 | if let Some(m) = INCLUDE_RE.captures(&bytes) { |
|
151 | if let Some(m) = INCLUDE_RE.captures(&bytes) { | |
152 | let filename_bytes = &m[1]; |
|
152 | let filename_bytes = &m[1]; | |
|
153 | let filename_bytes = crate::utils::expand_vars(filename_bytes); | |||
153 | // `Path::parent` only fails for the root directory, |
|
154 | // `Path::parent` only fails for the root directory, | |
154 | // which `src` canβt be since weβve managed to open it as a |
|
155 | // which `src` canβt be since weβve managed to open it as a | |
155 | // file. |
|
156 | // file. | |
156 | let dir = src |
|
157 | let dir = src | |
157 | .parent() |
|
158 | .parent() | |
158 | .expect("Path::parent fail on a file weβve read"); |
|
159 | .expect("Path::parent fail on a file weβve read"); | |
159 | // `Path::join` with an absolute argument correctly ignores the |
|
160 | // `Path::join` with an absolute argument correctly ignores the | |
160 | // base path |
|
161 | // base path | |
161 | let filename = dir.join(&get_path_from_bytes(&filename_bytes)); |
|
162 | let filename = dir.join(&get_path_from_bytes(&filename_bytes)); | |
162 | let data = std::fs::read(&filename).map_err(|io_error| { |
|
163 | let data = std::fs::read(&filename).map_err(|io_error| { | |
163 | ConfigParseError { |
|
164 | ConfigParseError { | |
164 | origin: ConfigOrigin::File(src.to_owned()), |
|
165 | origin: ConfigOrigin::File(src.to_owned()), | |
165 | line, |
|
166 | line, | |
166 | message: format_bytes!( |
|
167 | message: format_bytes!( | |
167 | b"cannot include {} ({})", |
|
168 | b"cannot include {} ({})", | |
168 | filename_bytes, |
|
169 | filename_bytes, | |
169 | format_bytes::Utf8(io_error) |
|
170 | format_bytes::Utf8(io_error) | |
170 | ), |
|
171 | ), | |
171 | } |
|
172 | } | |
172 | })?; |
|
173 | })?; | |
173 | layers.push(current_layer); |
|
174 | layers.push(current_layer); | |
174 | layers.extend(Self::parse(&filename, &data)?); |
|
175 | layers.extend(Self::parse(&filename, &data)?); | |
175 | current_layer = Self::new(ConfigOrigin::File(src.to_owned())); |
|
176 | current_layer = Self::new(ConfigOrigin::File(src.to_owned())); | |
176 | } else if let Some(_) = EMPTY_RE.captures(&bytes) { |
|
177 | } else if let Some(_) = EMPTY_RE.captures(&bytes) { | |
177 | } else if let Some(m) = SECTION_RE.captures(&bytes) { |
|
178 | } else if let Some(m) = SECTION_RE.captures(&bytes) { | |
178 | section = m[1].to_vec(); |
|
179 | section = m[1].to_vec(); | |
179 | } else if let Some(m) = ITEM_RE.captures(&bytes) { |
|
180 | } else if let Some(m) = ITEM_RE.captures(&bytes) { | |
180 | let item = m[1].to_vec(); |
|
181 | let item = m[1].to_vec(); | |
181 | let mut value = m[2].to_vec(); |
|
182 | let mut value = m[2].to_vec(); | |
182 | loop { |
|
183 | loop { | |
183 | match lines_iter.peek() { |
|
184 | match lines_iter.peek() { | |
184 | None => break, |
|
185 | None => break, | |
185 | Some((_, v)) => { |
|
186 | Some((_, v)) => { | |
186 | if let Some(_) = COMMENT_RE.captures(&v) { |
|
187 | if let Some(_) = COMMENT_RE.captures(&v) { | |
187 | } else if let Some(_) = CONT_RE.captures(&v) { |
|
188 | } else if let Some(_) = CONT_RE.captures(&v) { | |
188 | value.extend(b"\n"); |
|
189 | value.extend(b"\n"); | |
189 | value.extend(&m[1]); |
|
190 | value.extend(&m[1]); | |
190 | } else { |
|
191 | } else { | |
191 | break; |
|
192 | break; | |
192 | } |
|
193 | } | |
193 | } |
|
194 | } | |
194 | }; |
|
195 | }; | |
195 | lines_iter.next(); |
|
196 | lines_iter.next(); | |
196 | } |
|
197 | } | |
197 | current_layer.add(section.clone(), item, value, line); |
|
198 | current_layer.add(section.clone(), item, value, line); | |
198 | } else if let Some(m) = UNSET_RE.captures(&bytes) { |
|
199 | } else if let Some(m) = UNSET_RE.captures(&bytes) { | |
199 | if let Some(map) = current_layer.sections.get_mut(§ion) { |
|
200 | if let Some(map) = current_layer.sections.get_mut(§ion) { | |
200 | map.remove(&m[1]); |
|
201 | map.remove(&m[1]); | |
201 | } |
|
202 | } | |
202 | } else { |
|
203 | } else { | |
203 | let message = if bytes.starts_with(b" ") { |
|
204 | let message = if bytes.starts_with(b" ") { | |
204 | format_bytes!(b"unexpected leading whitespace: {}", bytes) |
|
205 | format_bytes!(b"unexpected leading whitespace: {}", bytes) | |
205 | } else { |
|
206 | } else { | |
206 | bytes.to_owned() |
|
207 | bytes.to_owned() | |
207 | }; |
|
208 | }; | |
208 | return Err(ConfigParseError { |
|
209 | return Err(ConfigParseError { | |
209 | origin: ConfigOrigin::File(src.to_owned()), |
|
210 | origin: ConfigOrigin::File(src.to_owned()), | |
210 | line, |
|
211 | line, | |
211 | message, |
|
212 | message, | |
212 | } |
|
213 | } | |
213 | .into()); |
|
214 | .into()); | |
214 | } |
|
215 | } | |
215 | } |
|
216 | } | |
216 | if !current_layer.is_empty() { |
|
217 | if !current_layer.is_empty() { | |
217 | layers.push(current_layer); |
|
218 | layers.push(current_layer); | |
218 | } |
|
219 | } | |
219 | Ok(layers) |
|
220 | Ok(layers) | |
220 | } |
|
221 | } | |
221 | } |
|
222 | } | |
222 |
|
223 | |||
223 | impl DisplayBytes for ConfigLayer { |
|
224 | impl DisplayBytes for ConfigLayer { | |
224 | fn display_bytes( |
|
225 | fn display_bytes( | |
225 | &self, |
|
226 | &self, | |
226 | out: &mut dyn std::io::Write, |
|
227 | out: &mut dyn std::io::Write, | |
227 | ) -> std::io::Result<()> { |
|
228 | ) -> std::io::Result<()> { | |
228 | let mut sections: Vec<_> = self.sections.iter().collect(); |
|
229 | let mut sections: Vec<_> = self.sections.iter().collect(); | |
229 | sections.sort_by(|e0, e1| e0.0.cmp(e1.0)); |
|
230 | sections.sort_by(|e0, e1| e0.0.cmp(e1.0)); | |
230 |
|
231 | |||
231 | for (section, items) in sections.into_iter() { |
|
232 | for (section, items) in sections.into_iter() { | |
232 | let mut items: Vec<_> = items.into_iter().collect(); |
|
233 | let mut items: Vec<_> = items.into_iter().collect(); | |
233 | items.sort_by(|e0, e1| e0.0.cmp(e1.0)); |
|
234 | items.sort_by(|e0, e1| e0.0.cmp(e1.0)); | |
234 |
|
235 | |||
235 | for (item, config_entry) in items { |
|
236 | for (item, config_entry) in items { | |
236 | write_bytes!( |
|
237 | write_bytes!( | |
237 | out, |
|
238 | out, | |
238 | b"{}.{}={} # {}\n", |
|
239 | b"{}.{}={} # {}\n", | |
239 | section, |
|
240 | section, | |
240 | item, |
|
241 | item, | |
241 | &config_entry.bytes, |
|
242 | &config_entry.bytes, | |
242 | &self.origin, |
|
243 | &self.origin, | |
243 | )? |
|
244 | )? | |
244 | } |
|
245 | } | |
245 | } |
|
246 | } | |
246 | Ok(()) |
|
247 | Ok(()) | |
247 | } |
|
248 | } | |
248 | } |
|
249 | } | |
249 |
|
250 | |||
250 | /// Mapping of section item to value. |
|
251 | /// Mapping of section item to value. | |
251 | /// In the following: |
|
252 | /// In the following: | |
252 | /// ```text |
|
253 | /// ```text | |
253 | /// [ui] |
|
254 | /// [ui] | |
254 | /// paginate=no |
|
255 | /// paginate=no | |
255 | /// ``` |
|
256 | /// ``` | |
256 | /// "paginate" is the section item and "no" the value. |
|
257 | /// "paginate" is the section item and "no" the value. | |
257 | pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>; |
|
258 | pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>; | |
258 |
|
259 | |||
259 | #[derive(Clone, Debug, PartialEq)] |
|
260 | #[derive(Clone, Debug, PartialEq)] | |
260 | pub struct ConfigValue { |
|
261 | pub struct ConfigValue { | |
261 | /// The raw bytes of the value (be it from the CLI, env or from a file) |
|
262 | /// The raw bytes of the value (be it from the CLI, env or from a file) | |
262 | pub bytes: Vec<u8>, |
|
263 | pub bytes: Vec<u8>, | |
263 | /// Only present if the value comes from a file, 1-indexed. |
|
264 | /// Only present if the value comes from a file, 1-indexed. | |
264 | pub line: Option<usize>, |
|
265 | pub line: Option<usize>, | |
265 | } |
|
266 | } | |
266 |
|
267 | |||
267 | #[derive(Clone, Debug)] |
|
268 | #[derive(Clone, Debug)] | |
268 | pub enum ConfigOrigin { |
|
269 | pub enum ConfigOrigin { | |
269 | /// From a configuration file |
|
270 | /// From a configuration file | |
270 | File(PathBuf), |
|
271 | File(PathBuf), | |
271 | /// From a `--config` CLI argument |
|
272 | /// From a `--config` CLI argument | |
272 | CommandLine, |
|
273 | CommandLine, | |
273 | /// From environment variables like `$PAGER` or `$EDITOR` |
|
274 | /// From environment variables like `$PAGER` or `$EDITOR` | |
274 | Environment(Vec<u8>), |
|
275 | Environment(Vec<u8>), | |
275 | /* TODO cli |
|
276 | /* TODO cli | |
276 | * TODO defaults (configitems.py) |
|
277 | * TODO defaults (configitems.py) | |
277 | * TODO extensions |
|
278 | * TODO extensions | |
278 | * TODO Python resources? |
|
279 | * TODO Python resources? | |
279 | * Others? */ |
|
280 | * Others? */ | |
280 | } |
|
281 | } | |
281 |
|
282 | |||
282 | impl DisplayBytes for ConfigOrigin { |
|
283 | impl DisplayBytes for ConfigOrigin { | |
283 | fn display_bytes( |
|
284 | fn display_bytes( | |
284 | &self, |
|
285 | &self, | |
285 | out: &mut dyn std::io::Write, |
|
286 | out: &mut dyn std::io::Write, | |
286 | ) -> std::io::Result<()> { |
|
287 | ) -> std::io::Result<()> { | |
287 | match self { |
|
288 | match self { | |
288 | ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)), |
|
289 | ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)), | |
289 | ConfigOrigin::CommandLine => out.write_all(b"--config"), |
|
290 | ConfigOrigin::CommandLine => out.write_all(b"--config"), | |
290 | ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), |
|
291 | ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), | |
291 | } |
|
292 | } | |
292 | } |
|
293 | } | |
293 | } |
|
294 | } | |
294 |
|
295 | |||
295 | #[derive(Debug)] |
|
296 | #[derive(Debug)] | |
296 | pub struct ConfigParseError { |
|
297 | pub struct ConfigParseError { | |
297 | pub origin: ConfigOrigin, |
|
298 | pub origin: ConfigOrigin, | |
298 | pub line: Option<usize>, |
|
299 | pub line: Option<usize>, | |
299 | pub message: Vec<u8>, |
|
300 | pub message: Vec<u8>, | |
300 | } |
|
301 | } | |
301 |
|
302 | |||
302 | #[derive(Debug, derive_more::From)] |
|
303 | #[derive(Debug, derive_more::From)] | |
303 | pub enum ConfigError { |
|
304 | pub enum ConfigError { | |
304 | Parse(ConfigParseError), |
|
305 | Parse(ConfigParseError), | |
305 | Other(HgError), |
|
306 | Other(HgError), | |
306 | } |
|
307 | } | |
307 |
|
308 | |||
308 | fn make_regex(pattern: &'static str) -> Regex { |
|
309 | fn make_regex(pattern: &'static str) -> Regex { | |
309 | Regex::new(pattern).expect("expected a valid regex") |
|
310 | Regex::new(pattern).expect("expected a valid regex") | |
310 | } |
|
311 | } |
@@ -1,430 +1,483 | |||||
1 | // utils module |
|
1 | // utils module | |
2 | // |
|
2 | // | |
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> |
|
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | |
4 | // |
|
4 | // | |
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | //! Contains useful functions, traits, structs, etc. for use in core. |
|
8 | //! Contains useful functions, traits, structs, etc. for use in core. | |
9 |
|
9 | |||
10 | use crate::errors::{HgError, IoErrorContext}; |
|
10 | use crate::errors::{HgError, IoErrorContext}; | |
11 | use crate::utils::hg_path::HgPath; |
|
11 | use crate::utils::hg_path::HgPath; | |
12 | use im_rc::ordmap::DiffItem; |
|
12 | use im_rc::ordmap::DiffItem; | |
13 | use im_rc::ordmap::OrdMap; |
|
13 | use im_rc::ordmap::OrdMap; | |
14 | use std::cell::Cell; |
|
14 | use std::cell::Cell; | |
15 | use std::fmt; |
|
15 | use std::fmt; | |
16 | use std::{io::Write, ops::Deref}; |
|
16 | use std::{io::Write, ops::Deref}; | |
17 |
|
17 | |||
18 | pub mod files; |
|
18 | pub mod files; | |
19 | pub mod hg_path; |
|
19 | pub mod hg_path; | |
20 | pub mod path_auditor; |
|
20 | pub mod path_auditor; | |
21 |
|
21 | |||
22 | /// Useful until rust/issues/56345 is stable |
|
22 | /// Useful until rust/issues/56345 is stable | |
23 | /// |
|
23 | /// | |
24 | /// # Examples |
|
24 | /// # Examples | |
25 | /// |
|
25 | /// | |
26 | /// ``` |
|
26 | /// ``` | |
27 | /// use crate::hg::utils::find_slice_in_slice; |
|
27 | /// use crate::hg::utils::find_slice_in_slice; | |
28 | /// |
|
28 | /// | |
29 | /// let haystack = b"This is the haystack".to_vec(); |
|
29 | /// let haystack = b"This is the haystack".to_vec(); | |
30 | /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8)); |
|
30 | /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8)); | |
31 | /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None); |
|
31 | /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None); | |
32 | /// ``` |
|
32 | /// ``` | |
33 | pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize> |
|
33 | pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize> | |
34 | where |
|
34 | where | |
35 | for<'a> &'a [T]: PartialEq, |
|
35 | for<'a> &'a [T]: PartialEq, | |
36 | { |
|
36 | { | |
37 | slice |
|
37 | slice | |
38 | .windows(needle.len()) |
|
38 | .windows(needle.len()) | |
39 | .position(|window| window == needle) |
|
39 | .position(|window| window == needle) | |
40 | } |
|
40 | } | |
41 |
|
41 | |||
42 | /// Replaces the `from` slice with the `to` slice inside the `buf` slice. |
|
42 | /// Replaces the `from` slice with the `to` slice inside the `buf` slice. | |
43 | /// |
|
43 | /// | |
44 | /// # Examples |
|
44 | /// # Examples | |
45 | /// |
|
45 | /// | |
46 | /// ``` |
|
46 | /// ``` | |
47 | /// use crate::hg::utils::replace_slice; |
|
47 | /// use crate::hg::utils::replace_slice; | |
48 | /// let mut line = b"I hate writing tests!".to_vec(); |
|
48 | /// let mut line = b"I hate writing tests!".to_vec(); | |
49 | /// replace_slice(&mut line, b"hate", b"love"); |
|
49 | /// replace_slice(&mut line, b"hate", b"love"); | |
50 | /// assert_eq!( |
|
50 | /// assert_eq!( | |
51 | /// line, |
|
51 | /// line, | |
52 | /// b"I love writing tests!".to_vec() |
|
52 | /// b"I love writing tests!".to_vec() | |
53 | /// ); |
|
53 | /// ); | |
54 | /// ``` |
|
54 | /// ``` | |
55 | pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T]) |
|
55 | pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T]) | |
56 | where |
|
56 | where | |
57 | T: Clone + PartialEq, |
|
57 | T: Clone + PartialEq, | |
58 | { |
|
58 | { | |
59 | if buf.len() < from.len() || from.len() != to.len() { |
|
59 | if buf.len() < from.len() || from.len() != to.len() { | |
60 | return; |
|
60 | return; | |
61 | } |
|
61 | } | |
62 | for i in 0..=buf.len() - from.len() { |
|
62 | for i in 0..=buf.len() - from.len() { | |
63 | if buf[i..].starts_with(from) { |
|
63 | if buf[i..].starts_with(from) { | |
64 | buf[i..(i + from.len())].clone_from_slice(to); |
|
64 | buf[i..(i + from.len())].clone_from_slice(to); | |
65 | } |
|
65 | } | |
66 | } |
|
66 | } | |
67 | } |
|
67 | } | |
68 |
|
68 | |||
69 | pub trait SliceExt { |
|
69 | pub trait SliceExt { | |
70 | fn trim_end_newlines(&self) -> &Self; |
|
70 | fn trim_end_newlines(&self) -> &Self; | |
71 | fn trim_end(&self) -> &Self; |
|
71 | fn trim_end(&self) -> &Self; | |
72 | fn trim_start(&self) -> &Self; |
|
72 | fn trim_start(&self) -> &Self; | |
73 | fn trim(&self) -> &Self; |
|
73 | fn trim(&self) -> &Self; | |
74 | fn drop_prefix(&self, needle: &Self) -> Option<&Self>; |
|
74 | fn drop_prefix(&self, needle: &Self) -> Option<&Self>; | |
75 | fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>; |
|
75 | fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>; | |
76 | } |
|
76 | } | |
77 |
|
77 | |||
78 | #[allow(clippy::trivially_copy_pass_by_ref)] |
|
78 | #[allow(clippy::trivially_copy_pass_by_ref)] | |
79 | fn is_not_whitespace(c: &u8) -> bool { |
|
79 | fn is_not_whitespace(c: &u8) -> bool { | |
80 | !(*c as char).is_whitespace() |
|
80 | !(*c as char).is_whitespace() | |
81 | } |
|
81 | } | |
82 |
|
82 | |||
83 | impl SliceExt for [u8] { |
|
83 | impl SliceExt for [u8] { | |
84 | fn trim_end_newlines(&self) -> &[u8] { |
|
84 | fn trim_end_newlines(&self) -> &[u8] { | |
85 | if let Some(last) = self.iter().rposition(|&byte| byte != b'\n') { |
|
85 | if let Some(last) = self.iter().rposition(|&byte| byte != b'\n') { | |
86 | &self[..=last] |
|
86 | &self[..=last] | |
87 | } else { |
|
87 | } else { | |
88 | &[] |
|
88 | &[] | |
89 | } |
|
89 | } | |
90 | } |
|
90 | } | |
91 | fn trim_end(&self) -> &[u8] { |
|
91 | fn trim_end(&self) -> &[u8] { | |
92 | if let Some(last) = self.iter().rposition(is_not_whitespace) { |
|
92 | if let Some(last) = self.iter().rposition(is_not_whitespace) { | |
93 | &self[..=last] |
|
93 | &self[..=last] | |
94 | } else { |
|
94 | } else { | |
95 | &[] |
|
95 | &[] | |
96 | } |
|
96 | } | |
97 | } |
|
97 | } | |
98 | fn trim_start(&self) -> &[u8] { |
|
98 | fn trim_start(&self) -> &[u8] { | |
99 | if let Some(first) = self.iter().position(is_not_whitespace) { |
|
99 | if let Some(first) = self.iter().position(is_not_whitespace) { | |
100 | &self[first..] |
|
100 | &self[first..] | |
101 | } else { |
|
101 | } else { | |
102 | &[] |
|
102 | &[] | |
103 | } |
|
103 | } | |
104 | } |
|
104 | } | |
105 |
|
105 | |||
106 | /// ``` |
|
106 | /// ``` | |
107 | /// use hg::utils::SliceExt; |
|
107 | /// use hg::utils::SliceExt; | |
108 | /// assert_eq!( |
|
108 | /// assert_eq!( | |
109 | /// b" to trim ".trim(), |
|
109 | /// b" to trim ".trim(), | |
110 | /// b"to trim" |
|
110 | /// b"to trim" | |
111 | /// ); |
|
111 | /// ); | |
112 | /// assert_eq!( |
|
112 | /// assert_eq!( | |
113 | /// b"to trim ".trim(), |
|
113 | /// b"to trim ".trim(), | |
114 | /// b"to trim" |
|
114 | /// b"to trim" | |
115 | /// ); |
|
115 | /// ); | |
116 | /// assert_eq!( |
|
116 | /// assert_eq!( | |
117 | /// b" to trim".trim(), |
|
117 | /// b" to trim".trim(), | |
118 | /// b"to trim" |
|
118 | /// b"to trim" | |
119 | /// ); |
|
119 | /// ); | |
120 | /// ``` |
|
120 | /// ``` | |
121 | fn trim(&self) -> &[u8] { |
|
121 | fn trim(&self) -> &[u8] { | |
122 | self.trim_start().trim_end() |
|
122 | self.trim_start().trim_end() | |
123 | } |
|
123 | } | |
124 |
|
124 | |||
125 | fn drop_prefix(&self, needle: &Self) -> Option<&Self> { |
|
125 | fn drop_prefix(&self, needle: &Self) -> Option<&Self> { | |
126 | if self.starts_with(needle) { |
|
126 | if self.starts_with(needle) { | |
127 | Some(&self[needle.len()..]) |
|
127 | Some(&self[needle.len()..]) | |
128 | } else { |
|
128 | } else { | |
129 | None |
|
129 | None | |
130 | } |
|
130 | } | |
131 | } |
|
131 | } | |
132 |
|
132 | |||
133 | fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> { |
|
133 | fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> { | |
134 | let mut iter = self.splitn(2, |&byte| byte == separator); |
|
134 | let mut iter = self.splitn(2, |&byte| byte == separator); | |
135 | let a = iter.next()?; |
|
135 | let a = iter.next()?; | |
136 | let b = iter.next()?; |
|
136 | let b = iter.next()?; | |
137 | Some((a, b)) |
|
137 | Some((a, b)) | |
138 | } |
|
138 | } | |
139 | } |
|
139 | } | |
140 |
|
140 | |||
141 | pub trait Escaped { |
|
141 | pub trait Escaped { | |
142 | /// Return bytes escaped for display to the user |
|
142 | /// Return bytes escaped for display to the user | |
143 | fn escaped_bytes(&self) -> Vec<u8>; |
|
143 | fn escaped_bytes(&self) -> Vec<u8>; | |
144 | } |
|
144 | } | |
145 |
|
145 | |||
146 | impl Escaped for u8 { |
|
146 | impl Escaped for u8 { | |
147 | fn escaped_bytes(&self) -> Vec<u8> { |
|
147 | fn escaped_bytes(&self) -> Vec<u8> { | |
148 | let mut acc = vec![]; |
|
148 | let mut acc = vec![]; | |
149 | match self { |
|
149 | match self { | |
150 | c @ b'\'' | c @ b'\\' => { |
|
150 | c @ b'\'' | c @ b'\\' => { | |
151 | acc.push(b'\\'); |
|
151 | acc.push(b'\\'); | |
152 | acc.push(*c); |
|
152 | acc.push(*c); | |
153 | } |
|
153 | } | |
154 | b'\t' => { |
|
154 | b'\t' => { | |
155 | acc.extend(br"\\t"); |
|
155 | acc.extend(br"\\t"); | |
156 | } |
|
156 | } | |
157 | b'\n' => { |
|
157 | b'\n' => { | |
158 | acc.extend(br"\\n"); |
|
158 | acc.extend(br"\\n"); | |
159 | } |
|
159 | } | |
160 | b'\r' => { |
|
160 | b'\r' => { | |
161 | acc.extend(br"\\r"); |
|
161 | acc.extend(br"\\r"); | |
162 | } |
|
162 | } | |
163 | c if (*c < b' ' || *c >= 127) => { |
|
163 | c if (*c < b' ' || *c >= 127) => { | |
164 | write!(acc, "\\x{:x}", self).unwrap(); |
|
164 | write!(acc, "\\x{:x}", self).unwrap(); | |
165 | } |
|
165 | } | |
166 | c => { |
|
166 | c => { | |
167 | acc.push(*c); |
|
167 | acc.push(*c); | |
168 | } |
|
168 | } | |
169 | } |
|
169 | } | |
170 | acc |
|
170 | acc | |
171 | } |
|
171 | } | |
172 | } |
|
172 | } | |
173 |
|
173 | |||
174 | impl<'a, T: Escaped> Escaped for &'a [T] { |
|
174 | impl<'a, T: Escaped> Escaped for &'a [T] { | |
175 | fn escaped_bytes(&self) -> Vec<u8> { |
|
175 | fn escaped_bytes(&self) -> Vec<u8> { | |
176 | self.iter().flat_map(Escaped::escaped_bytes).collect() |
|
176 | self.iter().flat_map(Escaped::escaped_bytes).collect() | |
177 | } |
|
177 | } | |
178 | } |
|
178 | } | |
179 |
|
179 | |||
180 | impl<T: Escaped> Escaped for Vec<T> { |
|
180 | impl<T: Escaped> Escaped for Vec<T> { | |
181 | fn escaped_bytes(&self) -> Vec<u8> { |
|
181 | fn escaped_bytes(&self) -> Vec<u8> { | |
182 | self.deref().escaped_bytes() |
|
182 | self.deref().escaped_bytes() | |
183 | } |
|
183 | } | |
184 | } |
|
184 | } | |
185 |
|
185 | |||
186 | impl<'a> Escaped for &'a HgPath { |
|
186 | impl<'a> Escaped for &'a HgPath { | |
187 | fn escaped_bytes(&self) -> Vec<u8> { |
|
187 | fn escaped_bytes(&self) -> Vec<u8> { | |
188 | self.as_bytes().escaped_bytes() |
|
188 | self.as_bytes().escaped_bytes() | |
189 | } |
|
189 | } | |
190 | } |
|
190 | } | |
191 |
|
191 | |||
192 | // TODO: use the str method when we require Rust 1.45 |
|
192 | // TODO: use the str method when we require Rust 1.45 | |
193 | pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> { |
|
193 | pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> { | |
194 | if s.ends_with(suffix) { |
|
194 | if s.ends_with(suffix) { | |
195 | Some(&s[..s.len() - suffix.len()]) |
|
195 | Some(&s[..s.len() - suffix.len()]) | |
196 | } else { |
|
196 | } else { | |
197 | None |
|
197 | None | |
198 | } |
|
198 | } | |
199 | } |
|
199 | } | |
200 |
|
200 | |||
201 | #[cfg(unix)] |
|
201 | #[cfg(unix)] | |
202 | pub fn shell_quote(value: &[u8]) -> Vec<u8> { |
|
202 | pub fn shell_quote(value: &[u8]) -> Vec<u8> { | |
203 | // TODO: Use the `matches!` macro when we require Rust 1.42+ |
|
203 | // TODO: Use the `matches!` macro when we require Rust 1.42+ | |
204 | if value.iter().all(|&byte| match byte { |
|
204 | if value.iter().all(|&byte| match byte { | |
205 | b'a'..=b'z' |
|
205 | b'a'..=b'z' | |
206 | | b'A'..=b'Z' |
|
206 | | b'A'..=b'Z' | |
207 | | b'0'..=b'9' |
|
207 | | b'0'..=b'9' | |
208 | | b'.' |
|
208 | | b'.' | |
209 | | b'_' |
|
209 | | b'_' | |
210 | | b'/' |
|
210 | | b'/' | |
211 | | b'+' |
|
211 | | b'+' | |
212 | | b'-' => true, |
|
212 | | b'-' => true, | |
213 | _ => false, |
|
213 | _ => false, | |
214 | }) { |
|
214 | }) { | |
215 | value.to_owned() |
|
215 | value.to_owned() | |
216 | } else { |
|
216 | } else { | |
217 | let mut quoted = Vec::with_capacity(value.len() + 2); |
|
217 | let mut quoted = Vec::with_capacity(value.len() + 2); | |
218 | quoted.push(b'\''); |
|
218 | quoted.push(b'\''); | |
219 | for &byte in value { |
|
219 | for &byte in value { | |
220 | if byte == b'\'' { |
|
220 | if byte == b'\'' { | |
221 | quoted.push(b'\\'); |
|
221 | quoted.push(b'\\'); | |
222 | } |
|
222 | } | |
223 | quoted.push(byte); |
|
223 | quoted.push(byte); | |
224 | } |
|
224 | } | |
225 | quoted.push(b'\''); |
|
225 | quoted.push(b'\''); | |
226 | quoted |
|
226 | quoted | |
227 | } |
|
227 | } | |
228 | } |
|
228 | } | |
229 |
|
229 | |||
230 | pub fn current_dir() -> Result<std::path::PathBuf, HgError> { |
|
230 | pub fn current_dir() -> Result<std::path::PathBuf, HgError> { | |
231 | std::env::current_dir().map_err(|error| HgError::IoError { |
|
231 | std::env::current_dir().map_err(|error| HgError::IoError { | |
232 | error, |
|
232 | error, | |
233 | context: IoErrorContext::CurrentDir, |
|
233 | context: IoErrorContext::CurrentDir, | |
234 | }) |
|
234 | }) | |
235 | } |
|
235 | } | |
236 |
|
236 | |||
237 | pub fn current_exe() -> Result<std::path::PathBuf, HgError> { |
|
237 | pub fn current_exe() -> Result<std::path::PathBuf, HgError> { | |
238 | std::env::current_exe().map_err(|error| HgError::IoError { |
|
238 | std::env::current_exe().map_err(|error| HgError::IoError { | |
239 | error, |
|
239 | error, | |
240 | context: IoErrorContext::CurrentExe, |
|
240 | context: IoErrorContext::CurrentExe, | |
241 | }) |
|
241 | }) | |
242 | } |
|
242 | } | |
243 |
|
243 | |||
|
244 | /// Expand `$FOO` and `${FOO}` environment variables in the given byte string | |||
|
245 | pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> { | |||
|
246 | lazy_static::lazy_static! { | |||
|
247 | /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301 | |||
|
248 | /// The `x` makes whitespace ignored. | |||
|
249 | /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag. | |||
|
250 | static ref VAR_RE: regex::bytes::Regex = | |||
|
251 | regex::bytes::Regex::new(r"(?x-u) | |||
|
252 | \$ | |||
|
253 | (?: | |||
|
254 | (\w+) | |||
|
255 | | | |||
|
256 | \{ | |||
|
257 | ([^}]*) | |||
|
258 | \} | |||
|
259 | ) | |||
|
260 | ").unwrap(); | |||
|
261 | } | |||
|
262 | VAR_RE.replace_all(s, |captures: ®ex::bytes::Captures| { | |||
|
263 | let var_name = files::get_os_str_from_bytes( | |||
|
264 | captures | |||
|
265 | .get(1) | |||
|
266 | .or_else(|| captures.get(2)) | |||
|
267 | .expect("either side of `|` must participate in match") | |||
|
268 | .as_bytes(), | |||
|
269 | ); | |||
|
270 | std::env::var_os(var_name) | |||
|
271 | .map(files::get_bytes_from_os_str) | |||
|
272 | .unwrap_or_else(|| { | |||
|
273 | // Referencing an environment variable that does not exist. | |||
|
274 | // Leave the $FOO reference as-is. | |||
|
275 | captures[0].to_owned() | |||
|
276 | }) | |||
|
277 | }) | |||
|
278 | } | |||
|
279 | ||||
|
280 | #[test] | |||
|
281 | fn test_expand_vars() { | |||
|
282 | // Modifying process-global state in a test isnβt great, | |||
|
283 | // but hopefully this wonβt collide with anything. | |||
|
284 | std::env::set_var("TEST_EXPAND_VAR", "1"); | |||
|
285 | assert_eq!( | |||
|
286 | expand_vars(b"before/$TEST_EXPAND_VAR/after"), | |||
|
287 | &b"before/1/after"[..] | |||
|
288 | ); | |||
|
289 | assert_eq!( | |||
|
290 | expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"), | |||
|
291 | &b"before111after"[..] | |||
|
292 | ); | |||
|
293 | let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after"; | |||
|
294 | assert_eq!(expand_vars(s), &s[..]); | |||
|
295 | } | |||
|
296 | ||||
244 | pub(crate) enum MergeResult<V> { |
|
297 | pub(crate) enum MergeResult<V> { | |
245 | UseLeftValue, |
|
298 | UseLeftValue, | |
246 | UseRightValue, |
|
299 | UseRightValue, | |
247 | UseNewValue(V), |
|
300 | UseNewValue(V), | |
248 | } |
|
301 | } | |
249 |
|
302 | |||
250 | /// Return the union of the two given maps, |
|
303 | /// Return the union of the two given maps, | |
251 | /// calling `merge(key, left_value, right_value)` to resolve keys that exist in |
|
304 | /// calling `merge(key, left_value, right_value)` to resolve keys that exist in | |
252 | /// both. |
|
305 | /// both. | |
253 | /// |
|
306 | /// | |
254 | /// CC https://github.com/bodil/im-rs/issues/166 |
|
307 | /// CC https://github.com/bodil/im-rs/issues/166 | |
255 | pub(crate) fn ordmap_union_with_merge<K, V>( |
|
308 | pub(crate) fn ordmap_union_with_merge<K, V>( | |
256 | left: OrdMap<K, V>, |
|
309 | left: OrdMap<K, V>, | |
257 | right: OrdMap<K, V>, |
|
310 | right: OrdMap<K, V>, | |
258 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, |
|
311 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, | |
259 | ) -> OrdMap<K, V> |
|
312 | ) -> OrdMap<K, V> | |
260 | where |
|
313 | where | |
261 | K: Clone + Ord, |
|
314 | K: Clone + Ord, | |
262 | V: Clone + PartialEq, |
|
315 | V: Clone + PartialEq, | |
263 | { |
|
316 | { | |
264 | if left.ptr_eq(&right) { |
|
317 | if left.ptr_eq(&right) { | |
265 | // One of the two maps is an unmodified clone of the other |
|
318 | // One of the two maps is an unmodified clone of the other | |
266 | left |
|
319 | left | |
267 | } else if left.len() / 2 > right.len() { |
|
320 | } else if left.len() / 2 > right.len() { | |
268 | // When two maps have different sizes, |
|
321 | // When two maps have different sizes, | |
269 | // their size difference is a lower bound on |
|
322 | // their size difference is a lower bound on | |
270 | // how many keys of the larger map are not also in the smaller map. |
|
323 | // how many keys of the larger map are not also in the smaller map. | |
271 | // This in turn is a lower bound on the number of differences in |
|
324 | // This in turn is a lower bound on the number of differences in | |
272 | // `OrdMap::diff` and the "amount of work" that would be done |
|
325 | // `OrdMap::diff` and the "amount of work" that would be done | |
273 | // by `ordmap_union_with_merge_by_diff`. |
|
326 | // by `ordmap_union_with_merge_by_diff`. | |
274 | // |
|
327 | // | |
275 | // Here `left` is more than twice the size of `right`, |
|
328 | // Here `left` is more than twice the size of `right`, | |
276 | // so the number of differences is more than the total size of |
|
329 | // so the number of differences is more than the total size of | |
277 | // `right`. Therefore an algorithm based on iterating `right` |
|
330 | // `right`. Therefore an algorithm based on iterating `right` | |
278 | // is more efficient. |
|
331 | // is more efficient. | |
279 | // |
|
332 | // | |
280 | // This helps a lot when a tiny (or empty) map is merged |
|
333 | // This helps a lot when a tiny (or empty) map is merged | |
281 | // with a large one. |
|
334 | // with a large one. | |
282 | ordmap_union_with_merge_by_iter(left, right, merge) |
|
335 | ordmap_union_with_merge_by_iter(left, right, merge) | |
283 | } else if left.len() < right.len() / 2 { |
|
336 | } else if left.len() < right.len() / 2 { | |
284 | // Same as above but with `left` and `right` swapped |
|
337 | // Same as above but with `left` and `right` swapped | |
285 | ordmap_union_with_merge_by_iter(right, left, |key, a, b| { |
|
338 | ordmap_union_with_merge_by_iter(right, left, |key, a, b| { | |
286 | // Also swapped in `merge` arguments: |
|
339 | // Also swapped in `merge` arguments: | |
287 | match merge(key, b, a) { |
|
340 | match merge(key, b, a) { | |
288 | MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v), |
|
341 | MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v), | |
289 | // β¦ and swap back in `merge` result: |
|
342 | // β¦ and swap back in `merge` result: | |
290 | MergeResult::UseLeftValue => MergeResult::UseRightValue, |
|
343 | MergeResult::UseLeftValue => MergeResult::UseRightValue, | |
291 | MergeResult::UseRightValue => MergeResult::UseLeftValue, |
|
344 | MergeResult::UseRightValue => MergeResult::UseLeftValue, | |
292 | } |
|
345 | } | |
293 | }) |
|
346 | }) | |
294 | } else { |
|
347 | } else { | |
295 | // For maps of similar size, use the algorithm based on `OrdMap::diff` |
|
348 | // For maps of similar size, use the algorithm based on `OrdMap::diff` | |
296 | ordmap_union_with_merge_by_diff(left, right, merge) |
|
349 | ordmap_union_with_merge_by_diff(left, right, merge) | |
297 | } |
|
350 | } | |
298 | } |
|
351 | } | |
299 |
|
352 | |||
300 | /// Efficient if `right` is much smaller than `left` |
|
353 | /// Efficient if `right` is much smaller than `left` | |
301 | fn ordmap_union_with_merge_by_iter<K, V>( |
|
354 | fn ordmap_union_with_merge_by_iter<K, V>( | |
302 | mut left: OrdMap<K, V>, |
|
355 | mut left: OrdMap<K, V>, | |
303 | right: OrdMap<K, V>, |
|
356 | right: OrdMap<K, V>, | |
304 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, |
|
357 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, | |
305 | ) -> OrdMap<K, V> |
|
358 | ) -> OrdMap<K, V> | |
306 | where |
|
359 | where | |
307 | K: Clone + Ord, |
|
360 | K: Clone + Ord, | |
308 | V: Clone, |
|
361 | V: Clone, | |
309 | { |
|
362 | { | |
310 | for (key, right_value) in right { |
|
363 | for (key, right_value) in right { | |
311 | match left.get(&key) { |
|
364 | match left.get(&key) { | |
312 | None => { |
|
365 | None => { | |
313 | left.insert(key, right_value); |
|
366 | left.insert(key, right_value); | |
314 | } |
|
367 | } | |
315 | Some(left_value) => match merge(&key, left_value, &right_value) { |
|
368 | Some(left_value) => match merge(&key, left_value, &right_value) { | |
316 | MergeResult::UseLeftValue => {} |
|
369 | MergeResult::UseLeftValue => {} | |
317 | MergeResult::UseRightValue => { |
|
370 | MergeResult::UseRightValue => { | |
318 | left.insert(key, right_value); |
|
371 | left.insert(key, right_value); | |
319 | } |
|
372 | } | |
320 | MergeResult::UseNewValue(new_value) => { |
|
373 | MergeResult::UseNewValue(new_value) => { | |
321 | left.insert(key, new_value); |
|
374 | left.insert(key, new_value); | |
322 | } |
|
375 | } | |
323 | }, |
|
376 | }, | |
324 | } |
|
377 | } | |
325 | } |
|
378 | } | |
326 | left |
|
379 | left | |
327 | } |
|
380 | } | |
328 |
|
381 | |||
329 | /// Fallback when both maps are of similar size |
|
382 | /// Fallback when both maps are of similar size | |
330 | fn ordmap_union_with_merge_by_diff<K, V>( |
|
383 | fn ordmap_union_with_merge_by_diff<K, V>( | |
331 | mut left: OrdMap<K, V>, |
|
384 | mut left: OrdMap<K, V>, | |
332 | mut right: OrdMap<K, V>, |
|
385 | mut right: OrdMap<K, V>, | |
333 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, |
|
386 | mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>, | |
334 | ) -> OrdMap<K, V> |
|
387 | ) -> OrdMap<K, V> | |
335 | where |
|
388 | where | |
336 | K: Clone + Ord, |
|
389 | K: Clone + Ord, | |
337 | V: Clone + PartialEq, |
|
390 | V: Clone + PartialEq, | |
338 | { |
|
391 | { | |
339 | // (key, value) pairs that would need to be inserted in either map |
|
392 | // (key, value) pairs that would need to be inserted in either map | |
340 | // in order to turn it into the union. |
|
393 | // in order to turn it into the union. | |
341 | // |
|
394 | // | |
342 | // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted, |
|
395 | // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted, | |
343 | // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>` |
|
396 | // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>` | |
344 | // with `left_updates` only borrowing from `right` and `right_updates` from |
|
397 | // with `left_updates` only borrowing from `right` and `right_updates` from | |
345 | // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`. |
|
398 | // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`. | |
346 | // |
|
399 | // | |
347 | // This would allow moving all `.clone()` calls to after weβve decided |
|
400 | // This would allow moving all `.clone()` calls to after weβve decided | |
348 | // which of `right_updates` or `left_updates` to use |
|
401 | // which of `right_updates` or `left_updates` to use | |
349 | // (value ones becoming `Cow::into_owned`), |
|
402 | // (value ones becoming `Cow::into_owned`), | |
350 | // and avoid making clones we donβt end up using. |
|
403 | // and avoid making clones we donβt end up using. | |
351 | let mut left_updates = Vec::new(); |
|
404 | let mut left_updates = Vec::new(); | |
352 | let mut right_updates = Vec::new(); |
|
405 | let mut right_updates = Vec::new(); | |
353 |
|
406 | |||
354 | for difference in left.diff(&right) { |
|
407 | for difference in left.diff(&right) { | |
355 | match difference { |
|
408 | match difference { | |
356 | DiffItem::Add(key, value) => { |
|
409 | DiffItem::Add(key, value) => { | |
357 | left_updates.push((key.clone(), value.clone())) |
|
410 | left_updates.push((key.clone(), value.clone())) | |
358 | } |
|
411 | } | |
359 | DiffItem::Remove(key, value) => { |
|
412 | DiffItem::Remove(key, value) => { | |
360 | right_updates.push((key.clone(), value.clone())) |
|
413 | right_updates.push((key.clone(), value.clone())) | |
361 | } |
|
414 | } | |
362 | DiffItem::Update { |
|
415 | DiffItem::Update { | |
363 | old: (key, left_value), |
|
416 | old: (key, left_value), | |
364 | new: (_, right_value), |
|
417 | new: (_, right_value), | |
365 | } => match merge(key, left_value, right_value) { |
|
418 | } => match merge(key, left_value, right_value) { | |
366 | MergeResult::UseLeftValue => { |
|
419 | MergeResult::UseLeftValue => { | |
367 | right_updates.push((key.clone(), left_value.clone())) |
|
420 | right_updates.push((key.clone(), left_value.clone())) | |
368 | } |
|
421 | } | |
369 | MergeResult::UseRightValue => { |
|
422 | MergeResult::UseRightValue => { | |
370 | left_updates.push((key.clone(), right_value.clone())) |
|
423 | left_updates.push((key.clone(), right_value.clone())) | |
371 | } |
|
424 | } | |
372 | MergeResult::UseNewValue(new_value) => { |
|
425 | MergeResult::UseNewValue(new_value) => { | |
373 | left_updates.push((key.clone(), new_value.clone())); |
|
426 | left_updates.push((key.clone(), new_value.clone())); | |
374 | right_updates.push((key.clone(), new_value)) |
|
427 | right_updates.push((key.clone(), new_value)) | |
375 | } |
|
428 | } | |
376 | }, |
|
429 | }, | |
377 | } |
|
430 | } | |
378 | } |
|
431 | } | |
379 | if left_updates.len() < right_updates.len() { |
|
432 | if left_updates.len() < right_updates.len() { | |
380 | for (key, value) in left_updates { |
|
433 | for (key, value) in left_updates { | |
381 | left.insert(key, value); |
|
434 | left.insert(key, value); | |
382 | } |
|
435 | } | |
383 | left |
|
436 | left | |
384 | } else { |
|
437 | } else { | |
385 | for (key, value) in right_updates { |
|
438 | for (key, value) in right_updates { | |
386 | right.insert(key, value); |
|
439 | right.insert(key, value); | |
387 | } |
|
440 | } | |
388 | right |
|
441 | right | |
389 | } |
|
442 | } | |
390 | } |
|
443 | } | |
391 |
|
444 | |||
392 | /// Join items of the iterable with the given separator, similar to Pythonβs |
|
445 | /// Join items of the iterable with the given separator, similar to Pythonβs | |
393 | /// `separator.join(iter)`. |
|
446 | /// `separator.join(iter)`. | |
394 | /// |
|
447 | /// | |
395 | /// Formatting the return value consumes the iterator. |
|
448 | /// Formatting the return value consumes the iterator. | |
396 | /// Formatting it again will produce an empty string. |
|
449 | /// Formatting it again will produce an empty string. | |
397 | pub fn join_display( |
|
450 | pub fn join_display( | |
398 | iter: impl IntoIterator<Item = impl fmt::Display>, |
|
451 | iter: impl IntoIterator<Item = impl fmt::Display>, | |
399 | separator: impl fmt::Display, |
|
452 | separator: impl fmt::Display, | |
400 | ) -> impl fmt::Display { |
|
453 | ) -> impl fmt::Display { | |
401 | JoinDisplay { |
|
454 | JoinDisplay { | |
402 | iter: Cell::new(Some(iter.into_iter())), |
|
455 | iter: Cell::new(Some(iter.into_iter())), | |
403 | separator, |
|
456 | separator, | |
404 | } |
|
457 | } | |
405 | } |
|
458 | } | |
406 |
|
459 | |||
407 | struct JoinDisplay<I, S> { |
|
460 | struct JoinDisplay<I, S> { | |
408 | iter: Cell<Option<I>>, |
|
461 | iter: Cell<Option<I>>, | |
409 | separator: S, |
|
462 | separator: S, | |
410 | } |
|
463 | } | |
411 |
|
464 | |||
412 | impl<I, T, S> fmt::Display for JoinDisplay<I, S> |
|
465 | impl<I, T, S> fmt::Display for JoinDisplay<I, S> | |
413 | where |
|
466 | where | |
414 | I: Iterator<Item = T>, |
|
467 | I: Iterator<Item = T>, | |
415 | T: fmt::Display, |
|
468 | T: fmt::Display, | |
416 | S: fmt::Display, |
|
469 | S: fmt::Display, | |
417 | { |
|
470 | { | |
418 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
471 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
419 | if let Some(mut iter) = self.iter.take() { |
|
472 | if let Some(mut iter) = self.iter.take() { | |
420 | if let Some(first) = iter.next() { |
|
473 | if let Some(first) = iter.next() { | |
421 | first.fmt(f)?; |
|
474 | first.fmt(f)?; | |
422 | } |
|
475 | } | |
423 | for value in iter { |
|
476 | for value in iter { | |
424 | self.separator.fmt(f)?; |
|
477 | self.separator.fmt(f)?; | |
425 | value.fmt(f)?; |
|
478 | value.fmt(f)?; | |
426 | } |
|
479 | } | |
427 | } |
|
480 | } | |
428 | Ok(()) |
|
481 | Ok(()) | |
429 | } |
|
482 | } | |
430 | } |
|
483 | } |
@@ -1,448 +1,451 | |||||
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::ffi::OsStr; | |
21 | use std::fs::Metadata; |
|
21 | use std::fs::Metadata; | |
22 | use std::iter::FusedIterator; |
|
22 | use std::iter::FusedIterator; | |
23 | use std::ops::Deref; |
|
23 | use std::ops::Deref; | |
24 | use std::path::{Path, PathBuf}; |
|
24 | use std::path::{Path, PathBuf}; | |
25 |
|
25 | |||
26 |
pub fn get_ |
|
26 | pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr { | |
27 | let os_str; |
|
27 | let os_str; | |
28 | #[cfg(unix)] |
|
28 | #[cfg(unix)] | |
29 | { |
|
29 | { | |
30 | use std::os::unix::ffi::OsStrExt; |
|
30 | use std::os::unix::ffi::OsStrExt; | |
31 | os_str = std::ffi::OsStr::from_bytes(bytes); |
|
31 | os_str = std::ffi::OsStr::from_bytes(bytes); | |
32 | } |
|
32 | } | |
33 | // TODO Handle other platforms |
|
33 | // TODO Handle other platforms | |
34 | // TODO: convert from WTF8 to Windows MBCS (ANSI encoding). |
|
34 | // TODO: convert from WTF8 to Windows MBCS (ANSI encoding). | |
35 | // Perhaps, the return type would have to be Result<PathBuf>. |
|
35 | // Perhaps, the return type would have to be Result<PathBuf>. | |
|
36 | os_str | |||
|
37 | } | |||
36 |
|
38 | |||
37 | Path::new(os_str) |
|
39 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { | |
|
40 | Path::new(get_os_str_from_bytes(bytes)) | |||
38 | } |
|
41 | } | |
39 |
|
42 | |||
40 | // TODO: need to convert from WTF8 to MBCS bytes on Windows. |
|
43 | // TODO: need to convert from WTF8 to MBCS bytes on Windows. | |
41 | // that's why Vec<u8> is returned. |
|
44 | // that's why Vec<u8> is returned. | |
42 | #[cfg(unix)] |
|
45 | #[cfg(unix)] | |
43 | pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> { |
|
46 | pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> { | |
44 | get_bytes_from_os_str(path.as_ref()) |
|
47 | get_bytes_from_os_str(path.as_ref()) | |
45 | } |
|
48 | } | |
46 |
|
49 | |||
47 | #[cfg(unix)] |
|
50 | #[cfg(unix)] | |
48 | pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> { |
|
51 | pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> { | |
49 | use std::os::unix::ffi::OsStrExt; |
|
52 | use std::os::unix::ffi::OsStrExt; | |
50 | str.as_ref().as_bytes().to_vec() |
|
53 | str.as_ref().as_bytes().to_vec() | |
51 | } |
|
54 | } | |
52 |
|
55 | |||
53 | /// An iterator over repository path yielding itself and its ancestors. |
|
56 | /// An iterator over repository path yielding itself and its ancestors. | |
54 | #[derive(Copy, Clone, Debug)] |
|
57 | #[derive(Copy, Clone, Debug)] | |
55 | pub struct Ancestors<'a> { |
|
58 | pub struct Ancestors<'a> { | |
56 | next: Option<&'a HgPath>, |
|
59 | next: Option<&'a HgPath>, | |
57 | } |
|
60 | } | |
58 |
|
61 | |||
59 | impl<'a> Iterator for Ancestors<'a> { |
|
62 | impl<'a> Iterator for Ancestors<'a> { | |
60 | type Item = &'a HgPath; |
|
63 | type Item = &'a HgPath; | |
61 |
|
64 | |||
62 | fn next(&mut self) -> Option<Self::Item> { |
|
65 | fn next(&mut self) -> Option<Self::Item> { | |
63 | let next = self.next; |
|
66 | let next = self.next; | |
64 | self.next = match self.next { |
|
67 | self.next = match self.next { | |
65 | Some(s) if s.is_empty() => None, |
|
68 | Some(s) if s.is_empty() => None, | |
66 | Some(s) => { |
|
69 | Some(s) => { | |
67 | let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0); |
|
70 | let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0); | |
68 | Some(HgPath::new(&s.as_bytes()[..p])) |
|
71 | Some(HgPath::new(&s.as_bytes()[..p])) | |
69 | } |
|
72 | } | |
70 | None => None, |
|
73 | None => None, | |
71 | }; |
|
74 | }; | |
72 | next |
|
75 | next | |
73 | } |
|
76 | } | |
74 | } |
|
77 | } | |
75 |
|
78 | |||
76 | impl<'a> FusedIterator for Ancestors<'a> {} |
|
79 | impl<'a> FusedIterator for Ancestors<'a> {} | |
77 |
|
80 | |||
78 | /// An iterator over repository path yielding itself and its ancestors. |
|
81 | /// An iterator over repository path yielding itself and its ancestors. | |
79 | #[derive(Copy, Clone, Debug)] |
|
82 | #[derive(Copy, Clone, Debug)] | |
80 | pub(crate) struct AncestorsWithBase<'a> { |
|
83 | pub(crate) struct AncestorsWithBase<'a> { | |
81 | next: Option<(&'a HgPath, &'a HgPath)>, |
|
84 | next: Option<(&'a HgPath, &'a HgPath)>, | |
82 | } |
|
85 | } | |
83 |
|
86 | |||
84 | impl<'a> Iterator for AncestorsWithBase<'a> { |
|
87 | impl<'a> Iterator for AncestorsWithBase<'a> { | |
85 | type Item = (&'a HgPath, &'a HgPath); |
|
88 | type Item = (&'a HgPath, &'a HgPath); | |
86 |
|
89 | |||
87 | fn next(&mut self) -> Option<Self::Item> { |
|
90 | fn next(&mut self) -> Option<Self::Item> { | |
88 | let next = self.next; |
|
91 | let next = self.next; | |
89 | self.next = match self.next { |
|
92 | self.next = match self.next { | |
90 | Some((s, _)) if s.is_empty() => None, |
|
93 | Some((s, _)) if s.is_empty() => None, | |
91 | Some((s, _)) => Some(s.split_filename()), |
|
94 | Some((s, _)) => Some(s.split_filename()), | |
92 | None => None, |
|
95 | None => None, | |
93 | }; |
|
96 | }; | |
94 | next |
|
97 | next | |
95 | } |
|
98 | } | |
96 | } |
|
99 | } | |
97 |
|
100 | |||
98 | impl<'a> FusedIterator for AncestorsWithBase<'a> {} |
|
101 | impl<'a> FusedIterator for AncestorsWithBase<'a> {} | |
99 |
|
102 | |||
100 | /// Returns an iterator yielding ancestor directories of the given repository |
|
103 | /// Returns an iterator yielding ancestor directories of the given repository | |
101 | /// path. |
|
104 | /// path. | |
102 | /// |
|
105 | /// | |
103 | /// The path is separated by '/', and must not start with '/'. |
|
106 | /// The path is separated by '/', and must not start with '/'. | |
104 | /// |
|
107 | /// | |
105 | /// The path itself isn't included unless it is b"" (meaning the root |
|
108 | /// The path itself isn't included unless it is b"" (meaning the root | |
106 | /// directory.) |
|
109 | /// directory.) | |
107 | pub fn find_dirs(path: &HgPath) -> Ancestors { |
|
110 | pub fn find_dirs(path: &HgPath) -> Ancestors { | |
108 | let mut dirs = Ancestors { next: Some(path) }; |
|
111 | let mut dirs = Ancestors { next: Some(path) }; | |
109 | if !path.is_empty() { |
|
112 | if !path.is_empty() { | |
110 | dirs.next(); // skip itself |
|
113 | dirs.next(); // skip itself | |
111 | } |
|
114 | } | |
112 | dirs |
|
115 | dirs | |
113 | } |
|
116 | } | |
114 |
|
117 | |||
115 | /// Returns an iterator yielding ancestor directories of the given repository |
|
118 | /// Returns an iterator yielding ancestor directories of the given repository | |
116 | /// path. |
|
119 | /// path. | |
117 | /// |
|
120 | /// | |
118 | /// The path is separated by '/', and must not start with '/'. |
|
121 | /// The path is separated by '/', and must not start with '/'. | |
119 | /// |
|
122 | /// | |
120 | /// The path itself isn't included unless it is b"" (meaning the root |
|
123 | /// The path itself isn't included unless it is b"" (meaning the root | |
121 | /// directory.) |
|
124 | /// directory.) | |
122 | pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase { |
|
125 | pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase { | |
123 | let mut dirs = AncestorsWithBase { |
|
126 | let mut dirs = AncestorsWithBase { | |
124 | next: Some((path, HgPath::new(b""))), |
|
127 | next: Some((path, HgPath::new(b""))), | |
125 | }; |
|
128 | }; | |
126 | if !path.is_empty() { |
|
129 | if !path.is_empty() { | |
127 | dirs.next(); // skip itself |
|
130 | dirs.next(); // skip itself | |
128 | } |
|
131 | } | |
129 | dirs |
|
132 | dirs | |
130 | } |
|
133 | } | |
131 |
|
134 | |||
132 | /// TODO more than ASCII? |
|
135 | /// TODO more than ASCII? | |
133 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { |
|
136 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { | |
134 | #[cfg(windows)] // NTFS compares via upper() |
|
137 | #[cfg(windows)] // NTFS compares via upper() | |
135 | return path.to_ascii_uppercase(); |
|
138 | return path.to_ascii_uppercase(); | |
136 | #[cfg(unix)] |
|
139 | #[cfg(unix)] | |
137 | path.to_ascii_lowercase() |
|
140 | path.to_ascii_lowercase() | |
138 | } |
|
141 | } | |
139 |
|
142 | |||
140 | lazy_static! { |
|
143 | lazy_static! { | |
141 | static ref IGNORED_CHARS: Vec<Vec<u8>> = { |
|
144 | static ref IGNORED_CHARS: Vec<Vec<u8>> = { | |
142 | [ |
|
145 | [ | |
143 | 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, |
|
146 | 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, | |
144 | 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff, |
|
147 | 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff, | |
145 | ] |
|
148 | ] | |
146 | .iter() |
|
149 | .iter() | |
147 | .map(|code| { |
|
150 | .map(|code| { | |
148 | std::char::from_u32(*code) |
|
151 | std::char::from_u32(*code) | |
149 | .unwrap() |
|
152 | .unwrap() | |
150 | .encode_utf8(&mut [0; 3]) |
|
153 | .encode_utf8(&mut [0; 3]) | |
151 | .bytes() |
|
154 | .bytes() | |
152 | .collect() |
|
155 | .collect() | |
153 | }) |
|
156 | }) | |
154 | .collect() |
|
157 | .collect() | |
155 | }; |
|
158 | }; | |
156 | } |
|
159 | } | |
157 |
|
160 | |||
158 | fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> { |
|
161 | fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> { | |
159 | let mut buf = bytes.to_owned(); |
|
162 | let mut buf = bytes.to_owned(); | |
160 | let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef'); |
|
163 | let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef'); | |
161 | if needs_escaping { |
|
164 | if needs_escaping { | |
162 | for forbidden in IGNORED_CHARS.iter() { |
|
165 | for forbidden in IGNORED_CHARS.iter() { | |
163 | replace_slice(&mut buf, forbidden, &[]) |
|
166 | replace_slice(&mut buf, forbidden, &[]) | |
164 | } |
|
167 | } | |
165 | buf |
|
168 | buf | |
166 | } else { |
|
169 | } else { | |
167 | buf |
|
170 | buf | |
168 | } |
|
171 | } | |
169 | } |
|
172 | } | |
170 |
|
173 | |||
171 | pub fn lower_clean(bytes: &[u8]) -> Vec<u8> { |
|
174 | pub fn lower_clean(bytes: &[u8]) -> Vec<u8> { | |
172 | hfs_ignore_clean(&bytes.to_ascii_lowercase()) |
|
175 | hfs_ignore_clean(&bytes.to_ascii_lowercase()) | |
173 | } |
|
176 | } | |
174 |
|
177 | |||
175 | #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] |
|
178 | #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] | |
176 | pub struct HgMetadata { |
|
179 | pub struct HgMetadata { | |
177 | pub st_dev: u64, |
|
180 | pub st_dev: u64, | |
178 | pub st_mode: u32, |
|
181 | pub st_mode: u32, | |
179 | pub st_nlink: u64, |
|
182 | pub st_nlink: u64, | |
180 | pub st_size: u64, |
|
183 | pub st_size: u64, | |
181 | pub st_mtime: i64, |
|
184 | pub st_mtime: i64, | |
182 | pub st_ctime: i64, |
|
185 | pub st_ctime: i64, | |
183 | } |
|
186 | } | |
184 |
|
187 | |||
185 | // TODO support other plaforms |
|
188 | // TODO support other plaforms | |
186 | #[cfg(unix)] |
|
189 | #[cfg(unix)] | |
187 | impl HgMetadata { |
|
190 | impl HgMetadata { | |
188 | pub fn from_metadata(metadata: Metadata) -> Self { |
|
191 | pub fn from_metadata(metadata: Metadata) -> Self { | |
189 | use std::os::unix::fs::MetadataExt; |
|
192 | use std::os::unix::fs::MetadataExt; | |
190 | Self { |
|
193 | Self { | |
191 | st_dev: metadata.dev(), |
|
194 | st_dev: metadata.dev(), | |
192 | st_mode: metadata.mode(), |
|
195 | st_mode: metadata.mode(), | |
193 | st_nlink: metadata.nlink(), |
|
196 | st_nlink: metadata.nlink(), | |
194 | st_size: metadata.size(), |
|
197 | st_size: metadata.size(), | |
195 | st_mtime: metadata.mtime(), |
|
198 | st_mtime: metadata.mtime(), | |
196 | st_ctime: metadata.ctime(), |
|
199 | st_ctime: metadata.ctime(), | |
197 | } |
|
200 | } | |
198 | } |
|
201 | } | |
199 | } |
|
202 | } | |
200 |
|
203 | |||
201 | /// Returns the canonical path of `name`, given `cwd` and `root` |
|
204 | /// Returns the canonical path of `name`, given `cwd` and `root` | |
202 | pub fn canonical_path( |
|
205 | pub fn canonical_path( | |
203 | root: impl AsRef<Path>, |
|
206 | root: impl AsRef<Path>, | |
204 | cwd: impl AsRef<Path>, |
|
207 | cwd: impl AsRef<Path>, | |
205 | name: impl AsRef<Path>, |
|
208 | name: impl AsRef<Path>, | |
206 | ) -> Result<PathBuf, HgPathError> { |
|
209 | ) -> Result<PathBuf, HgPathError> { | |
207 | // TODO add missing normalization for other platforms |
|
210 | // TODO add missing normalization for other platforms | |
208 | let root = root.as_ref(); |
|
211 | let root = root.as_ref(); | |
209 | let cwd = cwd.as_ref(); |
|
212 | let cwd = cwd.as_ref(); | |
210 | let name = name.as_ref(); |
|
213 | let name = name.as_ref(); | |
211 |
|
214 | |||
212 | let name = if !name.is_absolute() { |
|
215 | let name = if !name.is_absolute() { | |
213 | root.join(&cwd).join(&name) |
|
216 | root.join(&cwd).join(&name) | |
214 | } else { |
|
217 | } else { | |
215 | name.to_owned() |
|
218 | name.to_owned() | |
216 | }; |
|
219 | }; | |
217 | let auditor = PathAuditor::new(&root); |
|
220 | let auditor = PathAuditor::new(&root); | |
218 | if name != root && name.starts_with(&root) { |
|
221 | if name != root && name.starts_with(&root) { | |
219 | let name = name.strip_prefix(&root).unwrap(); |
|
222 | let name = name.strip_prefix(&root).unwrap(); | |
220 | auditor.audit_path(path_to_hg_path_buf(name)?)?; |
|
223 | auditor.audit_path(path_to_hg_path_buf(name)?)?; | |
221 | Ok(name.to_owned()) |
|
224 | Ok(name.to_owned()) | |
222 | } else if name == root { |
|
225 | } else if name == root { | |
223 | Ok("".into()) |
|
226 | Ok("".into()) | |
224 | } else { |
|
227 | } else { | |
225 | // Determine whether `name' is in the hierarchy at or beneath `root', |
|
228 | // Determine whether `name' is in the hierarchy at or beneath `root', | |
226 | // by iterating name=name.parent() until it returns `None` (can't |
|
229 | // by iterating name=name.parent() until it returns `None` (can't | |
227 | // check name == '/', because that doesn't work on windows). |
|
230 | // check name == '/', because that doesn't work on windows). | |
228 | let mut name = name.deref(); |
|
231 | let mut name = name.deref(); | |
229 | let original_name = name.to_owned(); |
|
232 | let original_name = name.to_owned(); | |
230 | loop { |
|
233 | loop { | |
231 | let same = is_same_file(&name, &root).unwrap_or(false); |
|
234 | let same = is_same_file(&name, &root).unwrap_or(false); | |
232 | if same { |
|
235 | if same { | |
233 | if name == original_name { |
|
236 | if name == original_name { | |
234 | // `name` was actually the same as root (maybe a symlink) |
|
237 | // `name` was actually the same as root (maybe a symlink) | |
235 | return Ok("".into()); |
|
238 | return Ok("".into()); | |
236 | } |
|
239 | } | |
237 | // `name` is a symlink to root, so `original_name` is under |
|
240 | // `name` is a symlink to root, so `original_name` is under | |
238 | // root |
|
241 | // root | |
239 | let rel_path = original_name.strip_prefix(&name).unwrap(); |
|
242 | let rel_path = original_name.strip_prefix(&name).unwrap(); | |
240 | auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?; |
|
243 | auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?; | |
241 | return Ok(rel_path.to_owned()); |
|
244 | return Ok(rel_path.to_owned()); | |
242 | } |
|
245 | } | |
243 | name = match name.parent() { |
|
246 | name = match name.parent() { | |
244 | None => break, |
|
247 | None => break, | |
245 | Some(p) => p, |
|
248 | Some(p) => p, | |
246 | }; |
|
249 | }; | |
247 | } |
|
250 | } | |
248 | // TODO hint to the user about using --cwd |
|
251 | // TODO hint to the user about using --cwd | |
249 | // Bubble up the responsibility to Python for now |
|
252 | // Bubble up the responsibility to Python for now | |
250 | Err(HgPathError::NotUnderRoot { |
|
253 | Err(HgPathError::NotUnderRoot { | |
251 | path: original_name.to_owned(), |
|
254 | path: original_name.to_owned(), | |
252 | root: root.to_owned(), |
|
255 | root: root.to_owned(), | |
253 | }) |
|
256 | }) | |
254 | } |
|
257 | } | |
255 | } |
|
258 | } | |
256 |
|
259 | |||
257 | /// Returns the representation of the path relative to the current working |
|
260 | /// Returns the representation of the path relative to the current working | |
258 | /// directory for display purposes. |
|
261 | /// directory for display purposes. | |
259 | /// |
|
262 | /// | |
260 | /// `cwd` is a `HgPath`, so it is considered relative to the root directory |
|
263 | /// `cwd` is a `HgPath`, so it is considered relative to the root directory | |
261 | /// of the repository. |
|
264 | /// of the repository. | |
262 | /// |
|
265 | /// | |
263 | /// # Examples |
|
266 | /// # Examples | |
264 | /// |
|
267 | /// | |
265 | /// ``` |
|
268 | /// ``` | |
266 | /// use hg::utils::hg_path::HgPath; |
|
269 | /// use hg::utils::hg_path::HgPath; | |
267 | /// use hg::utils::files::relativize_path; |
|
270 | /// use hg::utils::files::relativize_path; | |
268 | /// use std::borrow::Cow; |
|
271 | /// use std::borrow::Cow; | |
269 | /// |
|
272 | /// | |
270 | /// let file = HgPath::new(b"nested/file"); |
|
273 | /// let file = HgPath::new(b"nested/file"); | |
271 | /// let cwd = HgPath::new(b""); |
|
274 | /// let cwd = HgPath::new(b""); | |
272 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file")); |
|
275 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file")); | |
273 | /// |
|
276 | /// | |
274 | /// let cwd = HgPath::new(b"nested"); |
|
277 | /// let cwd = HgPath::new(b"nested"); | |
275 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file")); |
|
278 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file")); | |
276 | /// |
|
279 | /// | |
277 | /// let cwd = HgPath::new(b"other"); |
|
280 | /// let cwd = HgPath::new(b"other"); | |
278 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file")); |
|
281 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file")); | |
279 | /// ``` |
|
282 | /// ``` | |
280 | pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> { |
|
283 | pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> { | |
281 | if cwd.as_ref().is_empty() { |
|
284 | if cwd.as_ref().is_empty() { | |
282 | Cow::Borrowed(path.as_bytes()) |
|
285 | Cow::Borrowed(path.as_bytes()) | |
283 | } else { |
|
286 | } else { | |
284 | let mut res: Vec<u8> = Vec::new(); |
|
287 | let mut res: Vec<u8> = Vec::new(); | |
285 | let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable(); |
|
288 | let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable(); | |
286 | let mut cwd_iter = |
|
289 | let mut cwd_iter = | |
287 | cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable(); |
|
290 | cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable(); | |
288 | loop { |
|
291 | loop { | |
289 | match (path_iter.peek(), cwd_iter.peek()) { |
|
292 | match (path_iter.peek(), cwd_iter.peek()) { | |
290 | (Some(a), Some(b)) if a == b => (), |
|
293 | (Some(a), Some(b)) if a == b => (), | |
291 | _ => break, |
|
294 | _ => break, | |
292 | } |
|
295 | } | |
293 | path_iter.next(); |
|
296 | path_iter.next(); | |
294 | cwd_iter.next(); |
|
297 | cwd_iter.next(); | |
295 | } |
|
298 | } | |
296 | let mut need_sep = false; |
|
299 | let mut need_sep = false; | |
297 | for _ in cwd_iter { |
|
300 | for _ in cwd_iter { | |
298 | if need_sep { |
|
301 | if need_sep { | |
299 | res.extend(b"/") |
|
302 | res.extend(b"/") | |
300 | } else { |
|
303 | } else { | |
301 | need_sep = true |
|
304 | need_sep = true | |
302 | }; |
|
305 | }; | |
303 | res.extend(b".."); |
|
306 | res.extend(b".."); | |
304 | } |
|
307 | } | |
305 | for c in path_iter { |
|
308 | for c in path_iter { | |
306 | if need_sep { |
|
309 | if need_sep { | |
307 | res.extend(b"/") |
|
310 | res.extend(b"/") | |
308 | } else { |
|
311 | } else { | |
309 | need_sep = true |
|
312 | need_sep = true | |
310 | }; |
|
313 | }; | |
311 | res.extend(c); |
|
314 | res.extend(c); | |
312 | } |
|
315 | } | |
313 | Cow::Owned(res) |
|
316 | Cow::Owned(res) | |
314 | } |
|
317 | } | |
315 | } |
|
318 | } | |
316 |
|
319 | |||
317 | #[cfg(test)] |
|
320 | #[cfg(test)] | |
318 | mod tests { |
|
321 | mod tests { | |
319 | use super::*; |
|
322 | use super::*; | |
320 | use pretty_assertions::assert_eq; |
|
323 | use pretty_assertions::assert_eq; | |
321 |
|
324 | |||
322 | #[test] |
|
325 | #[test] | |
323 | fn find_dirs_some() { |
|
326 | fn find_dirs_some() { | |
324 | let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); |
|
327 | let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); | |
325 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); |
|
328 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); | |
326 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo"))); |
|
329 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo"))); | |
327 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); |
|
330 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); | |
328 | assert_eq!(dirs.next(), None); |
|
331 | assert_eq!(dirs.next(), None); | |
329 | assert_eq!(dirs.next(), None); |
|
332 | assert_eq!(dirs.next(), None); | |
330 | } |
|
333 | } | |
331 |
|
334 | |||
332 | #[test] |
|
335 | #[test] | |
333 | fn find_dirs_empty() { |
|
336 | fn find_dirs_empty() { | |
334 | // looks weird, but mercurial.pathutil.finddirs(b"") yields b"" |
|
337 | // looks weird, but mercurial.pathutil.finddirs(b"") yields b"" | |
335 | let mut dirs = super::find_dirs(HgPath::new(b"")); |
|
338 | let mut dirs = super::find_dirs(HgPath::new(b"")); | |
336 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); |
|
339 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); | |
337 | assert_eq!(dirs.next(), None); |
|
340 | assert_eq!(dirs.next(), None); | |
338 | assert_eq!(dirs.next(), None); |
|
341 | assert_eq!(dirs.next(), None); | |
339 | } |
|
342 | } | |
340 |
|
343 | |||
341 | #[test] |
|
344 | #[test] | |
342 | fn test_find_dirs_with_base_some() { |
|
345 | fn test_find_dirs_with_base_some() { | |
343 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz")); |
|
346 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz")); | |
344 | assert_eq!( |
|
347 | assert_eq!( | |
345 | dirs.next(), |
|
348 | dirs.next(), | |
346 | Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz"))) |
|
349 | Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz"))) | |
347 | ); |
|
350 | ); | |
348 | assert_eq!( |
|
351 | assert_eq!( | |
349 | dirs.next(), |
|
352 | dirs.next(), | |
350 | Some((HgPath::new(b"foo"), HgPath::new(b"bar"))) |
|
353 | Some((HgPath::new(b"foo"), HgPath::new(b"bar"))) | |
351 | ); |
|
354 | ); | |
352 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo")))); |
|
355 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo")))); | |
353 | assert_eq!(dirs.next(), None); |
|
356 | assert_eq!(dirs.next(), None); | |
354 | assert_eq!(dirs.next(), None); |
|
357 | assert_eq!(dirs.next(), None); | |
355 | } |
|
358 | } | |
356 |
|
359 | |||
357 | #[test] |
|
360 | #[test] | |
358 | fn test_find_dirs_with_base_empty() { |
|
361 | fn test_find_dirs_with_base_empty() { | |
359 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); |
|
362 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); | |
360 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); |
|
363 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); | |
361 | assert_eq!(dirs.next(), None); |
|
364 | assert_eq!(dirs.next(), None); | |
362 | assert_eq!(dirs.next(), None); |
|
365 | assert_eq!(dirs.next(), None); | |
363 | } |
|
366 | } | |
364 |
|
367 | |||
365 | #[test] |
|
368 | #[test] | |
366 | fn test_canonical_path() { |
|
369 | fn test_canonical_path() { | |
367 | let root = Path::new("/repo"); |
|
370 | let root = Path::new("/repo"); | |
368 | let cwd = Path::new("/dir"); |
|
371 | let cwd = Path::new("/dir"); | |
369 | let name = Path::new("filename"); |
|
372 | let name = Path::new("filename"); | |
370 | assert_eq!( |
|
373 | assert_eq!( | |
371 | canonical_path(root, cwd, name), |
|
374 | canonical_path(root, cwd, name), | |
372 | Err(HgPathError::NotUnderRoot { |
|
375 | Err(HgPathError::NotUnderRoot { | |
373 | path: PathBuf::from("/dir/filename"), |
|
376 | path: PathBuf::from("/dir/filename"), | |
374 | root: root.to_path_buf() |
|
377 | root: root.to_path_buf() | |
375 | }) |
|
378 | }) | |
376 | ); |
|
379 | ); | |
377 |
|
380 | |||
378 | let root = Path::new("/repo"); |
|
381 | let root = Path::new("/repo"); | |
379 | let cwd = Path::new("/"); |
|
382 | let cwd = Path::new("/"); | |
380 | let name = Path::new("filename"); |
|
383 | let name = Path::new("filename"); | |
381 | assert_eq!( |
|
384 | assert_eq!( | |
382 | canonical_path(root, cwd, name), |
|
385 | canonical_path(root, cwd, name), | |
383 | Err(HgPathError::NotUnderRoot { |
|
386 | Err(HgPathError::NotUnderRoot { | |
384 | path: PathBuf::from("/filename"), |
|
387 | path: PathBuf::from("/filename"), | |
385 | root: root.to_path_buf() |
|
388 | root: root.to_path_buf() | |
386 | }) |
|
389 | }) | |
387 | ); |
|
390 | ); | |
388 |
|
391 | |||
389 | let root = Path::new("/repo"); |
|
392 | let root = Path::new("/repo"); | |
390 | let cwd = Path::new("/"); |
|
393 | let cwd = Path::new("/"); | |
391 | let name = Path::new("repo/filename"); |
|
394 | let name = Path::new("repo/filename"); | |
392 | assert_eq!( |
|
395 | assert_eq!( | |
393 | canonical_path(root, cwd, name), |
|
396 | canonical_path(root, cwd, name), | |
394 | Ok(PathBuf::from("filename")) |
|
397 | Ok(PathBuf::from("filename")) | |
395 | ); |
|
398 | ); | |
396 |
|
399 | |||
397 | let root = Path::new("/repo"); |
|
400 | let root = Path::new("/repo"); | |
398 | let cwd = Path::new("/repo"); |
|
401 | let cwd = Path::new("/repo"); | |
399 | let name = Path::new("filename"); |
|
402 | let name = Path::new("filename"); | |
400 | assert_eq!( |
|
403 | assert_eq!( | |
401 | canonical_path(root, cwd, name), |
|
404 | canonical_path(root, cwd, name), | |
402 | Ok(PathBuf::from("filename")) |
|
405 | Ok(PathBuf::from("filename")) | |
403 | ); |
|
406 | ); | |
404 |
|
407 | |||
405 | let root = Path::new("/repo"); |
|
408 | let root = Path::new("/repo"); | |
406 | let cwd = Path::new("/repo/subdir"); |
|
409 | let cwd = Path::new("/repo/subdir"); | |
407 | let name = Path::new("filename"); |
|
410 | let name = Path::new("filename"); | |
408 | assert_eq!( |
|
411 | assert_eq!( | |
409 | canonical_path(root, cwd, name), |
|
412 | canonical_path(root, cwd, name), | |
410 | Ok(PathBuf::from("subdir/filename")) |
|
413 | Ok(PathBuf::from("subdir/filename")) | |
411 | ); |
|
414 | ); | |
412 | } |
|
415 | } | |
413 |
|
416 | |||
414 | #[test] |
|
417 | #[test] | |
415 | fn test_canonical_path_not_rooted() { |
|
418 | fn test_canonical_path_not_rooted() { | |
416 | use std::fs::create_dir; |
|
419 | use std::fs::create_dir; | |
417 | use tempfile::tempdir; |
|
420 | use tempfile::tempdir; | |
418 |
|
421 | |||
419 | let base_dir = tempdir().unwrap(); |
|
422 | let base_dir = tempdir().unwrap(); | |
420 | let base_dir_path = base_dir.path(); |
|
423 | let base_dir_path = base_dir.path(); | |
421 | let beneath_repo = base_dir_path.join("a"); |
|
424 | let beneath_repo = base_dir_path.join("a"); | |
422 | let root = base_dir_path.join("a/b"); |
|
425 | let root = base_dir_path.join("a/b"); | |
423 | let out_of_repo = base_dir_path.join("c"); |
|
426 | let out_of_repo = base_dir_path.join("c"); | |
424 | let under_repo_symlink = out_of_repo.join("d"); |
|
427 | let under_repo_symlink = out_of_repo.join("d"); | |
425 |
|
428 | |||
426 | create_dir(&beneath_repo).unwrap(); |
|
429 | create_dir(&beneath_repo).unwrap(); | |
427 | create_dir(&root).unwrap(); |
|
430 | create_dir(&root).unwrap(); | |
428 |
|
431 | |||
429 | // TODO make portable |
|
432 | // TODO make portable | |
430 | std::os::unix::fs::symlink(&root, &out_of_repo).unwrap(); |
|
433 | std::os::unix::fs::symlink(&root, &out_of_repo).unwrap(); | |
431 |
|
434 | |||
432 | assert_eq!( |
|
435 | assert_eq!( | |
433 | canonical_path(&root, Path::new(""), out_of_repo), |
|
436 | canonical_path(&root, Path::new(""), out_of_repo), | |
434 | Ok(PathBuf::from("")) |
|
437 | Ok(PathBuf::from("")) | |
435 | ); |
|
438 | ); | |
436 | assert_eq!( |
|
439 | assert_eq!( | |
437 | canonical_path(&root, Path::new(""), &beneath_repo), |
|
440 | canonical_path(&root, Path::new(""), &beneath_repo), | |
438 | Err(HgPathError::NotUnderRoot { |
|
441 | Err(HgPathError::NotUnderRoot { | |
439 | path: beneath_repo.to_owned(), |
|
442 | path: beneath_repo.to_owned(), | |
440 | root: root.to_owned() |
|
443 | root: root.to_owned() | |
441 | }) |
|
444 | }) | |
442 | ); |
|
445 | ); | |
443 | assert_eq!( |
|
446 | assert_eq!( | |
444 | canonical_path(&root, Path::new(""), &under_repo_symlink), |
|
447 | canonical_path(&root, Path::new(""), &under_repo_symlink), | |
445 | Ok(PathBuf::from("d")) |
|
448 | Ok(PathBuf::from("d")) | |
446 | ); |
|
449 | ); | |
447 | } |
|
450 | } | |
448 | } |
|
451 | } |
General Comments 0
You need to be logged in to leave comments.
Login now