Show More
@@ -1,198 +1,197 b'' | |||||
1 | // config.rs |
|
1 | // config.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2020 |
|
3 | // Copyright 2020 | |
4 | // Valentin Gatien-Baron, |
|
4 | // Valentin Gatien-Baron, | |
5 | // Raphaël Gomès <rgomes@octobus.net> |
|
5 | // Raphaël Gomès <rgomes@octobus.net> | |
6 | // |
|
6 | // | |
7 | // This software may be used and distributed according to the terms of the |
|
7 | // This software may be used and distributed according to the terms of the | |
8 | // GNU General Public License version 2 or any later version. |
|
8 | // GNU General Public License version 2 or any later version. | |
9 |
|
9 | |||
10 | use super::layer; |
|
10 | use super::layer; | |
11 | use crate::config::layer::{ |
|
11 | use crate::config::layer::{ | |
12 | ConfigError, ConfigLayer, ConfigParseError, ConfigValue, |
|
12 | ConfigError, ConfigLayer, ConfigParseError, ConfigValue, | |
13 | }; |
|
13 | }; | |
14 | use std::path::PathBuf; |
|
14 | use std::path::PathBuf; | |
15 |
|
15 | |||
16 | use crate::repo::Repo; |
|
16 | use crate::repo::Repo; | |
17 | use crate::utils::files::read_whole_file; |
|
|||
18 |
|
17 | |||
19 | /// Holds the config values for the current repository |
|
18 | /// Holds the config values for the current repository | |
20 | /// TODO update this docstring once we support more sources |
|
19 | /// TODO update this docstring once we support more sources | |
21 | pub struct Config { |
|
20 | pub struct Config { | |
22 | layers: Vec<layer::ConfigLayer>, |
|
21 | layers: Vec<layer::ConfigLayer>, | |
23 | } |
|
22 | } | |
24 |
|
23 | |||
25 | impl std::fmt::Debug for Config { |
|
24 | impl std::fmt::Debug for Config { | |
26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|
25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
27 | for (index, layer) in self.layers.iter().rev().enumerate() { |
|
26 | for (index, layer) in self.layers.iter().rev().enumerate() { | |
28 | write!( |
|
27 | write!( | |
29 | f, |
|
28 | f, | |
30 | "==== Layer {} (trusted: {}) ====\n{:?}", |
|
29 | "==== Layer {} (trusted: {}) ====\n{:?}", | |
31 | index, layer.trusted, layer |
|
30 | index, layer.trusted, layer | |
32 | )?; |
|
31 | )?; | |
33 | } |
|
32 | } | |
34 | Ok(()) |
|
33 | Ok(()) | |
35 | } |
|
34 | } | |
36 | } |
|
35 | } | |
37 |
|
36 | |||
38 | pub enum ConfigSource { |
|
37 | pub enum ConfigSource { | |
39 | /// Absolute path to a config file |
|
38 | /// Absolute path to a config file | |
40 | AbsPath(PathBuf), |
|
39 | AbsPath(PathBuf), | |
41 | /// Already parsed (from the CLI, env, Python resources, etc.) |
|
40 | /// Already parsed (from the CLI, env, Python resources, etc.) | |
42 | Parsed(layer::ConfigLayer), |
|
41 | Parsed(layer::ConfigLayer), | |
43 | } |
|
42 | } | |
44 |
|
43 | |||
45 | pub fn parse_bool(v: &[u8]) -> Option<bool> { |
|
44 | pub fn parse_bool(v: &[u8]) -> Option<bool> { | |
46 | match v.to_ascii_lowercase().as_slice() { |
|
45 | match v.to_ascii_lowercase().as_slice() { | |
47 | b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true), |
|
46 | b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true), | |
48 | b"0" | b"no" | b"false" | b"off" | b"never" => Some(false), |
|
47 | b"0" | b"no" | b"false" | b"off" | b"never" => Some(false), | |
49 | _ => None, |
|
48 | _ => None, | |
50 | } |
|
49 | } | |
51 | } |
|
50 | } | |
52 |
|
51 | |||
53 | impl Config { |
|
52 | impl Config { | |
54 | /// Loads in order, which means that the precedence is the same |
|
53 | /// Loads in order, which means that the precedence is the same | |
55 | /// as the order of `sources`. |
|
54 | /// as the order of `sources`. | |
56 | pub fn load_from_explicit_sources( |
|
55 | pub fn load_from_explicit_sources( | |
57 | sources: Vec<ConfigSource>, |
|
56 | sources: Vec<ConfigSource>, | |
58 | ) -> Result<Self, ConfigError> { |
|
57 | ) -> Result<Self, ConfigError> { | |
59 | let mut layers = vec![]; |
|
58 | let mut layers = vec![]; | |
60 |
|
59 | |||
61 | for source in sources.into_iter() { |
|
60 | for source in sources.into_iter() { | |
62 | match source { |
|
61 | match source { | |
63 | ConfigSource::Parsed(c) => layers.push(c), |
|
62 | ConfigSource::Parsed(c) => layers.push(c), | |
64 | ConfigSource::AbsPath(c) => { |
|
63 | ConfigSource::AbsPath(c) => { | |
65 | // TODO check if it should be trusted |
|
64 | // TODO check if it should be trusted | |
66 | // mercurial/ui.py:427 |
|
65 | // mercurial/ui.py:427 | |
67 |
let data = match read |
|
66 | let data = match std::fs::read(&c) { | |
68 | Err(_) => continue, // same as the python code |
|
67 | Err(_) => continue, // same as the python code | |
69 | Ok(data) => data, |
|
68 | Ok(data) => data, | |
70 | }; |
|
69 | }; | |
71 | layers.extend(ConfigLayer::parse(&c, &data)?) |
|
70 | layers.extend(ConfigLayer::parse(&c, &data)?) | |
72 | } |
|
71 | } | |
73 | } |
|
72 | } | |
74 | } |
|
73 | } | |
75 |
|
74 | |||
76 | Ok(Config { layers }) |
|
75 | Ok(Config { layers }) | |
77 | } |
|
76 | } | |
78 |
|
77 | |||
79 | /// Loads the local config. In a future version, this will also load the |
|
78 | /// Loads the local config. In a future version, this will also load the | |
80 | /// `$HOME/.hgrc` and more to mirror the Python implementation. |
|
79 | /// `$HOME/.hgrc` and more to mirror the Python implementation. | |
81 | pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> { |
|
80 | pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> { | |
82 | Ok(Self::load_from_explicit_sources(vec![ |
|
81 | Ok(Self::load_from_explicit_sources(vec![ | |
83 | ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")), |
|
82 | ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")), | |
84 | ])?) |
|
83 | ])?) | |
85 | } |
|
84 | } | |
86 |
|
85 | |||
87 | /// Returns an `Err` if the first value found is not a valid boolean. |
|
86 | /// Returns an `Err` if the first value found is not a valid boolean. | |
88 | /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if |
|
87 | /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if | |
89 | /// found, or `None`. |
|
88 | /// found, or `None`. | |
90 | pub fn get_option( |
|
89 | pub fn get_option( | |
91 | &self, |
|
90 | &self, | |
92 | section: &[u8], |
|
91 | section: &[u8], | |
93 | item: &[u8], |
|
92 | item: &[u8], | |
94 | ) -> Result<Option<bool>, ConfigParseError> { |
|
93 | ) -> Result<Option<bool>, ConfigParseError> { | |
95 | match self.get_inner(§ion, &item) { |
|
94 | match self.get_inner(§ion, &item) { | |
96 | Some((layer, v)) => match parse_bool(&v.bytes) { |
|
95 | Some((layer, v)) => match parse_bool(&v.bytes) { | |
97 | Some(b) => Ok(Some(b)), |
|
96 | Some(b) => Ok(Some(b)), | |
98 | None => Err(ConfigParseError { |
|
97 | None => Err(ConfigParseError { | |
99 | origin: layer.origin.to_owned(), |
|
98 | origin: layer.origin.to_owned(), | |
100 | line: v.line, |
|
99 | line: v.line, | |
101 | bytes: v.bytes.to_owned(), |
|
100 | bytes: v.bytes.to_owned(), | |
102 | }), |
|
101 | }), | |
103 | }, |
|
102 | }, | |
104 | None => Ok(None), |
|
103 | None => Ok(None), | |
105 | } |
|
104 | } | |
106 | } |
|
105 | } | |
107 |
|
106 | |||
108 | /// Returns the corresponding boolean in the config. Returns `Ok(false)` |
|
107 | /// Returns the corresponding boolean in the config. Returns `Ok(false)` | |
109 | /// if the value is not found, an `Err` if it's not a valid boolean. |
|
108 | /// if the value is not found, an `Err` if it's not a valid boolean. | |
110 | pub fn get_bool( |
|
109 | pub fn get_bool( | |
111 | &self, |
|
110 | &self, | |
112 | section: &[u8], |
|
111 | section: &[u8], | |
113 | item: &[u8], |
|
112 | item: &[u8], | |
114 | ) -> Result<bool, ConfigError> { |
|
113 | ) -> Result<bool, ConfigError> { | |
115 | Ok(self.get_option(section, item)?.unwrap_or(false)) |
|
114 | Ok(self.get_option(section, item)?.unwrap_or(false)) | |
116 | } |
|
115 | } | |
117 |
|
116 | |||
118 | /// Returns the raw value bytes of the first one found, or `None`. |
|
117 | /// Returns the raw value bytes of the first one found, or `None`. | |
119 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> { |
|
118 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> { | |
120 | self.get_inner(section, item) |
|
119 | self.get_inner(section, item) | |
121 | .map(|(_, value)| value.bytes.as_ref()) |
|
120 | .map(|(_, value)| value.bytes.as_ref()) | |
122 | } |
|
121 | } | |
123 |
|
122 | |||
124 | /// Returns the layer and the value of the first one found, or `None`. |
|
123 | /// Returns the layer and the value of the first one found, or `None`. | |
125 | fn get_inner( |
|
124 | fn get_inner( | |
126 | &self, |
|
125 | &self, | |
127 | section: &[u8], |
|
126 | section: &[u8], | |
128 | item: &[u8], |
|
127 | item: &[u8], | |
129 | ) -> Option<(&ConfigLayer, &ConfigValue)> { |
|
128 | ) -> Option<(&ConfigLayer, &ConfigValue)> { | |
130 | for layer in self.layers.iter().rev() { |
|
129 | for layer in self.layers.iter().rev() { | |
131 | if !layer.trusted { |
|
130 | if !layer.trusted { | |
132 | continue; |
|
131 | continue; | |
133 | } |
|
132 | } | |
134 | if let Some(v) = layer.get(§ion, &item) { |
|
133 | if let Some(v) = layer.get(§ion, &item) { | |
135 | return Some((&layer, v)); |
|
134 | return Some((&layer, v)); | |
136 | } |
|
135 | } | |
137 | } |
|
136 | } | |
138 | None |
|
137 | None | |
139 | } |
|
138 | } | |
140 |
|
139 | |||
141 | /// Get raw values bytes from all layers (even untrusted ones) in order |
|
140 | /// Get raw values bytes from all layers (even untrusted ones) in order | |
142 | /// of precedence. |
|
141 | /// of precedence. | |
143 | #[cfg(test)] |
|
142 | #[cfg(test)] | |
144 | fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> { |
|
143 | fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> { | |
145 | let mut res = vec![]; |
|
144 | let mut res = vec![]; | |
146 | for layer in self.layers.iter().rev() { |
|
145 | for layer in self.layers.iter().rev() { | |
147 | if let Some(v) = layer.get(§ion, &item) { |
|
146 | if let Some(v) = layer.get(§ion, &item) { | |
148 | res.push(v.bytes.as_ref()); |
|
147 | res.push(v.bytes.as_ref()); | |
149 | } |
|
148 | } | |
150 | } |
|
149 | } | |
151 | res |
|
150 | res | |
152 | } |
|
151 | } | |
153 | } |
|
152 | } | |
154 |
|
153 | |||
155 | #[cfg(test)] |
|
154 | #[cfg(test)] | |
156 | mod tests { |
|
155 | mod tests { | |
157 | use super::*; |
|
156 | use super::*; | |
158 | use pretty_assertions::assert_eq; |
|
157 | use pretty_assertions::assert_eq; | |
159 | use std::fs::File; |
|
158 | use std::fs::File; | |
160 | use std::io::Write; |
|
159 | use std::io::Write; | |
161 |
|
160 | |||
162 | #[test] |
|
161 | #[test] | |
163 | fn test_include_layer_ordering() { |
|
162 | fn test_include_layer_ordering() { | |
164 | let tmpdir = tempfile::tempdir().unwrap(); |
|
163 | let tmpdir = tempfile::tempdir().unwrap(); | |
165 | let tmpdir_path = tmpdir.path(); |
|
164 | let tmpdir_path = tmpdir.path(); | |
166 | let mut included_file = |
|
165 | let mut included_file = | |
167 | File::create(&tmpdir_path.join("included.rc")).unwrap(); |
|
166 | File::create(&tmpdir_path.join("included.rc")).unwrap(); | |
168 |
|
167 | |||
169 | included_file.write_all(b"[section]\nitem=value1").unwrap(); |
|
168 | included_file.write_all(b"[section]\nitem=value1").unwrap(); | |
170 | let base_config_path = tmpdir_path.join("base.rc"); |
|
169 | let base_config_path = tmpdir_path.join("base.rc"); | |
171 | let mut config_file = File::create(&base_config_path).unwrap(); |
|
170 | let mut config_file = File::create(&base_config_path).unwrap(); | |
172 | let data = |
|
171 | let data = | |
173 | b"[section]\nitem=value0\n%include included.rc\nitem=value2"; |
|
172 | b"[section]\nitem=value0\n%include included.rc\nitem=value2"; | |
174 | config_file.write_all(data).unwrap(); |
|
173 | config_file.write_all(data).unwrap(); | |
175 |
|
174 | |||
176 | let sources = vec![ConfigSource::AbsPath(base_config_path)]; |
|
175 | let sources = vec![ConfigSource::AbsPath(base_config_path)]; | |
177 | let config = Config::load_from_explicit_sources(sources) |
|
176 | let config = Config::load_from_explicit_sources(sources) | |
178 | .expect("expected valid config"); |
|
177 | .expect("expected valid config"); | |
179 |
|
178 | |||
180 | dbg!(&config); |
|
179 | dbg!(&config); | |
181 |
|
180 | |||
182 | let (_, value) = config.get_inner(b"section", b"item").unwrap(); |
|
181 | let (_, value) = config.get_inner(b"section", b"item").unwrap(); | |
183 | assert_eq!( |
|
182 | assert_eq!( | |
184 | value, |
|
183 | value, | |
185 | &ConfigValue { |
|
184 | &ConfigValue { | |
186 | bytes: b"value2".to_vec(), |
|
185 | bytes: b"value2".to_vec(), | |
187 | line: Some(4) |
|
186 | line: Some(4) | |
188 | } |
|
187 | } | |
189 | ); |
|
188 | ); | |
190 |
|
189 | |||
191 | let value = config.get(b"section", b"item").unwrap(); |
|
190 | let value = config.get(b"section", b"item").unwrap(); | |
192 | assert_eq!(value, b"value2",); |
|
191 | assert_eq!(value, b"value2",); | |
193 | assert_eq!( |
|
192 | assert_eq!( | |
194 | config.get_all(b"section", b"item"), |
|
193 | config.get_all(b"section", b"item"), | |
195 | [b"value2", b"value1", b"value0"] |
|
194 | [b"value2", b"value1", b"value0"] | |
196 | ); |
|
195 | ); | |
197 | } |
|
196 | } | |
198 | } |
|
197 | } |
@@ -1,253 +1,251 b'' | |||||
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, IoResultExt}; |
|
10 | use crate::errors::{HgError, IoResultExt}; | |
11 | use crate::utils::files::{ |
|
11 | use crate::utils::files::{get_bytes_from_path, get_path_from_bytes}; | |
12 | get_bytes_from_path, get_path_from_bytes, read_whole_file, |
|
|||
13 | }; |
|
|||
14 | use format_bytes::format_bytes; |
|
12 | use format_bytes::format_bytes; | |
15 | use lazy_static::lazy_static; |
|
13 | use lazy_static::lazy_static; | |
16 | use regex::bytes::Regex; |
|
14 | use regex::bytes::Regex; | |
17 | use std::collections::HashMap; |
|
15 | use std::collections::HashMap; | |
18 | use std::io; |
|
16 | use std::io; | |
19 | use std::path::{Path, PathBuf}; |
|
17 | use std::path::{Path, PathBuf}; | |
20 |
|
18 | |||
21 | lazy_static! { |
|
19 | lazy_static! { | |
22 | static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]"); |
|
20 | static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]"); | |
23 | static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)"); |
|
21 | static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)"); | |
24 | /// Continuation whitespace |
|
22 | /// Continuation whitespace | |
25 | static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$"); |
|
23 | static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$"); | |
26 | static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)"); |
|
24 | static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)"); | |
27 | static ref COMMENT_RE: Regex = make_regex(r"^(;|#)"); |
|
25 | static ref COMMENT_RE: Regex = make_regex(r"^(;|#)"); | |
28 | /// A directive that allows for removing previous entries |
|
26 | /// A directive that allows for removing previous entries | |
29 | static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)"); |
|
27 | static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)"); | |
30 | /// A directive that allows for including other config files |
|
28 | /// A directive that allows for including other config files | |
31 | static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$"); |
|
29 | static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$"); | |
32 | } |
|
30 | } | |
33 |
|
31 | |||
34 | /// All config values separated by layers of precedence. |
|
32 | /// All config values separated by layers of precedence. | |
35 | /// Each config source may be split in multiple layers if `%include` directives |
|
33 | /// Each config source may be split in multiple layers if `%include` directives | |
36 | /// are used. |
|
34 | /// are used. | |
37 | /// TODO detail the general precedence |
|
35 | /// TODO detail the general precedence | |
38 | #[derive(Clone)] |
|
36 | #[derive(Clone)] | |
39 | pub struct ConfigLayer { |
|
37 | pub struct ConfigLayer { | |
40 | /// Mapping of the sections to their items |
|
38 | /// Mapping of the sections to their items | |
41 | sections: HashMap<Vec<u8>, ConfigItem>, |
|
39 | sections: HashMap<Vec<u8>, ConfigItem>, | |
42 | /// All sections (and their items/values) in a layer share the same origin |
|
40 | /// All sections (and their items/values) in a layer share the same origin | |
43 | pub origin: ConfigOrigin, |
|
41 | pub origin: ConfigOrigin, | |
44 | /// Whether this layer comes from a trusted user or group |
|
42 | /// Whether this layer comes from a trusted user or group | |
45 | pub trusted: bool, |
|
43 | pub trusted: bool, | |
46 | } |
|
44 | } | |
47 |
|
45 | |||
48 | impl ConfigLayer { |
|
46 | impl ConfigLayer { | |
49 | pub fn new(origin: ConfigOrigin) -> Self { |
|
47 | pub fn new(origin: ConfigOrigin) -> Self { | |
50 | ConfigLayer { |
|
48 | ConfigLayer { | |
51 | sections: HashMap::new(), |
|
49 | sections: HashMap::new(), | |
52 | trusted: true, // TODO check |
|
50 | trusted: true, // TODO check | |
53 | origin, |
|
51 | origin, | |
54 | } |
|
52 | } | |
55 | } |
|
53 | } | |
56 |
|
54 | |||
57 | /// Add an entry to the config, overwriting the old one if already present. |
|
55 | /// Add an entry to the config, overwriting the old one if already present. | |
58 | pub fn add( |
|
56 | pub fn add( | |
59 | &mut self, |
|
57 | &mut self, | |
60 | section: Vec<u8>, |
|
58 | section: Vec<u8>, | |
61 | item: Vec<u8>, |
|
59 | item: Vec<u8>, | |
62 | value: Vec<u8>, |
|
60 | value: Vec<u8>, | |
63 | line: Option<usize>, |
|
61 | line: Option<usize>, | |
64 | ) { |
|
62 | ) { | |
65 | self.sections |
|
63 | self.sections | |
66 | .entry(section) |
|
64 | .entry(section) | |
67 | .or_insert_with(|| HashMap::new()) |
|
65 | .or_insert_with(|| HashMap::new()) | |
68 | .insert(item, ConfigValue { bytes: value, line }); |
|
66 | .insert(item, ConfigValue { bytes: value, line }); | |
69 | } |
|
67 | } | |
70 |
|
68 | |||
71 | /// Returns the config value in `<section>.<item>` if it exists |
|
69 | /// Returns the config value in `<section>.<item>` if it exists | |
72 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> { |
|
70 | pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> { | |
73 | Some(self.sections.get(section)?.get(item)?) |
|
71 | Some(self.sections.get(section)?.get(item)?) | |
74 | } |
|
72 | } | |
75 |
|
73 | |||
76 | pub fn is_empty(&self) -> bool { |
|
74 | pub fn is_empty(&self) -> bool { | |
77 | self.sections.is_empty() |
|
75 | self.sections.is_empty() | |
78 | } |
|
76 | } | |
79 |
|
77 | |||
80 | /// Returns a `Vec` of layers in order of precedence (so, in read order), |
|
78 | /// Returns a `Vec` of layers in order of precedence (so, in read order), | |
81 | /// recursively parsing the `%include` directives if any. |
|
79 | /// recursively parsing the `%include` directives if any. | |
82 | pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> { |
|
80 | pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> { | |
83 | let mut layers = vec![]; |
|
81 | let mut layers = vec![]; | |
84 |
|
82 | |||
85 | // Discard byte order mark if any |
|
83 | // Discard byte order mark if any | |
86 | let data = if data.starts_with(b"\xef\xbb\xbf") { |
|
84 | let data = if data.starts_with(b"\xef\xbb\xbf") { | |
87 | &data[3..] |
|
85 | &data[3..] | |
88 | } else { |
|
86 | } else { | |
89 | data |
|
87 | data | |
90 | }; |
|
88 | }; | |
91 |
|
89 | |||
92 | // TODO check if it's trusted |
|
90 | // TODO check if it's trusted | |
93 | let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned())); |
|
91 | let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned())); | |
94 |
|
92 | |||
95 | let mut lines_iter = |
|
93 | let mut lines_iter = | |
96 | data.split(|b| *b == b'\n').enumerate().peekable(); |
|
94 | data.split(|b| *b == b'\n').enumerate().peekable(); | |
97 | let mut section = b"".to_vec(); |
|
95 | let mut section = b"".to_vec(); | |
98 |
|
96 | |||
99 | while let Some((index, bytes)) = lines_iter.next() { |
|
97 | while let Some((index, bytes)) = lines_iter.next() { | |
100 | if let Some(m) = INCLUDE_RE.captures(&bytes) { |
|
98 | if let Some(m) = INCLUDE_RE.captures(&bytes) { | |
101 | let filename_bytes = &m[1]; |
|
99 | let filename_bytes = &m[1]; | |
102 | let filename_to_include = get_path_from_bytes(&filename_bytes); |
|
100 | let filename_to_include = get_path_from_bytes(&filename_bytes); | |
103 | let (include_src, result) = |
|
101 | let (include_src, result) = | |
104 | read_include(&src, &filename_to_include); |
|
102 | read_include(&src, &filename_to_include); | |
105 | let data = result.for_file(filename_to_include)?; |
|
103 | let data = result.for_file(filename_to_include)?; | |
106 | layers.push(current_layer); |
|
104 | layers.push(current_layer); | |
107 | layers.extend(Self::parse(&include_src, &data)?); |
|
105 | layers.extend(Self::parse(&include_src, &data)?); | |
108 | current_layer = Self::new(ConfigOrigin::File(src.to_owned())); |
|
106 | current_layer = Self::new(ConfigOrigin::File(src.to_owned())); | |
109 | } else if let Some(_) = EMPTY_RE.captures(&bytes) { |
|
107 | } else if let Some(_) = EMPTY_RE.captures(&bytes) { | |
110 | } else if let Some(m) = SECTION_RE.captures(&bytes) { |
|
108 | } else if let Some(m) = SECTION_RE.captures(&bytes) { | |
111 | section = m[1].to_vec(); |
|
109 | section = m[1].to_vec(); | |
112 | } else if let Some(m) = ITEM_RE.captures(&bytes) { |
|
110 | } else if let Some(m) = ITEM_RE.captures(&bytes) { | |
113 | let item = m[1].to_vec(); |
|
111 | let item = m[1].to_vec(); | |
114 | let mut value = m[2].to_vec(); |
|
112 | let mut value = m[2].to_vec(); | |
115 | loop { |
|
113 | loop { | |
116 | match lines_iter.peek() { |
|
114 | match lines_iter.peek() { | |
117 | None => break, |
|
115 | None => break, | |
118 | Some((_, v)) => { |
|
116 | Some((_, v)) => { | |
119 | if let Some(_) = COMMENT_RE.captures(&v) { |
|
117 | if let Some(_) = COMMENT_RE.captures(&v) { | |
120 | } else if let Some(_) = CONT_RE.captures(&v) { |
|
118 | } else if let Some(_) = CONT_RE.captures(&v) { | |
121 | value.extend(b"\n"); |
|
119 | value.extend(b"\n"); | |
122 | value.extend(&m[1]); |
|
120 | value.extend(&m[1]); | |
123 | } else { |
|
121 | } else { | |
124 | break; |
|
122 | break; | |
125 | } |
|
123 | } | |
126 | } |
|
124 | } | |
127 | }; |
|
125 | }; | |
128 | lines_iter.next(); |
|
126 | lines_iter.next(); | |
129 | } |
|
127 | } | |
130 | current_layer.add( |
|
128 | current_layer.add( | |
131 | section.clone(), |
|
129 | section.clone(), | |
132 | item, |
|
130 | item, | |
133 | value, |
|
131 | value, | |
134 | Some(index + 1), |
|
132 | Some(index + 1), | |
135 | ); |
|
133 | ); | |
136 | } else if let Some(m) = UNSET_RE.captures(&bytes) { |
|
134 | } else if let Some(m) = UNSET_RE.captures(&bytes) { | |
137 | if let Some(map) = current_layer.sections.get_mut(§ion) { |
|
135 | if let Some(map) = current_layer.sections.get_mut(§ion) { | |
138 | map.remove(&m[1]); |
|
136 | map.remove(&m[1]); | |
139 | } |
|
137 | } | |
140 | } else { |
|
138 | } else { | |
141 | return Err(ConfigParseError { |
|
139 | return Err(ConfigParseError { | |
142 | origin: ConfigOrigin::File(src.to_owned()), |
|
140 | origin: ConfigOrigin::File(src.to_owned()), | |
143 | line: Some(index + 1), |
|
141 | line: Some(index + 1), | |
144 | bytes: bytes.to_owned(), |
|
142 | bytes: bytes.to_owned(), | |
145 | } |
|
143 | } | |
146 | .into()); |
|
144 | .into()); | |
147 | } |
|
145 | } | |
148 | } |
|
146 | } | |
149 | if !current_layer.is_empty() { |
|
147 | if !current_layer.is_empty() { | |
150 | layers.push(current_layer); |
|
148 | layers.push(current_layer); | |
151 | } |
|
149 | } | |
152 | Ok(layers) |
|
150 | Ok(layers) | |
153 | } |
|
151 | } | |
154 | } |
|
152 | } | |
155 |
|
153 | |||
156 | impl std::fmt::Debug for ConfigLayer { |
|
154 | impl std::fmt::Debug for ConfigLayer { | |
157 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|
155 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
158 | let mut sections: Vec<_> = self.sections.iter().collect(); |
|
156 | let mut sections: Vec<_> = self.sections.iter().collect(); | |
159 | sections.sort_by(|e0, e1| e0.0.cmp(e1.0)); |
|
157 | sections.sort_by(|e0, e1| e0.0.cmp(e1.0)); | |
160 |
|
158 | |||
161 | for (section, items) in sections.into_iter() { |
|
159 | for (section, items) in sections.into_iter() { | |
162 | let mut items: Vec<_> = items.into_iter().collect(); |
|
160 | let mut items: Vec<_> = items.into_iter().collect(); | |
163 | items.sort_by(|e0, e1| e0.0.cmp(e1.0)); |
|
161 | items.sort_by(|e0, e1| e0.0.cmp(e1.0)); | |
164 |
|
162 | |||
165 | for (item, config_entry) in items { |
|
163 | for (item, config_entry) in items { | |
166 | writeln!( |
|
164 | writeln!( | |
167 | f, |
|
165 | f, | |
168 | "{}", |
|
166 | "{}", | |
169 | String::from_utf8_lossy(&format_bytes!( |
|
167 | String::from_utf8_lossy(&format_bytes!( | |
170 | b"{}.{}={} # {}", |
|
168 | b"{}.{}={} # {}", | |
171 | section, |
|
169 | section, | |
172 | item, |
|
170 | item, | |
173 | &config_entry.bytes, |
|
171 | &config_entry.bytes, | |
174 | &self.origin.to_bytes(), |
|
172 | &self.origin.to_bytes(), | |
175 | )) |
|
173 | )) | |
176 | )? |
|
174 | )? | |
177 | } |
|
175 | } | |
178 | } |
|
176 | } | |
179 | Ok(()) |
|
177 | Ok(()) | |
180 | } |
|
178 | } | |
181 | } |
|
179 | } | |
182 |
|
180 | |||
183 | /// Mapping of section item to value. |
|
181 | /// Mapping of section item to value. | |
184 | /// In the following: |
|
182 | /// In the following: | |
185 | /// ```text |
|
183 | /// ```text | |
186 | /// [ui] |
|
184 | /// [ui] | |
187 | /// paginate=no |
|
185 | /// paginate=no | |
188 | /// ``` |
|
186 | /// ``` | |
189 | /// "paginate" is the section item and "no" the value. |
|
187 | /// "paginate" is the section item and "no" the value. | |
190 | pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>; |
|
188 | pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>; | |
191 |
|
189 | |||
192 | #[derive(Clone, Debug, PartialEq)] |
|
190 | #[derive(Clone, Debug, PartialEq)] | |
193 | pub struct ConfigValue { |
|
191 | pub struct ConfigValue { | |
194 | /// The raw bytes of the value (be it from the CLI, env or from a file) |
|
192 | /// The raw bytes of the value (be it from the CLI, env or from a file) | |
195 | pub bytes: Vec<u8>, |
|
193 | pub bytes: Vec<u8>, | |
196 | /// Only present if the value comes from a file, 1-indexed. |
|
194 | /// Only present if the value comes from a file, 1-indexed. | |
197 | pub line: Option<usize>, |
|
195 | pub line: Option<usize>, | |
198 | } |
|
196 | } | |
199 |
|
197 | |||
200 | #[derive(Clone, Debug)] |
|
198 | #[derive(Clone, Debug)] | |
201 | pub enum ConfigOrigin { |
|
199 | pub enum ConfigOrigin { | |
202 | /// The value comes from a configuration file |
|
200 | /// The value comes from a configuration file | |
203 | File(PathBuf), |
|
201 | File(PathBuf), | |
204 | /// The value comes from the environment like `$PAGER` or `$EDITOR` |
|
202 | /// The value comes from the environment like `$PAGER` or `$EDITOR` | |
205 | Environment(Vec<u8>), |
|
203 | Environment(Vec<u8>), | |
206 | /* TODO cli |
|
204 | /* TODO cli | |
207 | * TODO defaults (configitems.py) |
|
205 | * TODO defaults (configitems.py) | |
208 | * TODO extensions |
|
206 | * TODO extensions | |
209 | * TODO Python resources? |
|
207 | * TODO Python resources? | |
210 | * Others? */ |
|
208 | * Others? */ | |
211 | } |
|
209 | } | |
212 |
|
210 | |||
213 | impl ConfigOrigin { |
|
211 | impl ConfigOrigin { | |
214 | /// TODO use some kind of dedicated trait? |
|
212 | /// TODO use some kind of dedicated trait? | |
215 | pub fn to_bytes(&self) -> Vec<u8> { |
|
213 | pub fn to_bytes(&self) -> Vec<u8> { | |
216 | match self { |
|
214 | match self { | |
217 | ConfigOrigin::File(p) => get_bytes_from_path(p), |
|
215 | ConfigOrigin::File(p) => get_bytes_from_path(p), | |
218 | ConfigOrigin::Environment(e) => e.to_owned(), |
|
216 | ConfigOrigin::Environment(e) => e.to_owned(), | |
219 | } |
|
217 | } | |
220 | } |
|
218 | } | |
221 | } |
|
219 | } | |
222 |
|
220 | |||
223 | #[derive(Debug)] |
|
221 | #[derive(Debug)] | |
224 | pub struct ConfigParseError { |
|
222 | pub struct ConfigParseError { | |
225 | pub origin: ConfigOrigin, |
|
223 | pub origin: ConfigOrigin, | |
226 | pub line: Option<usize>, |
|
224 | pub line: Option<usize>, | |
227 | pub bytes: Vec<u8>, |
|
225 | pub bytes: Vec<u8>, | |
228 | } |
|
226 | } | |
229 |
|
227 | |||
230 | #[derive(Debug, derive_more::From)] |
|
228 | #[derive(Debug, derive_more::From)] | |
231 | pub enum ConfigError { |
|
229 | pub enum ConfigError { | |
232 | Parse(ConfigParseError), |
|
230 | Parse(ConfigParseError), | |
233 | Other(HgError), |
|
231 | Other(HgError), | |
234 | } |
|
232 | } | |
235 |
|
233 | |||
236 | fn make_regex(pattern: &'static str) -> Regex { |
|
234 | fn make_regex(pattern: &'static str) -> Regex { | |
237 | Regex::new(pattern).expect("expected a valid regex") |
|
235 | Regex::new(pattern).expect("expected a valid regex") | |
238 | } |
|
236 | } | |
239 |
|
237 | |||
240 | /// Includes are relative to the file they're defined in, unless they're |
|
238 | /// Includes are relative to the file they're defined in, unless they're | |
241 | /// absolute. |
|
239 | /// absolute. | |
242 | fn read_include( |
|
240 | fn read_include( | |
243 | old_src: &Path, |
|
241 | old_src: &Path, | |
244 | new_src: &Path, |
|
242 | new_src: &Path, | |
245 | ) -> (PathBuf, io::Result<Vec<u8>>) { |
|
243 | ) -> (PathBuf, io::Result<Vec<u8>>) { | |
246 | if new_src.is_absolute() { |
|
244 | if new_src.is_absolute() { | |
247 |
(new_src.to_path_buf(), read |
|
245 | (new_src.to_path_buf(), std::fs::read(&new_src)) | |
248 | } else { |
|
246 | } else { | |
249 | let dir = old_src.parent().unwrap(); |
|
247 | let dir = old_src.parent().unwrap(); | |
250 | let new_src = dir.join(&new_src); |
|
248 | let new_src = dir.join(&new_src); | |
251 |
(new_src.to_owned(), read |
|
249 | (new_src.to_owned(), std::fs::read(&new_src)) | |
252 | } |
|
250 | } | |
253 | } |
|
251 | } |
@@ -1,454 +1,442 b'' | |||||
1 | // files.rs |
|
1 | // files.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2019 |
|
3 | // Copyright 2019 | |
4 | // Raphaël Gomès <rgomes@octobus.net>, |
|
4 | // Raphaël Gomès <rgomes@octobus.net>, | |
5 | // Yuya Nishihara <yuya@tcha.org> |
|
5 | // Yuya Nishihara <yuya@tcha.org> | |
6 | // |
|
6 | // | |
7 | // This software may be used and distributed according to the terms of the |
|
7 | // This software may be used and distributed according to the terms of the | |
8 | // GNU General Public License version 2 or any later version. |
|
8 | // GNU General Public License version 2 or any later version. | |
9 |
|
9 | |||
10 | //! Functions for fiddling with files. |
|
10 | //! Functions for fiddling with files. | |
11 |
|
11 | |||
12 | use crate::utils::{ |
|
12 | use crate::utils::{ | |
13 | hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError}, |
|
13 | hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError}, | |
14 | path_auditor::PathAuditor, |
|
14 | path_auditor::PathAuditor, | |
15 | replace_slice, |
|
15 | replace_slice, | |
16 | }; |
|
16 | }; | |
17 | use lazy_static::lazy_static; |
|
17 | use lazy_static::lazy_static; | |
18 | use same_file::is_same_file; |
|
18 | use same_file::is_same_file; | |
19 | use std::borrow::{Cow, ToOwned}; |
|
19 | use std::borrow::{Cow, ToOwned}; | |
20 | use std::fs::Metadata; |
|
20 | use std::fs::Metadata; | |
21 | use std::io::Read; |
|
|||
22 | use std::iter::FusedIterator; |
|
21 | use std::iter::FusedIterator; | |
23 | use std::ops::Deref; |
|
22 | use std::ops::Deref; | |
24 | use std::path::{Path, PathBuf}; |
|
23 | use std::path::{Path, PathBuf}; | |
25 |
|
24 | |||
26 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { |
|
25 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { | |
27 | let os_str; |
|
26 | let os_str; | |
28 | #[cfg(unix)] |
|
27 | #[cfg(unix)] | |
29 | { |
|
28 | { | |
30 | use std::os::unix::ffi::OsStrExt; |
|
29 | use std::os::unix::ffi::OsStrExt; | |
31 | os_str = std::ffi::OsStr::from_bytes(bytes); |
|
30 | os_str = std::ffi::OsStr::from_bytes(bytes); | |
32 | } |
|
31 | } | |
33 | // TODO Handle other platforms |
|
32 | // TODO Handle other platforms | |
34 | // TODO: convert from WTF8 to Windows MBCS (ANSI encoding). |
|
33 | // TODO: convert from WTF8 to Windows MBCS (ANSI encoding). | |
35 | // Perhaps, the return type would have to be Result<PathBuf>. |
|
34 | // Perhaps, the return type would have to be Result<PathBuf>. | |
36 |
|
35 | |||
37 | Path::new(os_str) |
|
36 | Path::new(os_str) | |
38 | } |
|
37 | } | |
39 |
|
38 | |||
40 | // TODO: need to convert from WTF8 to MBCS bytes on Windows. |
|
39 | // TODO: need to convert from WTF8 to MBCS bytes on Windows. | |
41 | // that's why Vec<u8> is returned. |
|
40 | // that's why Vec<u8> is returned. | |
42 | #[cfg(unix)] |
|
41 | #[cfg(unix)] | |
43 | pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> { |
|
42 | pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> { | |
44 | use std::os::unix::ffi::OsStrExt; |
|
43 | use std::os::unix::ffi::OsStrExt; | |
45 | path.as_ref().as_os_str().as_bytes().to_vec() |
|
44 | path.as_ref().as_os_str().as_bytes().to_vec() | |
46 | } |
|
45 | } | |
47 |
|
46 | |||
48 | /// An iterator over repository path yielding itself and its ancestors. |
|
47 | /// An iterator over repository path yielding itself and its ancestors. | |
49 | #[derive(Copy, Clone, Debug)] |
|
48 | #[derive(Copy, Clone, Debug)] | |
50 | pub struct Ancestors<'a> { |
|
49 | pub struct Ancestors<'a> { | |
51 | next: Option<&'a HgPath>, |
|
50 | next: Option<&'a HgPath>, | |
52 | } |
|
51 | } | |
53 |
|
52 | |||
54 | impl<'a> Iterator for Ancestors<'a> { |
|
53 | impl<'a> Iterator for Ancestors<'a> { | |
55 | type Item = &'a HgPath; |
|
54 | type Item = &'a HgPath; | |
56 |
|
55 | |||
57 | fn next(&mut self) -> Option<Self::Item> { |
|
56 | fn next(&mut self) -> Option<Self::Item> { | |
58 | let next = self.next; |
|
57 | let next = self.next; | |
59 | self.next = match self.next { |
|
58 | self.next = match self.next { | |
60 | Some(s) if s.is_empty() => None, |
|
59 | Some(s) if s.is_empty() => None, | |
61 | Some(s) => { |
|
60 | Some(s) => { | |
62 | let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0); |
|
61 | let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0); | |
63 | Some(HgPath::new(&s.as_bytes()[..p])) |
|
62 | Some(HgPath::new(&s.as_bytes()[..p])) | |
64 | } |
|
63 | } | |
65 | None => None, |
|
64 | None => None, | |
66 | }; |
|
65 | }; | |
67 | next |
|
66 | next | |
68 | } |
|
67 | } | |
69 | } |
|
68 | } | |
70 |
|
69 | |||
71 | impl<'a> FusedIterator for Ancestors<'a> {} |
|
70 | impl<'a> FusedIterator for Ancestors<'a> {} | |
72 |
|
71 | |||
73 | /// An iterator over repository path yielding itself and its ancestors. |
|
72 | /// An iterator over repository path yielding itself and its ancestors. | |
74 | #[derive(Copy, Clone, Debug)] |
|
73 | #[derive(Copy, Clone, Debug)] | |
75 | pub(crate) struct AncestorsWithBase<'a> { |
|
74 | pub(crate) struct AncestorsWithBase<'a> { | |
76 | next: Option<(&'a HgPath, &'a HgPath)>, |
|
75 | next: Option<(&'a HgPath, &'a HgPath)>, | |
77 | } |
|
76 | } | |
78 |
|
77 | |||
79 | impl<'a> Iterator for AncestorsWithBase<'a> { |
|
78 | impl<'a> Iterator for AncestorsWithBase<'a> { | |
80 | type Item = (&'a HgPath, &'a HgPath); |
|
79 | type Item = (&'a HgPath, &'a HgPath); | |
81 |
|
80 | |||
82 | fn next(&mut self) -> Option<Self::Item> { |
|
81 | fn next(&mut self) -> Option<Self::Item> { | |
83 | let next = self.next; |
|
82 | let next = self.next; | |
84 | self.next = match self.next { |
|
83 | self.next = match self.next { | |
85 | Some((s, _)) if s.is_empty() => None, |
|
84 | Some((s, _)) if s.is_empty() => None, | |
86 | Some((s, _)) => Some(s.split_filename()), |
|
85 | Some((s, _)) => Some(s.split_filename()), | |
87 | None => None, |
|
86 | None => None, | |
88 | }; |
|
87 | }; | |
89 | next |
|
88 | next | |
90 | } |
|
89 | } | |
91 | } |
|
90 | } | |
92 |
|
91 | |||
93 | impl<'a> FusedIterator for AncestorsWithBase<'a> {} |
|
92 | impl<'a> FusedIterator for AncestorsWithBase<'a> {} | |
94 |
|
93 | |||
95 | /// Returns an iterator yielding ancestor directories of the given repository |
|
94 | /// Returns an iterator yielding ancestor directories of the given repository | |
96 | /// path. |
|
95 | /// path. | |
97 | /// |
|
96 | /// | |
98 | /// The path is separated by '/', and must not start with '/'. |
|
97 | /// The path is separated by '/', and must not start with '/'. | |
99 | /// |
|
98 | /// | |
100 | /// The path itself isn't included unless it is b"" (meaning the root |
|
99 | /// The path itself isn't included unless it is b"" (meaning the root | |
101 | /// directory.) |
|
100 | /// directory.) | |
102 | pub fn find_dirs(path: &HgPath) -> Ancestors { |
|
101 | pub fn find_dirs(path: &HgPath) -> Ancestors { | |
103 | let mut dirs = Ancestors { next: Some(path) }; |
|
102 | let mut dirs = Ancestors { next: Some(path) }; | |
104 | if !path.is_empty() { |
|
103 | if !path.is_empty() { | |
105 | dirs.next(); // skip itself |
|
104 | dirs.next(); // skip itself | |
106 | } |
|
105 | } | |
107 | dirs |
|
106 | dirs | |
108 | } |
|
107 | } | |
109 |
|
108 | |||
110 | /// Returns an iterator yielding ancestor directories of the given repository |
|
109 | /// Returns an iterator yielding ancestor directories of the given repository | |
111 | /// path. |
|
110 | /// path. | |
112 | /// |
|
111 | /// | |
113 | /// The path is separated by '/', and must not start with '/'. |
|
112 | /// The path is separated by '/', and must not start with '/'. | |
114 | /// |
|
113 | /// | |
115 | /// The path itself isn't included unless it is b"" (meaning the root |
|
114 | /// The path itself isn't included unless it is b"" (meaning the root | |
116 | /// directory.) |
|
115 | /// directory.) | |
117 | pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase { |
|
116 | pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase { | |
118 | let mut dirs = AncestorsWithBase { |
|
117 | let mut dirs = AncestorsWithBase { | |
119 | next: Some((path, HgPath::new(b""))), |
|
118 | next: Some((path, HgPath::new(b""))), | |
120 | }; |
|
119 | }; | |
121 | if !path.is_empty() { |
|
120 | if !path.is_empty() { | |
122 | dirs.next(); // skip itself |
|
121 | dirs.next(); // skip itself | |
123 | } |
|
122 | } | |
124 | dirs |
|
123 | dirs | |
125 | } |
|
124 | } | |
126 |
|
125 | |||
127 | /// TODO more than ASCII? |
|
126 | /// TODO more than ASCII? | |
128 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { |
|
127 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { | |
129 | #[cfg(windows)] // NTFS compares via upper() |
|
128 | #[cfg(windows)] // NTFS compares via upper() | |
130 | return path.to_ascii_uppercase(); |
|
129 | return path.to_ascii_uppercase(); | |
131 | #[cfg(unix)] |
|
130 | #[cfg(unix)] | |
132 | path.to_ascii_lowercase() |
|
131 | path.to_ascii_lowercase() | |
133 | } |
|
132 | } | |
134 |
|
133 | |||
135 | lazy_static! { |
|
134 | lazy_static! { | |
136 | static ref IGNORED_CHARS: Vec<Vec<u8>> = { |
|
135 | static ref IGNORED_CHARS: Vec<Vec<u8>> = { | |
137 | [ |
|
136 | [ | |
138 | 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, |
|
137 | 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, | |
139 | 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff, |
|
138 | 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff, | |
140 | ] |
|
139 | ] | |
141 | .iter() |
|
140 | .iter() | |
142 | .map(|code| { |
|
141 | .map(|code| { | |
143 | std::char::from_u32(*code) |
|
142 | std::char::from_u32(*code) | |
144 | .unwrap() |
|
143 | .unwrap() | |
145 | .encode_utf8(&mut [0; 3]) |
|
144 | .encode_utf8(&mut [0; 3]) | |
146 | .bytes() |
|
145 | .bytes() | |
147 | .collect() |
|
146 | .collect() | |
148 | }) |
|
147 | }) | |
149 | .collect() |
|
148 | .collect() | |
150 | }; |
|
149 | }; | |
151 | } |
|
150 | } | |
152 |
|
151 | |||
153 | fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> { |
|
152 | fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> { | |
154 | let mut buf = bytes.to_owned(); |
|
153 | let mut buf = bytes.to_owned(); | |
155 | let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef'); |
|
154 | let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef'); | |
156 | if needs_escaping { |
|
155 | if needs_escaping { | |
157 | for forbidden in IGNORED_CHARS.iter() { |
|
156 | for forbidden in IGNORED_CHARS.iter() { | |
158 | replace_slice(&mut buf, forbidden, &[]) |
|
157 | replace_slice(&mut buf, forbidden, &[]) | |
159 | } |
|
158 | } | |
160 | buf |
|
159 | buf | |
161 | } else { |
|
160 | } else { | |
162 | buf |
|
161 | buf | |
163 | } |
|
162 | } | |
164 | } |
|
163 | } | |
165 |
|
164 | |||
166 | pub fn lower_clean(bytes: &[u8]) -> Vec<u8> { |
|
165 | pub fn lower_clean(bytes: &[u8]) -> Vec<u8> { | |
167 | hfs_ignore_clean(&bytes.to_ascii_lowercase()) |
|
166 | hfs_ignore_clean(&bytes.to_ascii_lowercase()) | |
168 | } |
|
167 | } | |
169 |
|
168 | |||
170 | #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] |
|
169 | #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] | |
171 | pub struct HgMetadata { |
|
170 | pub struct HgMetadata { | |
172 | pub st_dev: u64, |
|
171 | pub st_dev: u64, | |
173 | pub st_mode: u32, |
|
172 | pub st_mode: u32, | |
174 | pub st_nlink: u64, |
|
173 | pub st_nlink: u64, | |
175 | pub st_size: u64, |
|
174 | pub st_size: u64, | |
176 | pub st_mtime: i64, |
|
175 | pub st_mtime: i64, | |
177 | pub st_ctime: i64, |
|
176 | pub st_ctime: i64, | |
178 | } |
|
177 | } | |
179 |
|
178 | |||
180 | // TODO support other plaforms |
|
179 | // TODO support other plaforms | |
181 | #[cfg(unix)] |
|
180 | #[cfg(unix)] | |
182 | impl HgMetadata { |
|
181 | impl HgMetadata { | |
183 | pub fn from_metadata(metadata: Metadata) -> Self { |
|
182 | pub fn from_metadata(metadata: Metadata) -> Self { | |
184 | use std::os::unix::fs::MetadataExt; |
|
183 | use std::os::unix::fs::MetadataExt; | |
185 | Self { |
|
184 | Self { | |
186 | st_dev: metadata.dev(), |
|
185 | st_dev: metadata.dev(), | |
187 | st_mode: metadata.mode(), |
|
186 | st_mode: metadata.mode(), | |
188 | st_nlink: metadata.nlink(), |
|
187 | st_nlink: metadata.nlink(), | |
189 | st_size: metadata.size(), |
|
188 | st_size: metadata.size(), | |
190 | st_mtime: metadata.mtime(), |
|
189 | st_mtime: metadata.mtime(), | |
191 | st_ctime: metadata.ctime(), |
|
190 | st_ctime: metadata.ctime(), | |
192 | } |
|
191 | } | |
193 | } |
|
192 | } | |
194 | } |
|
193 | } | |
195 |
|
194 | |||
196 | /// Returns the canonical path of `name`, given `cwd` and `root` |
|
195 | /// Returns the canonical path of `name`, given `cwd` and `root` | |
197 | pub fn canonical_path( |
|
196 | pub fn canonical_path( | |
198 | root: impl AsRef<Path>, |
|
197 | root: impl AsRef<Path>, | |
199 | cwd: impl AsRef<Path>, |
|
198 | cwd: impl AsRef<Path>, | |
200 | name: impl AsRef<Path>, |
|
199 | name: impl AsRef<Path>, | |
201 | ) -> Result<PathBuf, HgPathError> { |
|
200 | ) -> Result<PathBuf, HgPathError> { | |
202 | // TODO add missing normalization for other platforms |
|
201 | // TODO add missing normalization for other platforms | |
203 | let root = root.as_ref(); |
|
202 | let root = root.as_ref(); | |
204 | let cwd = cwd.as_ref(); |
|
203 | let cwd = cwd.as_ref(); | |
205 | let name = name.as_ref(); |
|
204 | let name = name.as_ref(); | |
206 |
|
205 | |||
207 | let name = if !name.is_absolute() { |
|
206 | let name = if !name.is_absolute() { | |
208 | root.join(&cwd).join(&name) |
|
207 | root.join(&cwd).join(&name) | |
209 | } else { |
|
208 | } else { | |
210 | name.to_owned() |
|
209 | name.to_owned() | |
211 | }; |
|
210 | }; | |
212 | let auditor = PathAuditor::new(&root); |
|
211 | let auditor = PathAuditor::new(&root); | |
213 | if name != root && name.starts_with(&root) { |
|
212 | if name != root && name.starts_with(&root) { | |
214 | let name = name.strip_prefix(&root).unwrap(); |
|
213 | let name = name.strip_prefix(&root).unwrap(); | |
215 | auditor.audit_path(path_to_hg_path_buf(name)?)?; |
|
214 | auditor.audit_path(path_to_hg_path_buf(name)?)?; | |
216 | Ok(name.to_owned()) |
|
215 | Ok(name.to_owned()) | |
217 | } else if name == root { |
|
216 | } else if name == root { | |
218 | Ok("".into()) |
|
217 | Ok("".into()) | |
219 | } else { |
|
218 | } else { | |
220 | // Determine whether `name' is in the hierarchy at or beneath `root', |
|
219 | // Determine whether `name' is in the hierarchy at or beneath `root', | |
221 | // by iterating name=name.parent() until it returns `None` (can't |
|
220 | // by iterating name=name.parent() until it returns `None` (can't | |
222 | // check name == '/', because that doesn't work on windows). |
|
221 | // check name == '/', because that doesn't work on windows). | |
223 | let mut name = name.deref(); |
|
222 | let mut name = name.deref(); | |
224 | let original_name = name.to_owned(); |
|
223 | let original_name = name.to_owned(); | |
225 | loop { |
|
224 | loop { | |
226 | let same = is_same_file(&name, &root).unwrap_or(false); |
|
225 | let same = is_same_file(&name, &root).unwrap_or(false); | |
227 | if same { |
|
226 | if same { | |
228 | if name == original_name { |
|
227 | if name == original_name { | |
229 | // `name` was actually the same as root (maybe a symlink) |
|
228 | // `name` was actually the same as root (maybe a symlink) | |
230 | return Ok("".into()); |
|
229 | return Ok("".into()); | |
231 | } |
|
230 | } | |
232 | // `name` is a symlink to root, so `original_name` is under |
|
231 | // `name` is a symlink to root, so `original_name` is under | |
233 | // root |
|
232 | // root | |
234 | let rel_path = original_name.strip_prefix(&name).unwrap(); |
|
233 | let rel_path = original_name.strip_prefix(&name).unwrap(); | |
235 | auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?; |
|
234 | auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?; | |
236 | return Ok(rel_path.to_owned()); |
|
235 | return Ok(rel_path.to_owned()); | |
237 | } |
|
236 | } | |
238 | name = match name.parent() { |
|
237 | name = match name.parent() { | |
239 | None => break, |
|
238 | None => break, | |
240 | Some(p) => p, |
|
239 | Some(p) => p, | |
241 | }; |
|
240 | }; | |
242 | } |
|
241 | } | |
243 | // TODO hint to the user about using --cwd |
|
242 | // TODO hint to the user about using --cwd | |
244 | // Bubble up the responsibility to Python for now |
|
243 | // Bubble up the responsibility to Python for now | |
245 | Err(HgPathError::NotUnderRoot { |
|
244 | Err(HgPathError::NotUnderRoot { | |
246 | path: original_name.to_owned(), |
|
245 | path: original_name.to_owned(), | |
247 | root: root.to_owned(), |
|
246 | root: root.to_owned(), | |
248 | }) |
|
247 | }) | |
249 | } |
|
248 | } | |
250 | } |
|
249 | } | |
251 |
|
250 | |||
252 | /// Returns the representation of the path relative to the current working |
|
251 | /// Returns the representation of the path relative to the current working | |
253 | /// directory for display purposes. |
|
252 | /// directory for display purposes. | |
254 | /// |
|
253 | /// | |
255 | /// `cwd` is a `HgPath`, so it is considered relative to the root directory |
|
254 | /// `cwd` is a `HgPath`, so it is considered relative to the root directory | |
256 | /// of the repository. |
|
255 | /// of the repository. | |
257 | /// |
|
256 | /// | |
258 | /// # Examples |
|
257 | /// # Examples | |
259 | /// |
|
258 | /// | |
260 | /// ``` |
|
259 | /// ``` | |
261 | /// use hg::utils::hg_path::HgPath; |
|
260 | /// use hg::utils::hg_path::HgPath; | |
262 | /// use hg::utils::files::relativize_path; |
|
261 | /// use hg::utils::files::relativize_path; | |
263 | /// use std::borrow::Cow; |
|
262 | /// use std::borrow::Cow; | |
264 | /// |
|
263 | /// | |
265 | /// let file = HgPath::new(b"nested/file"); |
|
264 | /// let file = HgPath::new(b"nested/file"); | |
266 | /// let cwd = HgPath::new(b""); |
|
265 | /// let cwd = HgPath::new(b""); | |
267 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file")); |
|
266 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file")); | |
268 | /// |
|
267 | /// | |
269 | /// let cwd = HgPath::new(b"nested"); |
|
268 | /// let cwd = HgPath::new(b"nested"); | |
270 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file")); |
|
269 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file")); | |
271 | /// |
|
270 | /// | |
272 | /// let cwd = HgPath::new(b"other"); |
|
271 | /// let cwd = HgPath::new(b"other"); | |
273 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file")); |
|
272 | /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file")); | |
274 | /// ``` |
|
273 | /// ``` | |
275 | pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> { |
|
274 | pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> { | |
276 | if cwd.as_ref().is_empty() { |
|
275 | if cwd.as_ref().is_empty() { | |
277 | Cow::Borrowed(path.as_bytes()) |
|
276 | Cow::Borrowed(path.as_bytes()) | |
278 | } else { |
|
277 | } else { | |
279 | let mut res: Vec<u8> = Vec::new(); |
|
278 | let mut res: Vec<u8> = Vec::new(); | |
280 | let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable(); |
|
279 | let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable(); | |
281 | let mut cwd_iter = |
|
280 | let mut cwd_iter = | |
282 | cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable(); |
|
281 | cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable(); | |
283 | loop { |
|
282 | loop { | |
284 | match (path_iter.peek(), cwd_iter.peek()) { |
|
283 | match (path_iter.peek(), cwd_iter.peek()) { | |
285 | (Some(a), Some(b)) if a == b => (), |
|
284 | (Some(a), Some(b)) if a == b => (), | |
286 | _ => break, |
|
285 | _ => break, | |
287 | } |
|
286 | } | |
288 | path_iter.next(); |
|
287 | path_iter.next(); | |
289 | cwd_iter.next(); |
|
288 | cwd_iter.next(); | |
290 | } |
|
289 | } | |
291 | let mut need_sep = false; |
|
290 | let mut need_sep = false; | |
292 | for _ in cwd_iter { |
|
291 | for _ in cwd_iter { | |
293 | if need_sep { |
|
292 | if need_sep { | |
294 | res.extend(b"/") |
|
293 | res.extend(b"/") | |
295 | } else { |
|
294 | } else { | |
296 | need_sep = true |
|
295 | need_sep = true | |
297 | }; |
|
296 | }; | |
298 | res.extend(b".."); |
|
297 | res.extend(b".."); | |
299 | } |
|
298 | } | |
300 | for c in path_iter { |
|
299 | for c in path_iter { | |
301 | if need_sep { |
|
300 | if need_sep { | |
302 | res.extend(b"/") |
|
301 | res.extend(b"/") | |
303 | } else { |
|
302 | } else { | |
304 | need_sep = true |
|
303 | need_sep = true | |
305 | }; |
|
304 | }; | |
306 | res.extend(c); |
|
305 | res.extend(c); | |
307 | } |
|
306 | } | |
308 | Cow::Owned(res) |
|
307 | Cow::Owned(res) | |
309 | } |
|
308 | } | |
310 | } |
|
309 | } | |
311 |
|
310 | |||
312 | /// Reads a file in one big chunk instead of doing multiple reads |
|
|||
313 | pub fn read_whole_file(filepath: &Path) -> std::io::Result<Vec<u8>> { |
|
|||
314 | let mut file = std::fs::File::open(filepath)?; |
|
|||
315 | let size = file.metadata()?.len(); |
|
|||
316 |
|
||||
317 | let mut res = vec![0; size as usize]; |
|
|||
318 | file.read_exact(&mut res)?; |
|
|||
319 |
|
||||
320 | Ok(res) |
|
|||
321 | } |
|
|||
322 |
|
||||
323 | #[cfg(test)] |
|
311 | #[cfg(test)] | |
324 | mod tests { |
|
312 | mod tests { | |
325 | use super::*; |
|
313 | use super::*; | |
326 | use pretty_assertions::assert_eq; |
|
314 | use pretty_assertions::assert_eq; | |
327 |
|
315 | |||
328 | #[test] |
|
316 | #[test] | |
329 | fn find_dirs_some() { |
|
317 | fn find_dirs_some() { | |
330 | let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); |
|
318 | let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); | |
331 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); |
|
319 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); | |
332 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo"))); |
|
320 | assert_eq!(dirs.next(), Some(HgPath::new(b"foo"))); | |
333 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); |
|
321 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); | |
334 | assert_eq!(dirs.next(), None); |
|
322 | assert_eq!(dirs.next(), None); | |
335 | assert_eq!(dirs.next(), None); |
|
323 | assert_eq!(dirs.next(), None); | |
336 | } |
|
324 | } | |
337 |
|
325 | |||
338 | #[test] |
|
326 | #[test] | |
339 | fn find_dirs_empty() { |
|
327 | fn find_dirs_empty() { | |
340 | // looks weird, but mercurial.pathutil.finddirs(b"") yields b"" |
|
328 | // looks weird, but mercurial.pathutil.finddirs(b"") yields b"" | |
341 | let mut dirs = super::find_dirs(HgPath::new(b"")); |
|
329 | let mut dirs = super::find_dirs(HgPath::new(b"")); | |
342 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); |
|
330 | assert_eq!(dirs.next(), Some(HgPath::new(b""))); | |
343 | assert_eq!(dirs.next(), None); |
|
331 | assert_eq!(dirs.next(), None); | |
344 | assert_eq!(dirs.next(), None); |
|
332 | assert_eq!(dirs.next(), None); | |
345 | } |
|
333 | } | |
346 |
|
334 | |||
347 | #[test] |
|
335 | #[test] | |
348 | fn test_find_dirs_with_base_some() { |
|
336 | fn test_find_dirs_with_base_some() { | |
349 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz")); |
|
337 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz")); | |
350 | assert_eq!( |
|
338 | assert_eq!( | |
351 | dirs.next(), |
|
339 | dirs.next(), | |
352 | Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz"))) |
|
340 | Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz"))) | |
353 | ); |
|
341 | ); | |
354 | assert_eq!( |
|
342 | assert_eq!( | |
355 | dirs.next(), |
|
343 | dirs.next(), | |
356 | Some((HgPath::new(b"foo"), HgPath::new(b"bar"))) |
|
344 | Some((HgPath::new(b"foo"), HgPath::new(b"bar"))) | |
357 | ); |
|
345 | ); | |
358 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo")))); |
|
346 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo")))); | |
359 | assert_eq!(dirs.next(), None); |
|
347 | assert_eq!(dirs.next(), None); | |
360 | assert_eq!(dirs.next(), None); |
|
348 | assert_eq!(dirs.next(), None); | |
361 | } |
|
349 | } | |
362 |
|
350 | |||
363 | #[test] |
|
351 | #[test] | |
364 | fn test_find_dirs_with_base_empty() { |
|
352 | fn test_find_dirs_with_base_empty() { | |
365 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); |
|
353 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); | |
366 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); |
|
354 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); | |
367 | assert_eq!(dirs.next(), None); |
|
355 | assert_eq!(dirs.next(), None); | |
368 | assert_eq!(dirs.next(), None); |
|
356 | assert_eq!(dirs.next(), None); | |
369 | } |
|
357 | } | |
370 |
|
358 | |||
371 | #[test] |
|
359 | #[test] | |
372 | fn test_canonical_path() { |
|
360 | fn test_canonical_path() { | |
373 | let root = Path::new("/repo"); |
|
361 | let root = Path::new("/repo"); | |
374 | let cwd = Path::new("/dir"); |
|
362 | let cwd = Path::new("/dir"); | |
375 | let name = Path::new("filename"); |
|
363 | let name = Path::new("filename"); | |
376 | assert_eq!( |
|
364 | assert_eq!( | |
377 | canonical_path(root, cwd, name), |
|
365 | canonical_path(root, cwd, name), | |
378 | Err(HgPathError::NotUnderRoot { |
|
366 | Err(HgPathError::NotUnderRoot { | |
379 | path: PathBuf::from("/dir/filename"), |
|
367 | path: PathBuf::from("/dir/filename"), | |
380 | root: root.to_path_buf() |
|
368 | root: root.to_path_buf() | |
381 | }) |
|
369 | }) | |
382 | ); |
|
370 | ); | |
383 |
|
371 | |||
384 | let root = Path::new("/repo"); |
|
372 | let root = Path::new("/repo"); | |
385 | let cwd = Path::new("/"); |
|
373 | let cwd = Path::new("/"); | |
386 | let name = Path::new("filename"); |
|
374 | let name = Path::new("filename"); | |
387 | assert_eq!( |
|
375 | assert_eq!( | |
388 | canonical_path(root, cwd, name), |
|
376 | canonical_path(root, cwd, name), | |
389 | Err(HgPathError::NotUnderRoot { |
|
377 | Err(HgPathError::NotUnderRoot { | |
390 | path: PathBuf::from("/filename"), |
|
378 | path: PathBuf::from("/filename"), | |
391 | root: root.to_path_buf() |
|
379 | root: root.to_path_buf() | |
392 | }) |
|
380 | }) | |
393 | ); |
|
381 | ); | |
394 |
|
382 | |||
395 | let root = Path::new("/repo"); |
|
383 | let root = Path::new("/repo"); | |
396 | let cwd = Path::new("/"); |
|
384 | let cwd = Path::new("/"); | |
397 | let name = Path::new("repo/filename"); |
|
385 | let name = Path::new("repo/filename"); | |
398 | assert_eq!( |
|
386 | assert_eq!( | |
399 | canonical_path(root, cwd, name), |
|
387 | canonical_path(root, cwd, name), | |
400 | Ok(PathBuf::from("filename")) |
|
388 | Ok(PathBuf::from("filename")) | |
401 | ); |
|
389 | ); | |
402 |
|
390 | |||
403 | let root = Path::new("/repo"); |
|
391 | let root = Path::new("/repo"); | |
404 | let cwd = Path::new("/repo"); |
|
392 | let cwd = Path::new("/repo"); | |
405 | let name = Path::new("filename"); |
|
393 | let name = Path::new("filename"); | |
406 | assert_eq!( |
|
394 | assert_eq!( | |
407 | canonical_path(root, cwd, name), |
|
395 | canonical_path(root, cwd, name), | |
408 | Ok(PathBuf::from("filename")) |
|
396 | Ok(PathBuf::from("filename")) | |
409 | ); |
|
397 | ); | |
410 |
|
398 | |||
411 | let root = Path::new("/repo"); |
|
399 | let root = Path::new("/repo"); | |
412 | let cwd = Path::new("/repo/subdir"); |
|
400 | let cwd = Path::new("/repo/subdir"); | |
413 | let name = Path::new("filename"); |
|
401 | let name = Path::new("filename"); | |
414 | assert_eq!( |
|
402 | assert_eq!( | |
415 | canonical_path(root, cwd, name), |
|
403 | canonical_path(root, cwd, name), | |
416 | Ok(PathBuf::from("subdir/filename")) |
|
404 | Ok(PathBuf::from("subdir/filename")) | |
417 | ); |
|
405 | ); | |
418 | } |
|
406 | } | |
419 |
|
407 | |||
420 | #[test] |
|
408 | #[test] | |
421 | fn test_canonical_path_not_rooted() { |
|
409 | fn test_canonical_path_not_rooted() { | |
422 | use std::fs::create_dir; |
|
410 | use std::fs::create_dir; | |
423 | use tempfile::tempdir; |
|
411 | use tempfile::tempdir; | |
424 |
|
412 | |||
425 | let base_dir = tempdir().unwrap(); |
|
413 | let base_dir = tempdir().unwrap(); | |
426 | let base_dir_path = base_dir.path(); |
|
414 | let base_dir_path = base_dir.path(); | |
427 | let beneath_repo = base_dir_path.join("a"); |
|
415 | let beneath_repo = base_dir_path.join("a"); | |
428 | let root = base_dir_path.join("a/b"); |
|
416 | let root = base_dir_path.join("a/b"); | |
429 | let out_of_repo = base_dir_path.join("c"); |
|
417 | let out_of_repo = base_dir_path.join("c"); | |
430 | let under_repo_symlink = out_of_repo.join("d"); |
|
418 | let under_repo_symlink = out_of_repo.join("d"); | |
431 |
|
419 | |||
432 | create_dir(&beneath_repo).unwrap(); |
|
420 | create_dir(&beneath_repo).unwrap(); | |
433 | create_dir(&root).unwrap(); |
|
421 | create_dir(&root).unwrap(); | |
434 |
|
422 | |||
435 | // TODO make portable |
|
423 | // TODO make portable | |
436 | std::os::unix::fs::symlink(&root, &out_of_repo).unwrap(); |
|
424 | std::os::unix::fs::symlink(&root, &out_of_repo).unwrap(); | |
437 |
|
425 | |||
438 | assert_eq!( |
|
426 | assert_eq!( | |
439 | canonical_path(&root, Path::new(""), out_of_repo), |
|
427 | canonical_path(&root, Path::new(""), out_of_repo), | |
440 | Ok(PathBuf::from("")) |
|
428 | Ok(PathBuf::from("")) | |
441 | ); |
|
429 | ); | |
442 | assert_eq!( |
|
430 | assert_eq!( | |
443 | canonical_path(&root, Path::new(""), &beneath_repo), |
|
431 | canonical_path(&root, Path::new(""), &beneath_repo), | |
444 | Err(HgPathError::NotUnderRoot { |
|
432 | Err(HgPathError::NotUnderRoot { | |
445 | path: beneath_repo.to_owned(), |
|
433 | path: beneath_repo.to_owned(), | |
446 | root: root.to_owned() |
|
434 | root: root.to_owned() | |
447 | }) |
|
435 | }) | |
448 | ); |
|
436 | ); | |
449 | assert_eq!( |
|
437 | assert_eq!( | |
450 | canonical_path(&root, Path::new(""), &under_repo_symlink), |
|
438 | canonical_path(&root, Path::new(""), &under_repo_symlink), | |
451 | Ok(PathBuf::from("d")) |
|
439 | Ok(PathBuf::from("d")) | |
452 | ); |
|
440 | ); | |
453 | } |
|
441 | } | |
454 | } |
|
442 | } |
General Comments 0
You need to be logged in to leave comments.
Login now