##// END OF EJS Templates
rust: replace read_whole_file with std::fs::read...
Simon Sapin -
r47210:0d734c0a default
parent child Browse files
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_whole_file(&c) {
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(&section, &item) {
94 match self.get_inner(&section, &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(&section, &item) {
133 if let Some(v) = layer.get(&section, &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(&section, &item) {
146 if let Some(v) = layer.get(&section, &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(&section) {
135 if let Some(map) = current_layer.sections.get_mut(&section) {
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_whole_file(&new_src))
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_whole_file(&new_src))
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