##// END OF EJS Templates
rhg: Add support for environment variables in config include paths...
Simon Sapin -
r47476:91ab5190 default
parent child Browse files
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(&section) {
200 if let Some(map) = current_layer.sections.get_mut(&section) {
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: &regex::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_path_from_bytes(bytes: &[u8]) -> &Path {
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