##// END OF EJS Templates
rhg: Ignore trailing newlines in .hg/sharedpath...
Simon Sapin -
r47427:e8cd519a default
parent child Browse files
Show More
@@ -1,263 +1,264 b''
1 use crate::config::{Config, ConfigError, ConfigParseError};
1 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
3 use crate::requirements;
3 use crate::requirements;
4 use crate::utils::current_dir;
5 use crate::utils::files::get_path_from_bytes;
4 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::{current_dir, SliceExt};
6 use memmap::{Mmap, MmapOptions};
6 use memmap::{Mmap, MmapOptions};
7 use std::collections::HashSet;
7 use std::collections::HashSet;
8 use std::path::{Path, PathBuf};
8 use std::path::{Path, PathBuf};
9
9
10 /// A repository on disk
10 /// A repository on disk
11 pub struct Repo {
11 pub struct Repo {
12 working_directory: PathBuf,
12 working_directory: PathBuf,
13 dot_hg: PathBuf,
13 dot_hg: PathBuf,
14 store: PathBuf,
14 store: PathBuf,
15 requirements: HashSet<String>,
15 requirements: HashSet<String>,
16 config: Config,
16 config: Config,
17 }
17 }
18
18
19 #[derive(Debug, derive_more::From)]
19 #[derive(Debug, derive_more::From)]
20 pub enum RepoError {
20 pub enum RepoError {
21 NotFound {
21 NotFound {
22 at: PathBuf,
22 at: PathBuf,
23 },
23 },
24 #[from]
24 #[from]
25 ConfigParseError(ConfigParseError),
25 ConfigParseError(ConfigParseError),
26 #[from]
26 #[from]
27 Other(HgError),
27 Other(HgError),
28 }
28 }
29
29
30 impl From<ConfigError> for RepoError {
30 impl From<ConfigError> for RepoError {
31 fn from(error: ConfigError) -> Self {
31 fn from(error: ConfigError) -> Self {
32 match error {
32 match error {
33 ConfigError::Parse(error) => error.into(),
33 ConfigError::Parse(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 #[derive(Clone, Copy)]
40 #[derive(Clone, Copy)]
41 pub struct Vfs<'a> {
41 pub struct Vfs<'a> {
42 pub(crate) base: &'a Path,
42 pub(crate) base: &'a Path,
43 }
43 }
44
44
45 impl Repo {
45 impl Repo {
46 /// Find a repository, either at the given path (which must contain a `.hg`
46 /// Find a repository, either at the given path (which must contain a `.hg`
47 /// sub-directory) or by searching the current directory and its
47 /// sub-directory) or by searching the current directory and its
48 /// ancestors.
48 /// ancestors.
49 ///
49 ///
50 /// A method with two very different "modes" like this usually a code smell
50 /// A method with two very different "modes" like this usually a code smell
51 /// to make two methods instead, but in this case an `Option` is what rhg
51 /// to make two methods instead, but in this case an `Option` is what rhg
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 /// Having two methods would just move that `if` to almost all callers.
53 /// Having two methods would just move that `if` to almost all callers.
54 pub fn find(
54 pub fn find(
55 config: &Config,
55 config: &Config,
56 explicit_path: Option<&Path>,
56 explicit_path: Option<&Path>,
57 ) -> Result<Self, RepoError> {
57 ) -> Result<Self, RepoError> {
58 if let Some(root) = explicit_path {
58 if let Some(root) = explicit_path {
59 // Having an absolute path isn’t necessary here but can help code
59 // Having an absolute path isn’t necessary here but can help code
60 // elsewhere
60 // elsewhere
61 let root = current_dir()?.join(root);
61 let root = current_dir()?.join(root);
62 if root.join(".hg").is_dir() {
62 if root.join(".hg").is_dir() {
63 Self::new_at_path(root, config)
63 Self::new_at_path(root, config)
64 } else {
64 } else {
65 Err(RepoError::NotFound {
65 Err(RepoError::NotFound {
66 at: root.to_owned(),
66 at: root.to_owned(),
67 })
67 })
68 }
68 }
69 } else {
69 } else {
70 let current_directory = crate::utils::current_dir()?;
70 let current_directory = crate::utils::current_dir()?;
71 // ancestors() is inclusive: it first yields `current_directory`
71 // ancestors() is inclusive: it first yields `current_directory`
72 // as-is.
72 // as-is.
73 for ancestor in current_directory.ancestors() {
73 for ancestor in current_directory.ancestors() {
74 if ancestor.join(".hg").is_dir() {
74 if ancestor.join(".hg").is_dir() {
75 return Self::new_at_path(ancestor.to_owned(), config);
75 return Self::new_at_path(ancestor.to_owned(), config);
76 }
76 }
77 }
77 }
78 Err(RepoError::NotFound {
78 Err(RepoError::NotFound {
79 at: current_directory,
79 at: current_directory,
80 })
80 })
81 }
81 }
82 }
82 }
83
83
84 /// To be called after checking that `.hg` is a sub-directory
84 /// To be called after checking that `.hg` is a sub-directory
85 fn new_at_path(
85 fn new_at_path(
86 working_directory: PathBuf,
86 working_directory: PathBuf,
87 config: &Config,
87 config: &Config,
88 ) -> Result<Self, RepoError> {
88 ) -> Result<Self, RepoError> {
89 let dot_hg = working_directory.join(".hg");
89 let dot_hg = working_directory.join(".hg");
90
90
91 let mut repo_config_files = Vec::new();
91 let mut repo_config_files = Vec::new();
92 repo_config_files.push(dot_hg.join("hgrc"));
92 repo_config_files.push(dot_hg.join("hgrc"));
93 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
93 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
94
94
95 let hg_vfs = Vfs { base: &dot_hg };
95 let hg_vfs = Vfs { base: &dot_hg };
96 let mut reqs = requirements::load_if_exists(hg_vfs)?;
96 let mut reqs = requirements::load_if_exists(hg_vfs)?;
97 let relative =
97 let relative =
98 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
98 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
99 let shared =
99 let shared =
100 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
100 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
101
101
102 // From `mercurial/localrepo.py`:
102 // From `mercurial/localrepo.py`:
103 //
103 //
104 // if .hg/requires contains the sharesafe requirement, it means
104 // if .hg/requires contains the sharesafe requirement, it means
105 // there exists a `.hg/store/requires` too and we should read it
105 // there exists a `.hg/store/requires` too and we should read it
106 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
106 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
107 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
107 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
108 // is not present, refer checkrequirementscompat() for that
108 // is not present, refer checkrequirementscompat() for that
109 //
109 //
110 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
110 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
111 // repository was shared the old way. We check the share source
111 // repository was shared the old way. We check the share source
112 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
112 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
113 // current repository needs to be reshared
113 // current repository needs to be reshared
114 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
114 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
115
115
116 let store_path;
116 let store_path;
117 if !shared {
117 if !shared {
118 store_path = dot_hg.join("store");
118 store_path = dot_hg.join("store");
119 } else {
119 } else {
120 let bytes = hg_vfs.read("sharedpath")?;
120 let bytes = hg_vfs.read("sharedpath")?;
121 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
121 let mut shared_path =
122 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
122 if relative {
123 if relative {
123 shared_path = dot_hg.join(shared_path)
124 shared_path = dot_hg.join(shared_path)
124 }
125 }
125 if !shared_path.is_dir() {
126 if !shared_path.is_dir() {
126 return Err(HgError::corrupted(format!(
127 return Err(HgError::corrupted(format!(
127 ".hg/sharedpath points to nonexistent directory {}",
128 ".hg/sharedpath points to nonexistent directory {}",
128 shared_path.display()
129 shared_path.display()
129 ))
130 ))
130 .into());
131 .into());
131 }
132 }
132
133
133 store_path = shared_path.join("store");
134 store_path = shared_path.join("store");
134
135
135 let source_is_share_safe =
136 let source_is_share_safe =
136 requirements::load(Vfs { base: &shared_path })?
137 requirements::load(Vfs { base: &shared_path })?
137 .contains(requirements::SHARESAFE_REQUIREMENT);
138 .contains(requirements::SHARESAFE_REQUIREMENT);
138
139
139 if share_safe && !source_is_share_safe {
140 if share_safe && !source_is_share_safe {
140 return Err(match config
141 return Err(match config
141 .get(b"safe-mismatch", b"source-not-safe")
142 .get(b"safe-mismatch", b"source-not-safe")
142 {
143 {
143 Some(b"abort") | None => HgError::abort(
144 Some(b"abort") | None => HgError::abort(
144 "share source does not support share-safe requirement",
145 "share source does not support share-safe requirement",
145 ),
146 ),
146 _ => HgError::unsupported("share-safe downgrade"),
147 _ => HgError::unsupported("share-safe downgrade"),
147 }
148 }
148 .into());
149 .into());
149 } else if source_is_share_safe && !share_safe {
150 } else if source_is_share_safe && !share_safe {
150 return Err(
151 return Err(
151 match config.get(b"safe-mismatch", b"source-safe") {
152 match config.get(b"safe-mismatch", b"source-safe") {
152 Some(b"abort") | None => HgError::abort(
153 Some(b"abort") | None => HgError::abort(
153 "version mismatch: source uses share-safe \
154 "version mismatch: source uses share-safe \
154 functionality while the current share does not",
155 functionality while the current share does not",
155 ),
156 ),
156 _ => HgError::unsupported("share-safe upgrade"),
157 _ => HgError::unsupported("share-safe upgrade"),
157 }
158 }
158 .into(),
159 .into(),
159 );
160 );
160 }
161 }
161
162
162 if share_safe {
163 if share_safe {
163 repo_config_files.insert(0, shared_path.join("hgrc"))
164 repo_config_files.insert(0, shared_path.join("hgrc"))
164 }
165 }
165 }
166 }
166 if share_safe {
167 if share_safe {
167 reqs.extend(requirements::load(Vfs { base: &store_path })?);
168 reqs.extend(requirements::load(Vfs { base: &store_path })?);
168 }
169 }
169
170
170 let repo_config = config.combine_with_repo(&repo_config_files)?;
171 let repo_config = config.combine_with_repo(&repo_config_files)?;
171
172
172 let repo = Self {
173 let repo = Self {
173 requirements: reqs,
174 requirements: reqs,
174 working_directory,
175 working_directory,
175 store: store_path,
176 store: store_path,
176 dot_hg,
177 dot_hg,
177 config: repo_config,
178 config: repo_config,
178 };
179 };
179
180
180 requirements::check(&repo)?;
181 requirements::check(&repo)?;
181
182
182 Ok(repo)
183 Ok(repo)
183 }
184 }
184
185
185 pub fn working_directory_path(&self) -> &Path {
186 pub fn working_directory_path(&self) -> &Path {
186 &self.working_directory
187 &self.working_directory
187 }
188 }
188
189
189 pub fn requirements(&self) -> &HashSet<String> {
190 pub fn requirements(&self) -> &HashSet<String> {
190 &self.requirements
191 &self.requirements
191 }
192 }
192
193
193 pub fn config(&self) -> &Config {
194 pub fn config(&self) -> &Config {
194 &self.config
195 &self.config
195 }
196 }
196
197
197 /// For accessing repository files (in `.hg`), except for the store
198 /// For accessing repository files (in `.hg`), except for the store
198 /// (`.hg/store`).
199 /// (`.hg/store`).
199 pub fn hg_vfs(&self) -> Vfs<'_> {
200 pub fn hg_vfs(&self) -> Vfs<'_> {
200 Vfs { base: &self.dot_hg }
201 Vfs { base: &self.dot_hg }
201 }
202 }
202
203
203 /// For accessing repository store files (in `.hg/store`)
204 /// For accessing repository store files (in `.hg/store`)
204 pub fn store_vfs(&self) -> Vfs<'_> {
205 pub fn store_vfs(&self) -> Vfs<'_> {
205 Vfs { base: &self.store }
206 Vfs { base: &self.store }
206 }
207 }
207
208
208 /// For accessing the working copy
209 /// For accessing the working copy
209
210
210 // The undescore prefix silences the "never used" warning. Remove before
211 // The undescore prefix silences the "never used" warning. Remove before
211 // using.
212 // using.
212 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
213 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
213 Vfs {
214 Vfs {
214 base: &self.working_directory,
215 base: &self.working_directory,
215 }
216 }
216 }
217 }
217
218
218 pub fn dirstate_parents(
219 pub fn dirstate_parents(
219 &self,
220 &self,
220 ) -> Result<crate::dirstate::DirstateParents, HgError> {
221 ) -> Result<crate::dirstate::DirstateParents, HgError> {
221 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
222 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
222 let parents =
223 let parents =
223 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
224 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
224 Ok(parents.clone())
225 Ok(parents.clone())
225 }
226 }
226 }
227 }
227
228
228 impl Vfs<'_> {
229 impl Vfs<'_> {
229 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
230 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
230 self.base.join(relative_path)
231 self.base.join(relative_path)
231 }
232 }
232
233
233 pub fn read(
234 pub fn read(
234 &self,
235 &self,
235 relative_path: impl AsRef<Path>,
236 relative_path: impl AsRef<Path>,
236 ) -> Result<Vec<u8>, HgError> {
237 ) -> Result<Vec<u8>, HgError> {
237 let path = self.join(relative_path);
238 let path = self.join(relative_path);
238 std::fs::read(&path).when_reading_file(&path)
239 std::fs::read(&path).when_reading_file(&path)
239 }
240 }
240
241
241 pub fn mmap_open(
242 pub fn mmap_open(
242 &self,
243 &self,
243 relative_path: impl AsRef<Path>,
244 relative_path: impl AsRef<Path>,
244 ) -> Result<Mmap, HgError> {
245 ) -> Result<Mmap, HgError> {
245 let path = self.base.join(relative_path);
246 let path = self.base.join(relative_path);
246 let file = std::fs::File::open(&path).when_reading_file(&path)?;
247 let file = std::fs::File::open(&path).when_reading_file(&path)?;
247 // TODO: what are the safety requirements here?
248 // TODO: what are the safety requirements here?
248 let mmap = unsafe { MmapOptions::new().map(&file) }
249 let mmap = unsafe { MmapOptions::new().map(&file) }
249 .when_reading_file(&path)?;
250 .when_reading_file(&path)?;
250 Ok(mmap)
251 Ok(mmap)
251 }
252 }
252
253
253 pub fn rename(
254 pub fn rename(
254 &self,
255 &self,
255 relative_from: impl AsRef<Path>,
256 relative_from: impl AsRef<Path>,
256 relative_to: impl AsRef<Path>,
257 relative_to: impl AsRef<Path>,
257 ) -> Result<(), HgError> {
258 ) -> Result<(), HgError> {
258 let from = self.join(relative_from);
259 let from = self.join(relative_from);
259 let to = self.join(relative_to);
260 let to = self.join(relative_to);
260 std::fs::rename(&from, &to)
261 std::fs::rename(&from, &to)
261 .with_context(|| IoErrorContext::RenamingFile { from, to })
262 .with_context(|| IoErrorContext::RenamingFile { from, to })
262 }
263 }
263 }
264 }
@@ -1,422 +1,430 b''
1 // utils module
1 // utils module
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Contains useful functions, traits, structs, etc. for use in core.
8 //! Contains useful functions, traits, structs, etc. for use in core.
9
9
10 use crate::errors::{HgError, IoErrorContext};
10 use crate::errors::{HgError, IoErrorContext};
11 use crate::utils::hg_path::HgPath;
11 use crate::utils::hg_path::HgPath;
12 use im_rc::ordmap::DiffItem;
12 use im_rc::ordmap::DiffItem;
13 use im_rc::ordmap::OrdMap;
13 use im_rc::ordmap::OrdMap;
14 use std::cell::Cell;
14 use std::cell::Cell;
15 use std::fmt;
15 use std::fmt;
16 use std::{io::Write, ops::Deref};
16 use std::{io::Write, ops::Deref};
17
17
18 pub mod files;
18 pub mod files;
19 pub mod hg_path;
19 pub mod hg_path;
20 pub mod path_auditor;
20 pub mod path_auditor;
21
21
22 /// Useful until rust/issues/56345 is stable
22 /// Useful until rust/issues/56345 is stable
23 ///
23 ///
24 /// # Examples
24 /// # Examples
25 ///
25 ///
26 /// ```
26 /// ```
27 /// use crate::hg::utils::find_slice_in_slice;
27 /// use crate::hg::utils::find_slice_in_slice;
28 ///
28 ///
29 /// let haystack = b"This is the haystack".to_vec();
29 /// let haystack = b"This is the haystack".to_vec();
30 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
30 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
31 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
31 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
32 /// ```
32 /// ```
33 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
33 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
34 where
34 where
35 for<'a> &'a [T]: PartialEq,
35 for<'a> &'a [T]: PartialEq,
36 {
36 {
37 slice
37 slice
38 .windows(needle.len())
38 .windows(needle.len())
39 .position(|window| window == needle)
39 .position(|window| window == needle)
40 }
40 }
41
41
42 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
42 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
43 ///
43 ///
44 /// # Examples
44 /// # Examples
45 ///
45 ///
46 /// ```
46 /// ```
47 /// use crate::hg::utils::replace_slice;
47 /// use crate::hg::utils::replace_slice;
48 /// let mut line = b"I hate writing tests!".to_vec();
48 /// let mut line = b"I hate writing tests!".to_vec();
49 /// replace_slice(&mut line, b"hate", b"love");
49 /// replace_slice(&mut line, b"hate", b"love");
50 /// assert_eq!(
50 /// assert_eq!(
51 /// line,
51 /// line,
52 /// b"I love writing tests!".to_vec()
52 /// b"I love writing tests!".to_vec()
53 /// );
53 /// );
54 /// ```
54 /// ```
55 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
55 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
56 where
56 where
57 T: Clone + PartialEq,
57 T: Clone + PartialEq,
58 {
58 {
59 if buf.len() < from.len() || from.len() != to.len() {
59 if buf.len() < from.len() || from.len() != to.len() {
60 return;
60 return;
61 }
61 }
62 for i in 0..=buf.len() - from.len() {
62 for i in 0..=buf.len() - from.len() {
63 if buf[i..].starts_with(from) {
63 if buf[i..].starts_with(from) {
64 buf[i..(i + from.len())].clone_from_slice(to);
64 buf[i..(i + from.len())].clone_from_slice(to);
65 }
65 }
66 }
66 }
67 }
67 }
68
68
69 pub trait SliceExt {
69 pub trait SliceExt {
70 fn trim_end_newlines(&self) -> &Self;
70 fn trim_end(&self) -> &Self;
71 fn trim_end(&self) -> &Self;
71 fn trim_start(&self) -> &Self;
72 fn trim_start(&self) -> &Self;
72 fn trim(&self) -> &Self;
73 fn trim(&self) -> &Self;
73 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
74 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
74 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
75 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
75 }
76 }
76
77
77 #[allow(clippy::trivially_copy_pass_by_ref)]
78 #[allow(clippy::trivially_copy_pass_by_ref)]
78 fn is_not_whitespace(c: &u8) -> bool {
79 fn is_not_whitespace(c: &u8) -> bool {
79 !(*c as char).is_whitespace()
80 !(*c as char).is_whitespace()
80 }
81 }
81
82
82 impl SliceExt for [u8] {
83 impl SliceExt for [u8] {
84 fn trim_end_newlines(&self) -> &[u8] {
85 if let Some(last) = self.iter().rposition(|&byte| byte != b'\n') {
86 &self[..=last]
87 } else {
88 &[]
89 }
90 }
83 fn trim_end(&self) -> &[u8] {
91 fn trim_end(&self) -> &[u8] {
84 if let Some(last) = self.iter().rposition(is_not_whitespace) {
92 if let Some(last) = self.iter().rposition(is_not_whitespace) {
85 &self[..=last]
93 &self[..=last]
86 } else {
94 } else {
87 &[]
95 &[]
88 }
96 }
89 }
97 }
90 fn trim_start(&self) -> &[u8] {
98 fn trim_start(&self) -> &[u8] {
91 if let Some(first) = self.iter().position(is_not_whitespace) {
99 if let Some(first) = self.iter().position(is_not_whitespace) {
92 &self[first..]
100 &self[first..]
93 } else {
101 } else {
94 &[]
102 &[]
95 }
103 }
96 }
104 }
97
105
98 /// ```
106 /// ```
99 /// use hg::utils::SliceExt;
107 /// use hg::utils::SliceExt;
100 /// assert_eq!(
108 /// assert_eq!(
101 /// b" to trim ".trim(),
109 /// b" to trim ".trim(),
102 /// b"to trim"
110 /// b"to trim"
103 /// );
111 /// );
104 /// assert_eq!(
112 /// assert_eq!(
105 /// b"to trim ".trim(),
113 /// b"to trim ".trim(),
106 /// b"to trim"
114 /// b"to trim"
107 /// );
115 /// );
108 /// assert_eq!(
116 /// assert_eq!(
109 /// b" to trim".trim(),
117 /// b" to trim".trim(),
110 /// b"to trim"
118 /// b"to trim"
111 /// );
119 /// );
112 /// ```
120 /// ```
113 fn trim(&self) -> &[u8] {
121 fn trim(&self) -> &[u8] {
114 self.trim_start().trim_end()
122 self.trim_start().trim_end()
115 }
123 }
116
124
117 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
125 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
118 if self.starts_with(needle) {
126 if self.starts_with(needle) {
119 Some(&self[needle.len()..])
127 Some(&self[needle.len()..])
120 } else {
128 } else {
121 None
129 None
122 }
130 }
123 }
131 }
124
132
125 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
133 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
126 let mut iter = self.splitn(2, |&byte| byte == separator);
134 let mut iter = self.splitn(2, |&byte| byte == separator);
127 let a = iter.next()?;
135 let a = iter.next()?;
128 let b = iter.next()?;
136 let b = iter.next()?;
129 Some((a, b))
137 Some((a, b))
130 }
138 }
131 }
139 }
132
140
133 pub trait Escaped {
141 pub trait Escaped {
134 /// Return bytes escaped for display to the user
142 /// Return bytes escaped for display to the user
135 fn escaped_bytes(&self) -> Vec<u8>;
143 fn escaped_bytes(&self) -> Vec<u8>;
136 }
144 }
137
145
138 impl Escaped for u8 {
146 impl Escaped for u8 {
139 fn escaped_bytes(&self) -> Vec<u8> {
147 fn escaped_bytes(&self) -> Vec<u8> {
140 let mut acc = vec![];
148 let mut acc = vec![];
141 match self {
149 match self {
142 c @ b'\'' | c @ b'\\' => {
150 c @ b'\'' | c @ b'\\' => {
143 acc.push(b'\\');
151 acc.push(b'\\');
144 acc.push(*c);
152 acc.push(*c);
145 }
153 }
146 b'\t' => {
154 b'\t' => {
147 acc.extend(br"\\t");
155 acc.extend(br"\\t");
148 }
156 }
149 b'\n' => {
157 b'\n' => {
150 acc.extend(br"\\n");
158 acc.extend(br"\\n");
151 }
159 }
152 b'\r' => {
160 b'\r' => {
153 acc.extend(br"\\r");
161 acc.extend(br"\\r");
154 }
162 }
155 c if (*c < b' ' || *c >= 127) => {
163 c if (*c < b' ' || *c >= 127) => {
156 write!(acc, "\\x{:x}", self).unwrap();
164 write!(acc, "\\x{:x}", self).unwrap();
157 }
165 }
158 c => {
166 c => {
159 acc.push(*c);
167 acc.push(*c);
160 }
168 }
161 }
169 }
162 acc
170 acc
163 }
171 }
164 }
172 }
165
173
166 impl<'a, T: Escaped> Escaped for &'a [T] {
174 impl<'a, T: Escaped> Escaped for &'a [T] {
167 fn escaped_bytes(&self) -> Vec<u8> {
175 fn escaped_bytes(&self) -> Vec<u8> {
168 self.iter().flat_map(Escaped::escaped_bytes).collect()
176 self.iter().flat_map(Escaped::escaped_bytes).collect()
169 }
177 }
170 }
178 }
171
179
172 impl<T: Escaped> Escaped for Vec<T> {
180 impl<T: Escaped> Escaped for Vec<T> {
173 fn escaped_bytes(&self) -> Vec<u8> {
181 fn escaped_bytes(&self) -> Vec<u8> {
174 self.deref().escaped_bytes()
182 self.deref().escaped_bytes()
175 }
183 }
176 }
184 }
177
185
178 impl<'a> Escaped for &'a HgPath {
186 impl<'a> Escaped for &'a HgPath {
179 fn escaped_bytes(&self) -> Vec<u8> {
187 fn escaped_bytes(&self) -> Vec<u8> {
180 self.as_bytes().escaped_bytes()
188 self.as_bytes().escaped_bytes()
181 }
189 }
182 }
190 }
183
191
184 // TODO: use the str method when we require Rust 1.45
192 // TODO: use the str method when we require Rust 1.45
185 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
193 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
186 if s.ends_with(suffix) {
194 if s.ends_with(suffix) {
187 Some(&s[..s.len() - suffix.len()])
195 Some(&s[..s.len() - suffix.len()])
188 } else {
196 } else {
189 None
197 None
190 }
198 }
191 }
199 }
192
200
193 #[cfg(unix)]
201 #[cfg(unix)]
194 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
202 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
195 // TODO: Use the `matches!` macro when we require Rust 1.42+
203 // TODO: Use the `matches!` macro when we require Rust 1.42+
196 if value.iter().all(|&byte| match byte {
204 if value.iter().all(|&byte| match byte {
197 b'a'..=b'z'
205 b'a'..=b'z'
198 | b'A'..=b'Z'
206 | b'A'..=b'Z'
199 | b'0'..=b'9'
207 | b'0'..=b'9'
200 | b'.'
208 | b'.'
201 | b'_'
209 | b'_'
202 | b'/'
210 | b'/'
203 | b'+'
211 | b'+'
204 | b'-' => true,
212 | b'-' => true,
205 _ => false,
213 _ => false,
206 }) {
214 }) {
207 value.to_owned()
215 value.to_owned()
208 } else {
216 } else {
209 let mut quoted = Vec::with_capacity(value.len() + 2);
217 let mut quoted = Vec::with_capacity(value.len() + 2);
210 quoted.push(b'\'');
218 quoted.push(b'\'');
211 for &byte in value {
219 for &byte in value {
212 if byte == b'\'' {
220 if byte == b'\'' {
213 quoted.push(b'\\');
221 quoted.push(b'\\');
214 }
222 }
215 quoted.push(byte);
223 quoted.push(byte);
216 }
224 }
217 quoted.push(b'\'');
225 quoted.push(b'\'');
218 quoted
226 quoted
219 }
227 }
220 }
228 }
221
229
222 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
230 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
223 std::env::current_dir().map_err(|error| HgError::IoError {
231 std::env::current_dir().map_err(|error| HgError::IoError {
224 error,
232 error,
225 context: IoErrorContext::CurrentDir,
233 context: IoErrorContext::CurrentDir,
226 })
234 })
227 }
235 }
228
236
229 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
237 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
230 std::env::current_exe().map_err(|error| HgError::IoError {
238 std::env::current_exe().map_err(|error| HgError::IoError {
231 error,
239 error,
232 context: IoErrorContext::CurrentExe,
240 context: IoErrorContext::CurrentExe,
233 })
241 })
234 }
242 }
235
243
236 pub(crate) enum MergeResult<V> {
244 pub(crate) enum MergeResult<V> {
237 UseLeftValue,
245 UseLeftValue,
238 UseRightValue,
246 UseRightValue,
239 UseNewValue(V),
247 UseNewValue(V),
240 }
248 }
241
249
242 /// Return the union of the two given maps,
250 /// Return the union of the two given maps,
243 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
251 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
244 /// both.
252 /// both.
245 ///
253 ///
246 /// CC https://github.com/bodil/im-rs/issues/166
254 /// CC https://github.com/bodil/im-rs/issues/166
247 pub(crate) fn ordmap_union_with_merge<K, V>(
255 pub(crate) fn ordmap_union_with_merge<K, V>(
248 left: OrdMap<K, V>,
256 left: OrdMap<K, V>,
249 right: OrdMap<K, V>,
257 right: OrdMap<K, V>,
250 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
258 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
251 ) -> OrdMap<K, V>
259 ) -> OrdMap<K, V>
252 where
260 where
253 K: Clone + Ord,
261 K: Clone + Ord,
254 V: Clone + PartialEq,
262 V: Clone + PartialEq,
255 {
263 {
256 if left.ptr_eq(&right) {
264 if left.ptr_eq(&right) {
257 // One of the two maps is an unmodified clone of the other
265 // One of the two maps is an unmodified clone of the other
258 left
266 left
259 } else if left.len() / 2 > right.len() {
267 } else if left.len() / 2 > right.len() {
260 // When two maps have different sizes,
268 // When two maps have different sizes,
261 // their size difference is a lower bound on
269 // their size difference is a lower bound on
262 // how many keys of the larger map are not also in the smaller map.
270 // how many keys of the larger map are not also in the smaller map.
263 // This in turn is a lower bound on the number of differences in
271 // This in turn is a lower bound on the number of differences in
264 // `OrdMap::diff` and the "amount of work" that would be done
272 // `OrdMap::diff` and the "amount of work" that would be done
265 // by `ordmap_union_with_merge_by_diff`.
273 // by `ordmap_union_with_merge_by_diff`.
266 //
274 //
267 // Here `left` is more than twice the size of `right`,
275 // Here `left` is more than twice the size of `right`,
268 // so the number of differences is more than the total size of
276 // so the number of differences is more than the total size of
269 // `right`. Therefore an algorithm based on iterating `right`
277 // `right`. Therefore an algorithm based on iterating `right`
270 // is more efficient.
278 // is more efficient.
271 //
279 //
272 // This helps a lot when a tiny (or empty) map is merged
280 // This helps a lot when a tiny (or empty) map is merged
273 // with a large one.
281 // with a large one.
274 ordmap_union_with_merge_by_iter(left, right, merge)
282 ordmap_union_with_merge_by_iter(left, right, merge)
275 } else if left.len() < right.len() / 2 {
283 } else if left.len() < right.len() / 2 {
276 // Same as above but with `left` and `right` swapped
284 // Same as above but with `left` and `right` swapped
277 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
285 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
278 // Also swapped in `merge` arguments:
286 // Also swapped in `merge` arguments:
279 match merge(key, b, a) {
287 match merge(key, b, a) {
280 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
288 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
281 // … and swap back in `merge` result:
289 // … and swap back in `merge` result:
282 MergeResult::UseLeftValue => MergeResult::UseRightValue,
290 MergeResult::UseLeftValue => MergeResult::UseRightValue,
283 MergeResult::UseRightValue => MergeResult::UseLeftValue,
291 MergeResult::UseRightValue => MergeResult::UseLeftValue,
284 }
292 }
285 })
293 })
286 } else {
294 } else {
287 // For maps of similar size, use the algorithm based on `OrdMap::diff`
295 // For maps of similar size, use the algorithm based on `OrdMap::diff`
288 ordmap_union_with_merge_by_diff(left, right, merge)
296 ordmap_union_with_merge_by_diff(left, right, merge)
289 }
297 }
290 }
298 }
291
299
292 /// Efficient if `right` is much smaller than `left`
300 /// Efficient if `right` is much smaller than `left`
293 fn ordmap_union_with_merge_by_iter<K, V>(
301 fn ordmap_union_with_merge_by_iter<K, V>(
294 mut left: OrdMap<K, V>,
302 mut left: OrdMap<K, V>,
295 right: OrdMap<K, V>,
303 right: OrdMap<K, V>,
296 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
304 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
297 ) -> OrdMap<K, V>
305 ) -> OrdMap<K, V>
298 where
306 where
299 K: Clone + Ord,
307 K: Clone + Ord,
300 V: Clone,
308 V: Clone,
301 {
309 {
302 for (key, right_value) in right {
310 for (key, right_value) in right {
303 match left.get(&key) {
311 match left.get(&key) {
304 None => {
312 None => {
305 left.insert(key, right_value);
313 left.insert(key, right_value);
306 }
314 }
307 Some(left_value) => match merge(&key, left_value, &right_value) {
315 Some(left_value) => match merge(&key, left_value, &right_value) {
308 MergeResult::UseLeftValue => {}
316 MergeResult::UseLeftValue => {}
309 MergeResult::UseRightValue => {
317 MergeResult::UseRightValue => {
310 left.insert(key, right_value);
318 left.insert(key, right_value);
311 }
319 }
312 MergeResult::UseNewValue(new_value) => {
320 MergeResult::UseNewValue(new_value) => {
313 left.insert(key, new_value);
321 left.insert(key, new_value);
314 }
322 }
315 },
323 },
316 }
324 }
317 }
325 }
318 left
326 left
319 }
327 }
320
328
321 /// Fallback when both maps are of similar size
329 /// Fallback when both maps are of similar size
322 fn ordmap_union_with_merge_by_diff<K, V>(
330 fn ordmap_union_with_merge_by_diff<K, V>(
323 mut left: OrdMap<K, V>,
331 mut left: OrdMap<K, V>,
324 mut right: OrdMap<K, V>,
332 mut right: OrdMap<K, V>,
325 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
333 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
326 ) -> OrdMap<K, V>
334 ) -> OrdMap<K, V>
327 where
335 where
328 K: Clone + Ord,
336 K: Clone + Ord,
329 V: Clone + PartialEq,
337 V: Clone + PartialEq,
330 {
338 {
331 // (key, value) pairs that would need to be inserted in either map
339 // (key, value) pairs that would need to be inserted in either map
332 // in order to turn it into the union.
340 // in order to turn it into the union.
333 //
341 //
334 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
342 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
335 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
343 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
336 // with `left_updates` only borrowing from `right` and `right_updates` from
344 // with `left_updates` only borrowing from `right` and `right_updates` from
337 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
345 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
338 //
346 //
339 // This would allow moving all `.clone()` calls to after we’ve decided
347 // This would allow moving all `.clone()` calls to after we’ve decided
340 // which of `right_updates` or `left_updates` to use
348 // which of `right_updates` or `left_updates` to use
341 // (value ones becoming `Cow::into_owned`),
349 // (value ones becoming `Cow::into_owned`),
342 // and avoid making clones we don’t end up using.
350 // and avoid making clones we don’t end up using.
343 let mut left_updates = Vec::new();
351 let mut left_updates = Vec::new();
344 let mut right_updates = Vec::new();
352 let mut right_updates = Vec::new();
345
353
346 for difference in left.diff(&right) {
354 for difference in left.diff(&right) {
347 match difference {
355 match difference {
348 DiffItem::Add(key, value) => {
356 DiffItem::Add(key, value) => {
349 left_updates.push((key.clone(), value.clone()))
357 left_updates.push((key.clone(), value.clone()))
350 }
358 }
351 DiffItem::Remove(key, value) => {
359 DiffItem::Remove(key, value) => {
352 right_updates.push((key.clone(), value.clone()))
360 right_updates.push((key.clone(), value.clone()))
353 }
361 }
354 DiffItem::Update {
362 DiffItem::Update {
355 old: (key, left_value),
363 old: (key, left_value),
356 new: (_, right_value),
364 new: (_, right_value),
357 } => match merge(key, left_value, right_value) {
365 } => match merge(key, left_value, right_value) {
358 MergeResult::UseLeftValue => {
366 MergeResult::UseLeftValue => {
359 right_updates.push((key.clone(), left_value.clone()))
367 right_updates.push((key.clone(), left_value.clone()))
360 }
368 }
361 MergeResult::UseRightValue => {
369 MergeResult::UseRightValue => {
362 left_updates.push((key.clone(), right_value.clone()))
370 left_updates.push((key.clone(), right_value.clone()))
363 }
371 }
364 MergeResult::UseNewValue(new_value) => {
372 MergeResult::UseNewValue(new_value) => {
365 left_updates.push((key.clone(), new_value.clone()));
373 left_updates.push((key.clone(), new_value.clone()));
366 right_updates.push((key.clone(), new_value))
374 right_updates.push((key.clone(), new_value))
367 }
375 }
368 },
376 },
369 }
377 }
370 }
378 }
371 if left_updates.len() < right_updates.len() {
379 if left_updates.len() < right_updates.len() {
372 for (key, value) in left_updates {
380 for (key, value) in left_updates {
373 left.insert(key, value);
381 left.insert(key, value);
374 }
382 }
375 left
383 left
376 } else {
384 } else {
377 for (key, value) in right_updates {
385 for (key, value) in right_updates {
378 right.insert(key, value);
386 right.insert(key, value);
379 }
387 }
380 right
388 right
381 }
389 }
382 }
390 }
383
391
384 /// Join items of the iterable with the given separator, similar to Python’s
392 /// Join items of the iterable with the given separator, similar to Python’s
385 /// `separator.join(iter)`.
393 /// `separator.join(iter)`.
386 ///
394 ///
387 /// Formatting the return value consumes the iterator.
395 /// Formatting the return value consumes the iterator.
388 /// Formatting it again will produce an empty string.
396 /// Formatting it again will produce an empty string.
389 pub fn join_display(
397 pub fn join_display(
390 iter: impl IntoIterator<Item = impl fmt::Display>,
398 iter: impl IntoIterator<Item = impl fmt::Display>,
391 separator: impl fmt::Display,
399 separator: impl fmt::Display,
392 ) -> impl fmt::Display {
400 ) -> impl fmt::Display {
393 JoinDisplay {
401 JoinDisplay {
394 iter: Cell::new(Some(iter.into_iter())),
402 iter: Cell::new(Some(iter.into_iter())),
395 separator,
403 separator,
396 }
404 }
397 }
405 }
398
406
399 struct JoinDisplay<I, S> {
407 struct JoinDisplay<I, S> {
400 iter: Cell<Option<I>>,
408 iter: Cell<Option<I>>,
401 separator: S,
409 separator: S,
402 }
410 }
403
411
404 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
412 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
405 where
413 where
406 I: Iterator<Item = T>,
414 I: Iterator<Item = T>,
407 T: fmt::Display,
415 T: fmt::Display,
408 S: fmt::Display,
416 S: fmt::Display,
409 {
417 {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 if let Some(mut iter) = self.iter.take() {
419 if let Some(mut iter) = self.iter.take() {
412 if let Some(first) = iter.next() {
420 if let Some(first) = iter.next() {
413 first.fmt(f)?;
421 first.fmt(f)?;
414 }
422 }
415 for value in iter {
423 for value in iter {
416 self.separator.fmt(f)?;
424 self.separator.fmt(f)?;
417 value.fmt(f)?;
425 value.fmt(f)?;
418 }
426 }
419 }
427 }
420 Ok(())
428 Ok(())
421 }
429 }
422 }
430 }
General Comments 0
You need to be logged in to leave comments. Login now