##// END OF EJS Templates
rhg: Initial repository locking...
Simon Sapin -
r49245:5734b03e default
parent child Browse files
Show More
@@ -0,0 +1,187 b''
1 //! Filesystem-based locks for local repositories
2
3 use crate::errors::HgError;
4 use crate::errors::HgResultExt;
5 use crate::utils::StrExt;
6 use crate::vfs::Vfs;
7 use std::io;
8 use std::io::ErrorKind;
9
10 #[derive(derive_more::From)]
11 pub enum LockError {
12 AlreadyHeld,
13 #[from]
14 Other(HgError),
15 }
16
17 /// Try to call `f` with the lock acquired, without waiting.
18 ///
19 /// If the lock is aready held, `f` is not called and `LockError::AlreadyHeld`
20 /// is returned. `LockError::Io` is returned for any unexpected I/O error
21 /// accessing the lock file, including for removing it after `f` was called.
22 /// The return value of `f` is dropped in that case. If all is successful, the
23 /// return value of `f` is forwarded.
24 pub fn try_with_lock_no_wait<R>(
25 hg_vfs: Vfs,
26 lock_filename: &str,
27 f: impl FnOnce() -> R,
28 ) -> Result<R, LockError> {
29 let our_lock_data = &*OUR_LOCK_DATA;
30 for _retry in 0..5 {
31 match make_lock(hg_vfs, lock_filename, our_lock_data) {
32 Ok(()) => {
33 let result = f();
34 unlock(hg_vfs, lock_filename)?;
35 return Ok(result);
36 }
37 Err(HgError::IoError { error, .. })
38 if error.kind() == ErrorKind::AlreadyExists =>
39 {
40 let lock_data = read_lock(hg_vfs, lock_filename)?;
41 if lock_data.is_none() {
42 // Lock was apparently just released, retry acquiring it
43 continue;
44 }
45 if !lock_should_be_broken(&lock_data) {
46 return Err(LockError::AlreadyHeld);
47 }
48 // The lock file is left over from a process not running
49 // anymore. Break it, but with another lock to
50 // avoid a race.
51 break_lock(hg_vfs, lock_filename)?;
52
53 // Retry acquiring
54 }
55 Err(error) => Err(error)?,
56 }
57 }
58 Err(LockError::AlreadyHeld)
59 }
60
61 fn break_lock(hg_vfs: Vfs, lock_filename: &str) -> Result<(), LockError> {
62 try_with_lock_no_wait(hg_vfs, &format!("{}.break", lock_filename), || {
63 // Check again in case some other process broke and
64 // acquired the lock in the meantime
65 let lock_data = read_lock(hg_vfs, lock_filename)?;
66 if !lock_should_be_broken(&lock_data) {
67 return Err(LockError::AlreadyHeld);
68 }
69 Ok(hg_vfs.remove_file(lock_filename)?)
70 })?
71 }
72
73 #[cfg(unix)]
74 fn make_lock(
75 hg_vfs: Vfs,
76 lock_filename: &str,
77 data: &str,
78 ) -> Result<(), HgError> {
79 // Use a symbolic link because creating it is atomic.
80 // The link’s "target" contains data not representing any path.
81 let fake_symlink_target = data;
82 hg_vfs.create_symlink(lock_filename, fake_symlink_target)
83 }
84
85 fn read_lock(
86 hg_vfs: Vfs,
87 lock_filename: &str,
88 ) -> Result<Option<String>, HgError> {
89 let link_target =
90 hg_vfs.read_link(lock_filename).io_not_found_as_none()?;
91 if let Some(target) = link_target {
92 let data = target
93 .into_os_string()
94 .into_string()
95 .map_err(|_| HgError::corrupted("non-UTF-8 lock data"))?;
96 Ok(Some(data))
97 } else {
98 Ok(None)
99 }
100 }
101
102 fn unlock(hg_vfs: Vfs, lock_filename: &str) -> Result<(), HgError> {
103 hg_vfs.remove_file(lock_filename)
104 }
105
106 /// Return whether the process that is/was holding the lock is known not to be
107 /// running anymore.
108 fn lock_should_be_broken(data: &Option<String>) -> bool {
109 (|| -> Option<bool> {
110 let (prefix, pid) = data.as_ref()?.split_2(':')?;
111 if prefix != &*LOCK_PREFIX {
112 return Some(false);
113 }
114 let process_is_running;
115
116 #[cfg(unix)]
117 {
118 let pid: libc::pid_t = pid.parse().ok()?;
119 unsafe {
120 let signal = 0; // Test if we could send a signal, without sending
121 let result = libc::kill(pid, signal);
122 if result == 0 {
123 process_is_running = true
124 } else {
125 let errno =
126 io::Error::last_os_error().raw_os_error().unwrap();
127 process_is_running = errno != libc::ESRCH
128 }
129 }
130 }
131
132 Some(!process_is_running)
133 })()
134 .unwrap_or(false)
135 }
136
137 lazy_static::lazy_static! {
138 /// A string which is used to differentiate pid namespaces
139 ///
140 /// It's useful to detect "dead" processes and remove stale locks with
141 /// confidence. Typically it's just hostname. On modern linux, we include an
142 /// extra Linux-specific pid namespace identifier.
143 static ref LOCK_PREFIX: String = {
144 // Note: this must match the behavior of `_getlockprefix` in `mercurial/lock.py`
145
146 /// Same as https://github.com/python/cpython/blob/v3.10.0/Modules/socketmodule.c#L5414
147 const BUFFER_SIZE: usize = 1024;
148 let mut buffer = [0_i8; BUFFER_SIZE];
149 let hostname_bytes = unsafe {
150 let result = libc::gethostname(buffer.as_mut_ptr(), BUFFER_SIZE);
151 if result != 0 {
152 panic!("gethostname: {}", io::Error::last_os_error())
153 }
154 std::ffi::CStr::from_ptr(buffer.as_mut_ptr()).to_bytes()
155 };
156 let hostname =
157 std::str::from_utf8(hostname_bytes).expect("non-UTF-8 hostname");
158
159 #[cfg(target_os = "linux")]
160 {
161 use std::os::linux::fs::MetadataExt;
162 match std::fs::metadata("/proc/self/ns/pid") {
163 Ok(meta) => {
164 return format!("{}/{:x}", hostname, meta.st_ino())
165 }
166 Err(error) => {
167 // TODO: match on `error.kind()` when `NotADirectory`
168 // is available on all supported Rust versions:
169 // https://github.com/rust-lang/rust/issues/86442
170 use libc::{
171 ENOENT, // ErrorKind::NotFound
172 ENOTDIR, // ErrorKind::NotADirectory
173 EACCES, // ErrorKind::PermissionDenied
174 };
175 match error.raw_os_error() {
176 Some(ENOENT) | Some(ENOTDIR) | Some(EACCES) => {}
177 _ => panic!("stat /proc/self/ns/pid: {}", error),
178 }
179 }
180 }
181 }
182
183 hostname.to_owned()
184 };
185
186 static ref OUR_LOCK_DATA: String = format!("{}:{}", &*LOCK_PREFIX, std::process::id());
187 }
@@ -1,131 +1,132 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
5 // GNU General Public License version 2 or any later version.
5 // GNU General Public License version 2 or any later version.
6
6
7 mod ancestors;
7 mod ancestors;
8 pub mod dagops;
8 pub mod dagops;
9 pub mod errors;
9 pub mod errors;
10 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
10 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
11 pub mod dirstate;
11 pub mod dirstate;
12 pub mod dirstate_tree;
12 pub mod dirstate_tree;
13 pub mod discovery;
13 pub mod discovery;
14 pub mod exit_codes;
14 pub mod exit_codes;
15 pub mod requirements;
15 pub mod requirements;
16 pub mod testing; // unconditionally built, for use from integration tests
16 pub mod testing; // unconditionally built, for use from integration tests
17 pub use dirstate::{
17 pub use dirstate::{
18 dirs_multiset::{DirsMultiset, DirsMultisetIter},
18 dirs_multiset::{DirsMultiset, DirsMultisetIter},
19 status::{
19 status::{
20 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
20 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
21 StatusOptions,
21 StatusOptions,
22 },
22 },
23 DirstateEntry, DirstateParents, EntryState,
23 DirstateEntry, DirstateParents, EntryState,
24 };
24 };
25 pub mod copy_tracing;
25 pub mod copy_tracing;
26 mod filepatterns;
26 mod filepatterns;
27 pub mod matchers;
27 pub mod matchers;
28 pub mod repo;
28 pub mod repo;
29 pub mod revlog;
29 pub mod revlog;
30 pub use revlog::*;
30 pub use revlog::*;
31 pub mod config;
31 pub mod config;
32 pub mod lock;
32 pub mod logging;
33 pub mod logging;
33 pub mod operations;
34 pub mod operations;
34 pub mod revset;
35 pub mod revset;
35 pub mod utils;
36 pub mod utils;
36 pub mod vfs;
37 pub mod vfs;
37
38
38 use crate::utils::hg_path::{HgPathBuf, HgPathError};
39 use crate::utils::hg_path::{HgPathBuf, HgPathError};
39 pub use filepatterns::{
40 pub use filepatterns::{
40 parse_pattern_syntax, read_pattern_file, IgnorePattern,
41 parse_pattern_syntax, read_pattern_file, IgnorePattern,
41 PatternFileWarning, PatternSyntax,
42 PatternFileWarning, PatternSyntax,
42 };
43 };
43 use std::collections::HashMap;
44 use std::collections::HashMap;
44 use std::fmt;
45 use std::fmt;
45 use twox_hash::RandomXxHashBuilder64;
46 use twox_hash::RandomXxHashBuilder64;
46
47
47 /// This is a contract between the `micro-timer` crate and us, to expose
48 /// This is a contract between the `micro-timer` crate and us, to expose
48 /// the `log` crate as `crate::log`.
49 /// the `log` crate as `crate::log`.
49 use log;
50 use log;
50
51
51 pub type LineNumber = usize;
52 pub type LineNumber = usize;
52
53
53 /// Rust's default hasher is too slow because it tries to prevent collision
54 /// Rust's default hasher is too slow because it tries to prevent collision
54 /// attacks. We are not concerned about those: if an ill-minded person has
55 /// attacks. We are not concerned about those: if an ill-minded person has
55 /// write access to your repository, you have other issues.
56 /// write access to your repository, you have other issues.
56 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
57 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
57
58
58 #[derive(Debug, PartialEq)]
59 #[derive(Debug, PartialEq)]
59 pub enum DirstateMapError {
60 pub enum DirstateMapError {
60 PathNotFound(HgPathBuf),
61 PathNotFound(HgPathBuf),
61 EmptyPath,
62 EmptyPath,
62 InvalidPath(HgPathError),
63 InvalidPath(HgPathError),
63 }
64 }
64
65
65 impl fmt::Display for DirstateMapError {
66 impl fmt::Display for DirstateMapError {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 match self {
68 match self {
68 DirstateMapError::PathNotFound(_) => {
69 DirstateMapError::PathNotFound(_) => {
69 f.write_str("expected a value, found none")
70 f.write_str("expected a value, found none")
70 }
71 }
71 DirstateMapError::EmptyPath => {
72 DirstateMapError::EmptyPath => {
72 f.write_str("Overflow in dirstate.")
73 f.write_str("Overflow in dirstate.")
73 }
74 }
74 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
75 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
75 }
76 }
76 }
77 }
77 }
78 }
78
79
79 #[derive(Debug, derive_more::From)]
80 #[derive(Debug, derive_more::From)]
80 pub enum DirstateError {
81 pub enum DirstateError {
81 Map(DirstateMapError),
82 Map(DirstateMapError),
82 Common(errors::HgError),
83 Common(errors::HgError),
83 }
84 }
84
85
85 impl fmt::Display for DirstateError {
86 impl fmt::Display for DirstateError {
86 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87 match self {
88 match self {
88 DirstateError::Map(error) => error.fmt(f),
89 DirstateError::Map(error) => error.fmt(f),
89 DirstateError::Common(error) => error.fmt(f),
90 DirstateError::Common(error) => error.fmt(f),
90 }
91 }
91 }
92 }
92 }
93 }
93
94
94 #[derive(Debug, derive_more::From)]
95 #[derive(Debug, derive_more::From)]
95 pub enum PatternError {
96 pub enum PatternError {
96 #[from]
97 #[from]
97 Path(HgPathError),
98 Path(HgPathError),
98 UnsupportedSyntax(String),
99 UnsupportedSyntax(String),
99 UnsupportedSyntaxInFile(String, String, usize),
100 UnsupportedSyntaxInFile(String, String, usize),
100 TooLong(usize),
101 TooLong(usize),
101 #[from]
102 #[from]
102 IO(std::io::Error),
103 IO(std::io::Error),
103 /// Needed a pattern that can be turned into a regex but got one that
104 /// Needed a pattern that can be turned into a regex but got one that
104 /// can't. This should only happen through programmer error.
105 /// can't. This should only happen through programmer error.
105 NonRegexPattern(IgnorePattern),
106 NonRegexPattern(IgnorePattern),
106 }
107 }
107
108
108 impl fmt::Display for PatternError {
109 impl fmt::Display for PatternError {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 match self {
111 match self {
111 PatternError::UnsupportedSyntax(syntax) => {
112 PatternError::UnsupportedSyntax(syntax) => {
112 write!(f, "Unsupported syntax {}", syntax)
113 write!(f, "Unsupported syntax {}", syntax)
113 }
114 }
114 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
115 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
115 write!(
116 write!(
116 f,
117 f,
117 "{}:{}: unsupported syntax {}",
118 "{}:{}: unsupported syntax {}",
118 file_path, line, syntax
119 file_path, line, syntax
119 )
120 )
120 }
121 }
121 PatternError::TooLong(size) => {
122 PatternError::TooLong(size) => {
122 write!(f, "matcher pattern is too long ({} bytes)", size)
123 write!(f, "matcher pattern is too long ({} bytes)", size)
123 }
124 }
124 PatternError::IO(error) => error.fmt(f),
125 PatternError::IO(error) => error.fmt(f),
125 PatternError::Path(error) => error.fmt(f),
126 PatternError::Path(error) => error.fmt(f),
126 PatternError::NonRegexPattern(pattern) => {
127 PatternError::NonRegexPattern(pattern) => {
127 write!(f, "'{:?}' cannot be turned into a regex", pattern)
128 write!(f, "'{:?}' cannot be turned into a regex", pattern)
128 }
129 }
129 }
130 }
130 }
131 }
131 }
132 }
@@ -1,417 +1,425 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgError;
6 use crate::errors::HgError;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::exit_codes;
8 use crate::exit_codes;
9 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
15 use crate::utils::SliceExt;
15 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::{requirements, NodePrefix};
17 use crate::{requirements, NodePrefix};
17 use crate::{DirstateError, Revision};
18 use crate::{DirstateError, Revision};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
19 use std::cell::{Cell, Ref, RefCell, RefMut};
19 use std::collections::HashSet;
20 use std::collections::HashSet;
20 use std::path::{Path, PathBuf};
21 use std::path::{Path, PathBuf};
21
22
22 /// A repository on disk
23 /// A repository on disk
23 pub struct Repo {
24 pub struct Repo {
24 working_directory: PathBuf,
25 working_directory: PathBuf,
25 dot_hg: PathBuf,
26 dot_hg: PathBuf,
26 store: PathBuf,
27 store: PathBuf,
27 requirements: HashSet<String>,
28 requirements: HashSet<String>,
28 config: Config,
29 config: Config,
29 // None means not known/initialized yet
30 // None means not known/initialized yet
30 dirstate_parents: Cell<Option<DirstateParents>>,
31 dirstate_parents: Cell<Option<DirstateParents>>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 changelog: LazyCell<Changelog, HgError>,
33 changelog: LazyCell<Changelog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
34 manifestlog: LazyCell<Manifestlog, HgError>,
34 }
35 }
35
36
36 #[derive(Debug, derive_more::From)]
37 #[derive(Debug, derive_more::From)]
37 pub enum RepoError {
38 pub enum RepoError {
38 NotFound {
39 NotFound {
39 at: PathBuf,
40 at: PathBuf,
40 },
41 },
41 #[from]
42 #[from]
42 ConfigParseError(ConfigParseError),
43 ConfigParseError(ConfigParseError),
43 #[from]
44 #[from]
44 Other(HgError),
45 Other(HgError),
45 }
46 }
46
47
47 impl From<ConfigError> for RepoError {
48 impl From<ConfigError> for RepoError {
48 fn from(error: ConfigError) -> Self {
49 fn from(error: ConfigError) -> Self {
49 match error {
50 match error {
50 ConfigError::Parse(error) => error.into(),
51 ConfigError::Parse(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
52 ConfigError::Other(error) => error.into(),
52 }
53 }
53 }
54 }
54 }
55 }
55
56
56 impl Repo {
57 impl Repo {
57 /// tries to find nearest repository root in current working directory or
58 /// tries to find nearest repository root in current working directory or
58 /// its ancestors
59 /// its ancestors
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 let current_directory = crate::utils::current_dir()?;
61 let current_directory = crate::utils::current_dir()?;
61 // ancestors() is inclusive: it first yields `current_directory`
62 // ancestors() is inclusive: it first yields `current_directory`
62 // as-is.
63 // as-is.
63 for ancestor in current_directory.ancestors() {
64 for ancestor in current_directory.ancestors() {
64 if is_dir(ancestor.join(".hg"))? {
65 if is_dir(ancestor.join(".hg"))? {
65 return Ok(ancestor.to_path_buf());
66 return Ok(ancestor.to_path_buf());
66 }
67 }
67 }
68 }
68 return Err(RepoError::NotFound {
69 return Err(RepoError::NotFound {
69 at: current_directory,
70 at: current_directory,
70 });
71 });
71 }
72 }
72
73
73 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// sub-directory) or by searching the current directory and its
75 /// sub-directory) or by searching the current directory and its
75 /// ancestors.
76 /// ancestors.
76 ///
77 ///
77 /// A method with two very different "modes" like this usually a code smell
78 /// A method with two very different "modes" like this usually a code smell
78 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// Having two methods would just move that `if` to almost all callers.
81 /// Having two methods would just move that `if` to almost all callers.
81 pub fn find(
82 pub fn find(
82 config: &Config,
83 config: &Config,
83 explicit_path: Option<PathBuf>,
84 explicit_path: Option<PathBuf>,
84 ) -> Result<Self, RepoError> {
85 ) -> Result<Self, RepoError> {
85 if let Some(root) = explicit_path {
86 if let Some(root) = explicit_path {
86 if is_dir(root.join(".hg"))? {
87 if is_dir(root.join(".hg"))? {
87 Self::new_at_path(root.to_owned(), config)
88 Self::new_at_path(root.to_owned(), config)
88 } else if is_file(&root)? {
89 } else if is_file(&root)? {
89 Err(HgError::unsupported("bundle repository").into())
90 Err(HgError::unsupported("bundle repository").into())
90 } else {
91 } else {
91 Err(RepoError::NotFound {
92 Err(RepoError::NotFound {
92 at: root.to_owned(),
93 at: root.to_owned(),
93 })
94 })
94 }
95 }
95 } else {
96 } else {
96 let root = Self::find_repo_root()?;
97 let root = Self::find_repo_root()?;
97 Self::new_at_path(root, config)
98 Self::new_at_path(root, config)
98 }
99 }
99 }
100 }
100
101
101 /// To be called after checking that `.hg` is a sub-directory
102 /// To be called after checking that `.hg` is a sub-directory
102 fn new_at_path(
103 fn new_at_path(
103 working_directory: PathBuf,
104 working_directory: PathBuf,
104 config: &Config,
105 config: &Config,
105 ) -> Result<Self, RepoError> {
106 ) -> Result<Self, RepoError> {
106 let dot_hg = working_directory.join(".hg");
107 let dot_hg = working_directory.join(".hg");
107
108
108 let mut repo_config_files = Vec::new();
109 let mut repo_config_files = Vec::new();
109 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111
112
112 let hg_vfs = Vfs { base: &dot_hg };
113 let hg_vfs = Vfs { base: &dot_hg };
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let relative =
115 let relative =
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 let shared =
117 let shared =
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118
119
119 // From `mercurial/localrepo.py`:
120 // From `mercurial/localrepo.py`:
120 //
121 //
121 // if .hg/requires contains the sharesafe requirement, it means
122 // if .hg/requires contains the sharesafe requirement, it means
122 // there exists a `.hg/store/requires` too and we should read it
123 // there exists a `.hg/store/requires` too and we should read it
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is not present, refer checkrequirementscompat() for that
126 // is not present, refer checkrequirementscompat() for that
126 //
127 //
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // repository was shared the old way. We check the share source
129 // repository was shared the old way. We check the share source
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // current repository needs to be reshared
131 // current repository needs to be reshared
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132
133
133 let store_path;
134 let store_path;
134 if !shared {
135 if !shared {
135 store_path = dot_hg.join("store");
136 store_path = dot_hg.join("store");
136 } else {
137 } else {
137 let bytes = hg_vfs.read("sharedpath")?;
138 let bytes = hg_vfs.read("sharedpath")?;
138 let mut shared_path =
139 let mut shared_path =
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 .to_owned();
141 .to_owned();
141 if relative {
142 if relative {
142 shared_path = dot_hg.join(shared_path)
143 shared_path = dot_hg.join(shared_path)
143 }
144 }
144 if !is_dir(&shared_path)? {
145 if !is_dir(&shared_path)? {
145 return Err(HgError::corrupted(format!(
146 return Err(HgError::corrupted(format!(
146 ".hg/sharedpath points to nonexistent directory {}",
147 ".hg/sharedpath points to nonexistent directory {}",
147 shared_path.display()
148 shared_path.display()
148 ))
149 ))
149 .into());
150 .into());
150 }
151 }
151
152
152 store_path = shared_path.join("store");
153 store_path = shared_path.join("store");
153
154
154 let source_is_share_safe =
155 let source_is_share_safe =
155 requirements::load(Vfs { base: &shared_path })?
156 requirements::load(Vfs { base: &shared_path })?
156 .contains(requirements::SHARESAFE_REQUIREMENT);
157 .contains(requirements::SHARESAFE_REQUIREMENT);
157
158
158 if share_safe && !source_is_share_safe {
159 if share_safe && !source_is_share_safe {
159 return Err(match config
160 return Err(match config
160 .get(b"share", b"safe-mismatch.source-not-safe")
161 .get(b"share", b"safe-mismatch.source-not-safe")
161 {
162 {
162 Some(b"abort") | None => HgError::abort(
163 Some(b"abort") | None => HgError::abort(
163 "abort: share source does not support share-safe requirement\n\
164 "abort: share source does not support share-safe requirement\n\
164 (see `hg help config.format.use-share-safe` for more information)",
165 (see `hg help config.format.use-share-safe` for more information)",
165 exit_codes::ABORT,
166 exit_codes::ABORT,
166 ),
167 ),
167 _ => HgError::unsupported("share-safe downgrade"),
168 _ => HgError::unsupported("share-safe downgrade"),
168 }
169 }
169 .into());
170 .into());
170 } else if source_is_share_safe && !share_safe {
171 } else if source_is_share_safe && !share_safe {
171 return Err(
172 return Err(
172 match config.get(b"share", b"safe-mismatch.source-safe") {
173 match config.get(b"share", b"safe-mismatch.source-safe") {
173 Some(b"abort") | None => HgError::abort(
174 Some(b"abort") | None => HgError::abort(
174 "abort: version mismatch: source uses share-safe \
175 "abort: version mismatch: source uses share-safe \
175 functionality while the current share does not\n\
176 functionality while the current share does not\n\
176 (see `hg help config.format.use-share-safe` for more information)",
177 (see `hg help config.format.use-share-safe` for more information)",
177 exit_codes::ABORT,
178 exit_codes::ABORT,
178 ),
179 ),
179 _ => HgError::unsupported("share-safe upgrade"),
180 _ => HgError::unsupported("share-safe upgrade"),
180 }
181 }
181 .into(),
182 .into(),
182 );
183 );
183 }
184 }
184
185
185 if share_safe {
186 if share_safe {
186 repo_config_files.insert(0, shared_path.join("hgrc"))
187 repo_config_files.insert(0, shared_path.join("hgrc"))
187 }
188 }
188 }
189 }
189 if share_safe {
190 if share_safe {
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 }
192 }
192
193
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 config.combine_with_repo(&repo_config_files)?
195 config.combine_with_repo(&repo_config_files)?
195 } else {
196 } else {
196 config.clone()
197 config.clone()
197 };
198 };
198
199
199 let repo = Self {
200 let repo = Self {
200 requirements: reqs,
201 requirements: reqs,
201 working_directory,
202 working_directory,
202 store: store_path,
203 store: store_path,
203 dot_hg,
204 dot_hg,
204 config: repo_config,
205 config: repo_config,
205 dirstate_parents: Cell::new(None),
206 dirstate_parents: Cell::new(None),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 changelog: LazyCell::new(Changelog::open),
208 changelog: LazyCell::new(Changelog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
209 manifestlog: LazyCell::new(Manifestlog::open),
209 };
210 };
210
211
211 requirements::check(&repo)?;
212 requirements::check(&repo)?;
212
213
213 Ok(repo)
214 Ok(repo)
214 }
215 }
215
216
216 pub fn working_directory_path(&self) -> &Path {
217 pub fn working_directory_path(&self) -> &Path {
217 &self.working_directory
218 &self.working_directory
218 }
219 }
219
220
220 pub fn requirements(&self) -> &HashSet<String> {
221 pub fn requirements(&self) -> &HashSet<String> {
221 &self.requirements
222 &self.requirements
222 }
223 }
223
224
224 pub fn config(&self) -> &Config {
225 pub fn config(&self) -> &Config {
225 &self.config
226 &self.config
226 }
227 }
227
228
228 /// For accessing repository files (in `.hg`), except for the store
229 /// For accessing repository files (in `.hg`), except for the store
229 /// (`.hg/store`).
230 /// (`.hg/store`).
230 pub fn hg_vfs(&self) -> Vfs<'_> {
231 pub fn hg_vfs(&self) -> Vfs<'_> {
231 Vfs { base: &self.dot_hg }
232 Vfs { base: &self.dot_hg }
232 }
233 }
233
234
234 /// For accessing repository store files (in `.hg/store`)
235 /// For accessing repository store files (in `.hg/store`)
235 pub fn store_vfs(&self) -> Vfs<'_> {
236 pub fn store_vfs(&self) -> Vfs<'_> {
236 Vfs { base: &self.store }
237 Vfs { base: &self.store }
237 }
238 }
238
239
239 /// For accessing the working copy
240 /// For accessing the working copy
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 Vfs {
242 Vfs {
242 base: &self.working_directory,
243 base: &self.working_directory,
243 }
244 }
244 }
245 }
245
246
247 pub fn try_with_wlock_no_wait<R>(
248 &self,
249 f: impl FnOnce() -> R,
250 ) -> Result<R, LockError> {
251 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
252 }
253
246 pub fn has_dirstate_v2(&self) -> bool {
254 pub fn has_dirstate_v2(&self) -> bool {
247 self.requirements
255 self.requirements
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
256 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
249 }
257 }
250
258
251 pub fn has_sparse(&self) -> bool {
259 pub fn has_sparse(&self) -> bool {
252 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
260 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
253 }
261 }
254
262
255 pub fn has_narrow(&self) -> bool {
263 pub fn has_narrow(&self) -> bool {
256 self.requirements.contains(requirements::NARROW_REQUIREMENT)
264 self.requirements.contains(requirements::NARROW_REQUIREMENT)
257 }
265 }
258
266
259 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
267 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
260 Ok(self
268 Ok(self
261 .hg_vfs()
269 .hg_vfs()
262 .read("dirstate")
270 .read("dirstate")
263 .io_not_found_as_none()?
271 .io_not_found_as_none()?
264 .unwrap_or(Vec::new()))
272 .unwrap_or(Vec::new()))
265 }
273 }
266
274
267 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
275 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
268 if let Some(parents) = self.dirstate_parents.get() {
276 if let Some(parents) = self.dirstate_parents.get() {
269 return Ok(parents);
277 return Ok(parents);
270 }
278 }
271 let dirstate = self.dirstate_file_contents()?;
279 let dirstate = self.dirstate_file_contents()?;
272 let parents = if dirstate.is_empty() {
280 let parents = if dirstate.is_empty() {
273 DirstateParents::NULL
281 DirstateParents::NULL
274 } else if self.has_dirstate_v2() {
282 } else if self.has_dirstate_v2() {
275 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
283 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
276 } else {
284 } else {
277 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
285 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
278 .clone()
286 .clone()
279 };
287 };
280 self.dirstate_parents.set(Some(parents));
288 self.dirstate_parents.set(Some(parents));
281 Ok(parents)
289 Ok(parents)
282 }
290 }
283
291
284 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
292 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
285 let dirstate_file_contents = self.dirstate_file_contents()?;
293 let dirstate_file_contents = self.dirstate_file_contents()?;
286 if dirstate_file_contents.is_empty() {
294 if dirstate_file_contents.is_empty() {
287 self.dirstate_parents.set(Some(DirstateParents::NULL));
295 self.dirstate_parents.set(Some(DirstateParents::NULL));
288 Ok(OwningDirstateMap::new_empty(Vec::new()))
296 Ok(OwningDirstateMap::new_empty(Vec::new()))
289 } else if self.has_dirstate_v2() {
297 } else if self.has_dirstate_v2() {
290 let docket = crate::dirstate_tree::on_disk::read_docket(
298 let docket = crate::dirstate_tree::on_disk::read_docket(
291 &dirstate_file_contents,
299 &dirstate_file_contents,
292 )?;
300 )?;
293 self.dirstate_parents.set(Some(docket.parents()));
301 self.dirstate_parents.set(Some(docket.parents()));
294 let data_size = docket.data_size();
302 let data_size = docket.data_size();
295 let metadata = docket.tree_metadata();
303 let metadata = docket.tree_metadata();
296 let mut map = if let Some(data_mmap) = self
304 let mut map = if let Some(data_mmap) = self
297 .hg_vfs()
305 .hg_vfs()
298 .mmap_open(docket.data_filename())
306 .mmap_open(docket.data_filename())
299 .io_not_found_as_none()?
307 .io_not_found_as_none()?
300 {
308 {
301 OwningDirstateMap::new_empty(data_mmap)
309 OwningDirstateMap::new_empty(data_mmap)
302 } else {
310 } else {
303 OwningDirstateMap::new_empty(Vec::new())
311 OwningDirstateMap::new_empty(Vec::new())
304 };
312 };
305 let (on_disk, placeholder) = map.get_pair_mut();
313 let (on_disk, placeholder) = map.get_pair_mut();
306 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
314 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
307 Ok(map)
315 Ok(map)
308 } else {
316 } else {
309 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
317 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
310 let (on_disk, placeholder) = map.get_pair_mut();
318 let (on_disk, placeholder) = map.get_pair_mut();
311 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
319 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
312 self.dirstate_parents
320 self.dirstate_parents
313 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
321 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
314 *placeholder = inner;
322 *placeholder = inner;
315 Ok(map)
323 Ok(map)
316 }
324 }
317 }
325 }
318
326
319 pub fn dirstate_map(
327 pub fn dirstate_map(
320 &self,
328 &self,
321 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
329 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
322 self.dirstate_map.get_or_init(self)
330 self.dirstate_map.get_or_init(self)
323 }
331 }
324
332
325 pub fn dirstate_map_mut(
333 pub fn dirstate_map_mut(
326 &self,
334 &self,
327 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
335 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
328 self.dirstate_map.get_mut_or_init(self)
336 self.dirstate_map.get_mut_or_init(self)
329 }
337 }
330
338
331 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
339 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
332 self.changelog.get_or_init(self)
340 self.changelog.get_or_init(self)
333 }
341 }
334
342
335 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
343 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
336 self.changelog.get_mut_or_init(self)
344 self.changelog.get_mut_or_init(self)
337 }
345 }
338
346
339 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
347 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
340 self.manifestlog.get_or_init(self)
348 self.manifestlog.get_or_init(self)
341 }
349 }
342
350
343 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
351 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
344 self.manifestlog.get_mut_or_init(self)
352 self.manifestlog.get_mut_or_init(self)
345 }
353 }
346
354
347 /// Returns the manifest of the *changeset* with the given node ID
355 /// Returns the manifest of the *changeset* with the given node ID
348 pub fn manifest_for_node(
356 pub fn manifest_for_node(
349 &self,
357 &self,
350 node: impl Into<NodePrefix>,
358 node: impl Into<NodePrefix>,
351 ) -> Result<Manifest, RevlogError> {
359 ) -> Result<Manifest, RevlogError> {
352 self.manifestlog()?.data_for_node(
360 self.manifestlog()?.data_for_node(
353 self.changelog()?
361 self.changelog()?
354 .data_for_node(node.into())?
362 .data_for_node(node.into())?
355 .manifest_node()?
363 .manifest_node()?
356 .into(),
364 .into(),
357 )
365 )
358 }
366 }
359
367
360 /// Returns the manifest of the *changeset* with the given revision number
368 /// Returns the manifest of the *changeset* with the given revision number
361 pub fn manifest_for_rev(
369 pub fn manifest_for_rev(
362 &self,
370 &self,
363 revision: Revision,
371 revision: Revision,
364 ) -> Result<Manifest, RevlogError> {
372 ) -> Result<Manifest, RevlogError> {
365 self.manifestlog()?.data_for_node(
373 self.manifestlog()?.data_for_node(
366 self.changelog()?
374 self.changelog()?
367 .data_for_rev(revision)?
375 .data_for_rev(revision)?
368 .manifest_node()?
376 .manifest_node()?
369 .into(),
377 .into(),
370 )
378 )
371 }
379 }
372
380
373 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
381 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
374 Filelog::open(self, path)
382 Filelog::open(self, path)
375 }
383 }
376 }
384 }
377
385
378 /// Lazily-initialized component of `Repo` with interior mutability
386 /// Lazily-initialized component of `Repo` with interior mutability
379 ///
387 ///
380 /// This differs from `OnceCell` in that the value can still be "deinitialized"
388 /// This differs from `OnceCell` in that the value can still be "deinitialized"
381 /// later by setting its inner `Option` to `None`.
389 /// later by setting its inner `Option` to `None`.
382 struct LazyCell<T, E> {
390 struct LazyCell<T, E> {
383 value: RefCell<Option<T>>,
391 value: RefCell<Option<T>>,
384 // `Fn`s that don’t capture environment are zero-size, so this box does
392 // `Fn`s that don’t capture environment are zero-size, so this box does
385 // not allocate:
393 // not allocate:
386 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
394 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
387 }
395 }
388
396
389 impl<T, E> LazyCell<T, E> {
397 impl<T, E> LazyCell<T, E> {
390 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
398 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
391 Self {
399 Self {
392 value: RefCell::new(None),
400 value: RefCell::new(None),
393 init: Box::new(init),
401 init: Box::new(init),
394 }
402 }
395 }
403 }
396
404
397 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
405 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
398 let mut borrowed = self.value.borrow();
406 let mut borrowed = self.value.borrow();
399 if borrowed.is_none() {
407 if borrowed.is_none() {
400 drop(borrowed);
408 drop(borrowed);
401 // Only use `borrow_mut` if it is really needed to avoid panic in
409 // Only use `borrow_mut` if it is really needed to avoid panic in
402 // case there is another outstanding borrow but mutation is not
410 // case there is another outstanding borrow but mutation is not
403 // needed.
411 // needed.
404 *self.value.borrow_mut() = Some((self.init)(repo)?);
412 *self.value.borrow_mut() = Some((self.init)(repo)?);
405 borrowed = self.value.borrow()
413 borrowed = self.value.borrow()
406 }
414 }
407 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
415 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
408 }
416 }
409
417
410 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
418 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
411 let mut borrowed = self.value.borrow_mut();
419 let mut borrowed = self.value.borrow_mut();
412 if borrowed.is_none() {
420 if borrowed.is_none() {
413 *borrowed = Some((self.init)(repo)?);
421 *borrowed = Some((self.init)(repo)?);
414 }
422 }
415 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
423 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
416 }
424 }
417 }
425 }
@@ -1,490 +1,505 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(&self) -> &Self;
70 fn trim_end(&self) -> &Self;
71 fn trim_start(&self) -> &Self;
71 fn trim_start(&self) -> &Self;
72 fn trim_end_matches(&self, f: impl FnMut(u8) -> bool) -> &Self;
72 fn trim_end_matches(&self, f: impl FnMut(u8) -> bool) -> &Self;
73 fn trim_start_matches(&self, f: impl FnMut(u8) -> bool) -> &Self;
73 fn trim_start_matches(&self, f: impl FnMut(u8) -> bool) -> &Self;
74 fn trim(&self) -> &Self;
74 fn trim(&self) -> &Self;
75 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
75 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
76 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
76 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
77 fn split_2_by_slice(&self, separator: &[u8]) -> Option<(&[u8], &[u8])>;
77 fn split_2_by_slice(&self, separator: &[u8]) -> Option<(&[u8], &[u8])>;
78 }
78 }
79
79
80 impl SliceExt for [u8] {
80 impl SliceExt for [u8] {
81 fn trim_end(&self) -> &[u8] {
81 fn trim_end(&self) -> &[u8] {
82 self.trim_end_matches(|byte| byte.is_ascii_whitespace())
82 self.trim_end_matches(|byte| byte.is_ascii_whitespace())
83 }
83 }
84
84
85 fn trim_start(&self) -> &[u8] {
85 fn trim_start(&self) -> &[u8] {
86 self.trim_start_matches(|byte| byte.is_ascii_whitespace())
86 self.trim_start_matches(|byte| byte.is_ascii_whitespace())
87 }
87 }
88
88
89 fn trim_end_matches(&self, mut f: impl FnMut(u8) -> bool) -> &Self {
89 fn trim_end_matches(&self, mut f: impl FnMut(u8) -> bool) -> &Self {
90 if let Some(last) = self.iter().rposition(|&byte| !f(byte)) {
90 if let Some(last) = self.iter().rposition(|&byte| !f(byte)) {
91 &self[..=last]
91 &self[..=last]
92 } else {
92 } else {
93 &[]
93 &[]
94 }
94 }
95 }
95 }
96
96
97 fn trim_start_matches(&self, mut f: impl FnMut(u8) -> bool) -> &Self {
97 fn trim_start_matches(&self, mut f: impl FnMut(u8) -> bool) -> &Self {
98 if let Some(first) = self.iter().position(|&byte| !f(byte)) {
98 if let Some(first) = self.iter().position(|&byte| !f(byte)) {
99 &self[first..]
99 &self[first..]
100 } else {
100 } else {
101 &[]
101 &[]
102 }
102 }
103 }
103 }
104
104
105 /// ```
105 /// ```
106 /// use hg::utils::SliceExt;
106 /// use hg::utils::SliceExt;
107 /// assert_eq!(
107 /// assert_eq!(
108 /// b" to trim ".trim(),
108 /// b" to trim ".trim(),
109 /// b"to trim"
109 /// b"to trim"
110 /// );
110 /// );
111 /// assert_eq!(
111 /// assert_eq!(
112 /// b"to trim ".trim(),
112 /// b"to trim ".trim(),
113 /// b"to trim"
113 /// b"to trim"
114 /// );
114 /// );
115 /// assert_eq!(
115 /// assert_eq!(
116 /// b" to trim".trim(),
116 /// b" to trim".trim(),
117 /// b"to trim"
117 /// b"to trim"
118 /// );
118 /// );
119 /// ```
119 /// ```
120 fn trim(&self) -> &[u8] {
120 fn trim(&self) -> &[u8] {
121 self.trim_start().trim_end()
121 self.trim_start().trim_end()
122 }
122 }
123
123
124 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
124 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
125 if self.starts_with(needle) {
125 if self.starts_with(needle) {
126 Some(&self[needle.len()..])
126 Some(&self[needle.len()..])
127 } else {
127 } else {
128 None
128 None
129 }
129 }
130 }
130 }
131
131
132 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
132 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
133 let mut iter = self.splitn(2, |&byte| byte == separator);
133 let mut iter = self.splitn(2, |&byte| byte == separator);
134 let a = iter.next()?;
134 let a = iter.next()?;
135 let b = iter.next()?;
135 let b = iter.next()?;
136 Some((a, b))
136 Some((a, b))
137 }
137 }
138
138
139 fn split_2_by_slice(&self, separator: &[u8]) -> Option<(&[u8], &[u8])> {
139 fn split_2_by_slice(&self, separator: &[u8]) -> Option<(&[u8], &[u8])> {
140 if let Some(pos) = find_slice_in_slice(self, separator) {
140 if let Some(pos) = find_slice_in_slice(self, separator) {
141 Some((&self[..pos], &self[pos + separator.len()..]))
141 Some((&self[..pos], &self[pos + separator.len()..]))
142 } else {
142 } else {
143 None
143 None
144 }
144 }
145 }
145 }
146 }
146 }
147
147
148 pub trait StrExt {
149 // TODO: Use https://doc.rust-lang.org/nightly/std/primitive.str.html#method.split_once
150 // once we require Rust 1.52+
151 fn split_2(&self, separator: char) -> Option<(&str, &str)>;
152 }
153
154 impl StrExt for str {
155 fn split_2(&self, separator: char) -> Option<(&str, &str)> {
156 let mut iter = self.splitn(2, separator);
157 let a = iter.next()?;
158 let b = iter.next()?;
159 Some((a, b))
160 }
161 }
162
148 pub trait Escaped {
163 pub trait Escaped {
149 /// Return bytes escaped for display to the user
164 /// Return bytes escaped for display to the user
150 fn escaped_bytes(&self) -> Vec<u8>;
165 fn escaped_bytes(&self) -> Vec<u8>;
151 }
166 }
152
167
153 impl Escaped for u8 {
168 impl Escaped for u8 {
154 fn escaped_bytes(&self) -> Vec<u8> {
169 fn escaped_bytes(&self) -> Vec<u8> {
155 let mut acc = vec![];
170 let mut acc = vec![];
156 match self {
171 match self {
157 c @ b'\'' | c @ b'\\' => {
172 c @ b'\'' | c @ b'\\' => {
158 acc.push(b'\\');
173 acc.push(b'\\');
159 acc.push(*c);
174 acc.push(*c);
160 }
175 }
161 b'\t' => {
176 b'\t' => {
162 acc.extend(br"\\t");
177 acc.extend(br"\\t");
163 }
178 }
164 b'\n' => {
179 b'\n' => {
165 acc.extend(br"\\n");
180 acc.extend(br"\\n");
166 }
181 }
167 b'\r' => {
182 b'\r' => {
168 acc.extend(br"\\r");
183 acc.extend(br"\\r");
169 }
184 }
170 c if (*c < b' ' || *c >= 127) => {
185 c if (*c < b' ' || *c >= 127) => {
171 write!(acc, "\\x{:x}", self).unwrap();
186 write!(acc, "\\x{:x}", self).unwrap();
172 }
187 }
173 c => {
188 c => {
174 acc.push(*c);
189 acc.push(*c);
175 }
190 }
176 }
191 }
177 acc
192 acc
178 }
193 }
179 }
194 }
180
195
181 impl<'a, T: Escaped> Escaped for &'a [T] {
196 impl<'a, T: Escaped> Escaped for &'a [T] {
182 fn escaped_bytes(&self) -> Vec<u8> {
197 fn escaped_bytes(&self) -> Vec<u8> {
183 self.iter().flat_map(Escaped::escaped_bytes).collect()
198 self.iter().flat_map(Escaped::escaped_bytes).collect()
184 }
199 }
185 }
200 }
186
201
187 impl<T: Escaped> Escaped for Vec<T> {
202 impl<T: Escaped> Escaped for Vec<T> {
188 fn escaped_bytes(&self) -> Vec<u8> {
203 fn escaped_bytes(&self) -> Vec<u8> {
189 self.deref().escaped_bytes()
204 self.deref().escaped_bytes()
190 }
205 }
191 }
206 }
192
207
193 impl<'a> Escaped for &'a HgPath {
208 impl<'a> Escaped for &'a HgPath {
194 fn escaped_bytes(&self) -> Vec<u8> {
209 fn escaped_bytes(&self) -> Vec<u8> {
195 self.as_bytes().escaped_bytes()
210 self.as_bytes().escaped_bytes()
196 }
211 }
197 }
212 }
198
213
199 // TODO: use the str method when we require Rust 1.45
214 // TODO: use the str method when we require Rust 1.45
200 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
215 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
201 if s.ends_with(suffix) {
216 if s.ends_with(suffix) {
202 Some(&s[..s.len() - suffix.len()])
217 Some(&s[..s.len() - suffix.len()])
203 } else {
218 } else {
204 None
219 None
205 }
220 }
206 }
221 }
207
222
208 #[cfg(unix)]
223 #[cfg(unix)]
209 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
224 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
210 // TODO: Use the `matches!` macro when we require Rust 1.42+
225 // TODO: Use the `matches!` macro when we require Rust 1.42+
211 if value.iter().all(|&byte| match byte {
226 if value.iter().all(|&byte| match byte {
212 b'a'..=b'z'
227 b'a'..=b'z'
213 | b'A'..=b'Z'
228 | b'A'..=b'Z'
214 | b'0'..=b'9'
229 | b'0'..=b'9'
215 | b'.'
230 | b'.'
216 | b'_'
231 | b'_'
217 | b'/'
232 | b'/'
218 | b'+'
233 | b'+'
219 | b'-' => true,
234 | b'-' => true,
220 _ => false,
235 _ => false,
221 }) {
236 }) {
222 value.to_owned()
237 value.to_owned()
223 } else {
238 } else {
224 let mut quoted = Vec::with_capacity(value.len() + 2);
239 let mut quoted = Vec::with_capacity(value.len() + 2);
225 quoted.push(b'\'');
240 quoted.push(b'\'');
226 for &byte in value {
241 for &byte in value {
227 if byte == b'\'' {
242 if byte == b'\'' {
228 quoted.push(b'\\');
243 quoted.push(b'\\');
229 }
244 }
230 quoted.push(byte);
245 quoted.push(byte);
231 }
246 }
232 quoted.push(b'\'');
247 quoted.push(b'\'');
233 quoted
248 quoted
234 }
249 }
235 }
250 }
236
251
237 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
252 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
238 std::env::current_dir().map_err(|error| HgError::IoError {
253 std::env::current_dir().map_err(|error| HgError::IoError {
239 error,
254 error,
240 context: IoErrorContext::CurrentDir,
255 context: IoErrorContext::CurrentDir,
241 })
256 })
242 }
257 }
243
258
244 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
259 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
245 std::env::current_exe().map_err(|error| HgError::IoError {
260 std::env::current_exe().map_err(|error| HgError::IoError {
246 error,
261 error,
247 context: IoErrorContext::CurrentExe,
262 context: IoErrorContext::CurrentExe,
248 })
263 })
249 }
264 }
250
265
251 /// Expand `$FOO` and `${FOO}` environment variables in the given byte string
266 /// Expand `$FOO` and `${FOO}` environment variables in the given byte string
252 pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> {
267 pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> {
253 lazy_static::lazy_static! {
268 lazy_static::lazy_static! {
254 /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301
269 /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301
255 /// The `x` makes whitespace ignored.
270 /// The `x` makes whitespace ignored.
256 /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag.
271 /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag.
257 static ref VAR_RE: regex::bytes::Regex =
272 static ref VAR_RE: regex::bytes::Regex =
258 regex::bytes::Regex::new(r"(?x-u)
273 regex::bytes::Regex::new(r"(?x-u)
259 \$
274 \$
260 (?:
275 (?:
261 (\w+)
276 (\w+)
262 |
277 |
263 \{
278 \{
264 ([^}]*)
279 ([^}]*)
265 \}
280 \}
266 )
281 )
267 ").unwrap();
282 ").unwrap();
268 }
283 }
269 VAR_RE.replace_all(s, |captures: &regex::bytes::Captures| {
284 VAR_RE.replace_all(s, |captures: &regex::bytes::Captures| {
270 let var_name = files::get_os_str_from_bytes(
285 let var_name = files::get_os_str_from_bytes(
271 captures
286 captures
272 .get(1)
287 .get(1)
273 .or_else(|| captures.get(2))
288 .or_else(|| captures.get(2))
274 .expect("either side of `|` must participate in match")
289 .expect("either side of `|` must participate in match")
275 .as_bytes(),
290 .as_bytes(),
276 );
291 );
277 std::env::var_os(var_name)
292 std::env::var_os(var_name)
278 .map(files::get_bytes_from_os_str)
293 .map(files::get_bytes_from_os_str)
279 .unwrap_or_else(|| {
294 .unwrap_or_else(|| {
280 // Referencing an environment variable that does not exist.
295 // Referencing an environment variable that does not exist.
281 // Leave the $FOO reference as-is.
296 // Leave the $FOO reference as-is.
282 captures[0].to_owned()
297 captures[0].to_owned()
283 })
298 })
284 })
299 })
285 }
300 }
286
301
287 #[test]
302 #[test]
288 fn test_expand_vars() {
303 fn test_expand_vars() {
289 // Modifying process-global state in a test isn’t great,
304 // Modifying process-global state in a test isn’t great,
290 // but hopefully this won’t collide with anything.
305 // but hopefully this won’t collide with anything.
291 std::env::set_var("TEST_EXPAND_VAR", "1");
306 std::env::set_var("TEST_EXPAND_VAR", "1");
292 assert_eq!(
307 assert_eq!(
293 expand_vars(b"before/$TEST_EXPAND_VAR/after"),
308 expand_vars(b"before/$TEST_EXPAND_VAR/after"),
294 &b"before/1/after"[..]
309 &b"before/1/after"[..]
295 );
310 );
296 assert_eq!(
311 assert_eq!(
297 expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"),
312 expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"),
298 &b"before111after"[..]
313 &b"before111after"[..]
299 );
314 );
300 let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after";
315 let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after";
301 assert_eq!(expand_vars(s), &s[..]);
316 assert_eq!(expand_vars(s), &s[..]);
302 }
317 }
303
318
304 pub(crate) enum MergeResult<V> {
319 pub(crate) enum MergeResult<V> {
305 UseLeftValue,
320 UseLeftValue,
306 UseRightValue,
321 UseRightValue,
307 UseNewValue(V),
322 UseNewValue(V),
308 }
323 }
309
324
310 /// Return the union of the two given maps,
325 /// Return the union of the two given maps,
311 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
326 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
312 /// both.
327 /// both.
313 ///
328 ///
314 /// CC https://github.com/bodil/im-rs/issues/166
329 /// CC https://github.com/bodil/im-rs/issues/166
315 pub(crate) fn ordmap_union_with_merge<K, V>(
330 pub(crate) fn ordmap_union_with_merge<K, V>(
316 left: OrdMap<K, V>,
331 left: OrdMap<K, V>,
317 right: OrdMap<K, V>,
332 right: OrdMap<K, V>,
318 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
333 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
319 ) -> OrdMap<K, V>
334 ) -> OrdMap<K, V>
320 where
335 where
321 K: Clone + Ord,
336 K: Clone + Ord,
322 V: Clone + PartialEq,
337 V: Clone + PartialEq,
323 {
338 {
324 if left.ptr_eq(&right) {
339 if left.ptr_eq(&right) {
325 // One of the two maps is an unmodified clone of the other
340 // One of the two maps is an unmodified clone of the other
326 left
341 left
327 } else if left.len() / 2 > right.len() {
342 } else if left.len() / 2 > right.len() {
328 // When two maps have different sizes,
343 // When two maps have different sizes,
329 // their size difference is a lower bound on
344 // their size difference is a lower bound on
330 // how many keys of the larger map are not also in the smaller map.
345 // how many keys of the larger map are not also in the smaller map.
331 // This in turn is a lower bound on the number of differences in
346 // This in turn is a lower bound on the number of differences in
332 // `OrdMap::diff` and the "amount of work" that would be done
347 // `OrdMap::diff` and the "amount of work" that would be done
333 // by `ordmap_union_with_merge_by_diff`.
348 // by `ordmap_union_with_merge_by_diff`.
334 //
349 //
335 // Here `left` is more than twice the size of `right`,
350 // Here `left` is more than twice the size of `right`,
336 // so the number of differences is more than the total size of
351 // so the number of differences is more than the total size of
337 // `right`. Therefore an algorithm based on iterating `right`
352 // `right`. Therefore an algorithm based on iterating `right`
338 // is more efficient.
353 // is more efficient.
339 //
354 //
340 // This helps a lot when a tiny (or empty) map is merged
355 // This helps a lot when a tiny (or empty) map is merged
341 // with a large one.
356 // with a large one.
342 ordmap_union_with_merge_by_iter(left, right, merge)
357 ordmap_union_with_merge_by_iter(left, right, merge)
343 } else if left.len() < right.len() / 2 {
358 } else if left.len() < right.len() / 2 {
344 // Same as above but with `left` and `right` swapped
359 // Same as above but with `left` and `right` swapped
345 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
360 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
346 // Also swapped in `merge` arguments:
361 // Also swapped in `merge` arguments:
347 match merge(key, b, a) {
362 match merge(key, b, a) {
348 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
363 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
349 // … and swap back in `merge` result:
364 // … and swap back in `merge` result:
350 MergeResult::UseLeftValue => MergeResult::UseRightValue,
365 MergeResult::UseLeftValue => MergeResult::UseRightValue,
351 MergeResult::UseRightValue => MergeResult::UseLeftValue,
366 MergeResult::UseRightValue => MergeResult::UseLeftValue,
352 }
367 }
353 })
368 })
354 } else {
369 } else {
355 // For maps of similar size, use the algorithm based on `OrdMap::diff`
370 // For maps of similar size, use the algorithm based on `OrdMap::diff`
356 ordmap_union_with_merge_by_diff(left, right, merge)
371 ordmap_union_with_merge_by_diff(left, right, merge)
357 }
372 }
358 }
373 }
359
374
360 /// Efficient if `right` is much smaller than `left`
375 /// Efficient if `right` is much smaller than `left`
361 fn ordmap_union_with_merge_by_iter<K, V>(
376 fn ordmap_union_with_merge_by_iter<K, V>(
362 mut left: OrdMap<K, V>,
377 mut left: OrdMap<K, V>,
363 right: OrdMap<K, V>,
378 right: OrdMap<K, V>,
364 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
379 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
365 ) -> OrdMap<K, V>
380 ) -> OrdMap<K, V>
366 where
381 where
367 K: Clone + Ord,
382 K: Clone + Ord,
368 V: Clone,
383 V: Clone,
369 {
384 {
370 for (key, right_value) in right {
385 for (key, right_value) in right {
371 match left.get(&key) {
386 match left.get(&key) {
372 None => {
387 None => {
373 left.insert(key, right_value);
388 left.insert(key, right_value);
374 }
389 }
375 Some(left_value) => match merge(&key, left_value, &right_value) {
390 Some(left_value) => match merge(&key, left_value, &right_value) {
376 MergeResult::UseLeftValue => {}
391 MergeResult::UseLeftValue => {}
377 MergeResult::UseRightValue => {
392 MergeResult::UseRightValue => {
378 left.insert(key, right_value);
393 left.insert(key, right_value);
379 }
394 }
380 MergeResult::UseNewValue(new_value) => {
395 MergeResult::UseNewValue(new_value) => {
381 left.insert(key, new_value);
396 left.insert(key, new_value);
382 }
397 }
383 },
398 },
384 }
399 }
385 }
400 }
386 left
401 left
387 }
402 }
388
403
389 /// Fallback when both maps are of similar size
404 /// Fallback when both maps are of similar size
390 fn ordmap_union_with_merge_by_diff<K, V>(
405 fn ordmap_union_with_merge_by_diff<K, V>(
391 mut left: OrdMap<K, V>,
406 mut left: OrdMap<K, V>,
392 mut right: OrdMap<K, V>,
407 mut right: OrdMap<K, V>,
393 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
408 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
394 ) -> OrdMap<K, V>
409 ) -> OrdMap<K, V>
395 where
410 where
396 K: Clone + Ord,
411 K: Clone + Ord,
397 V: Clone + PartialEq,
412 V: Clone + PartialEq,
398 {
413 {
399 // (key, value) pairs that would need to be inserted in either map
414 // (key, value) pairs that would need to be inserted in either map
400 // in order to turn it into the union.
415 // in order to turn it into the union.
401 //
416 //
402 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
417 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
403 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
418 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
404 // with `left_updates` only borrowing from `right` and `right_updates` from
419 // with `left_updates` only borrowing from `right` and `right_updates` from
405 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
420 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
406 //
421 //
407 // This would allow moving all `.clone()` calls to after we’ve decided
422 // This would allow moving all `.clone()` calls to after we’ve decided
408 // which of `right_updates` or `left_updates` to use
423 // which of `right_updates` or `left_updates` to use
409 // (value ones becoming `Cow::into_owned`),
424 // (value ones becoming `Cow::into_owned`),
410 // and avoid making clones we don’t end up using.
425 // and avoid making clones we don’t end up using.
411 let mut left_updates = Vec::new();
426 let mut left_updates = Vec::new();
412 let mut right_updates = Vec::new();
427 let mut right_updates = Vec::new();
413
428
414 for difference in left.diff(&right) {
429 for difference in left.diff(&right) {
415 match difference {
430 match difference {
416 DiffItem::Add(key, value) => {
431 DiffItem::Add(key, value) => {
417 left_updates.push((key.clone(), value.clone()))
432 left_updates.push((key.clone(), value.clone()))
418 }
433 }
419 DiffItem::Remove(key, value) => {
434 DiffItem::Remove(key, value) => {
420 right_updates.push((key.clone(), value.clone()))
435 right_updates.push((key.clone(), value.clone()))
421 }
436 }
422 DiffItem::Update {
437 DiffItem::Update {
423 old: (key, left_value),
438 old: (key, left_value),
424 new: (_, right_value),
439 new: (_, right_value),
425 } => match merge(key, left_value, right_value) {
440 } => match merge(key, left_value, right_value) {
426 MergeResult::UseLeftValue => {
441 MergeResult::UseLeftValue => {
427 right_updates.push((key.clone(), left_value.clone()))
442 right_updates.push((key.clone(), left_value.clone()))
428 }
443 }
429 MergeResult::UseRightValue => {
444 MergeResult::UseRightValue => {
430 left_updates.push((key.clone(), right_value.clone()))
445 left_updates.push((key.clone(), right_value.clone()))
431 }
446 }
432 MergeResult::UseNewValue(new_value) => {
447 MergeResult::UseNewValue(new_value) => {
433 left_updates.push((key.clone(), new_value.clone()));
448 left_updates.push((key.clone(), new_value.clone()));
434 right_updates.push((key.clone(), new_value))
449 right_updates.push((key.clone(), new_value))
435 }
450 }
436 },
451 },
437 }
452 }
438 }
453 }
439 if left_updates.len() < right_updates.len() {
454 if left_updates.len() < right_updates.len() {
440 for (key, value) in left_updates {
455 for (key, value) in left_updates {
441 left.insert(key, value);
456 left.insert(key, value);
442 }
457 }
443 left
458 left
444 } else {
459 } else {
445 for (key, value) in right_updates {
460 for (key, value) in right_updates {
446 right.insert(key, value);
461 right.insert(key, value);
447 }
462 }
448 right
463 right
449 }
464 }
450 }
465 }
451
466
452 /// Join items of the iterable with the given separator, similar to Python’s
467 /// Join items of the iterable with the given separator, similar to Python’s
453 /// `separator.join(iter)`.
468 /// `separator.join(iter)`.
454 ///
469 ///
455 /// Formatting the return value consumes the iterator.
470 /// Formatting the return value consumes the iterator.
456 /// Formatting it again will produce an empty string.
471 /// Formatting it again will produce an empty string.
457 pub fn join_display(
472 pub fn join_display(
458 iter: impl IntoIterator<Item = impl fmt::Display>,
473 iter: impl IntoIterator<Item = impl fmt::Display>,
459 separator: impl fmt::Display,
474 separator: impl fmt::Display,
460 ) -> impl fmt::Display {
475 ) -> impl fmt::Display {
461 JoinDisplay {
476 JoinDisplay {
462 iter: Cell::new(Some(iter.into_iter())),
477 iter: Cell::new(Some(iter.into_iter())),
463 separator,
478 separator,
464 }
479 }
465 }
480 }
466
481
467 struct JoinDisplay<I, S> {
482 struct JoinDisplay<I, S> {
468 iter: Cell<Option<I>>,
483 iter: Cell<Option<I>>,
469 separator: S,
484 separator: S,
470 }
485 }
471
486
472 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
487 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
473 where
488 where
474 I: Iterator<Item = T>,
489 I: Iterator<Item = T>,
475 T: fmt::Display,
490 T: fmt::Display,
476 S: fmt::Display,
491 S: fmt::Display,
477 {
492 {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 if let Some(mut iter) = self.iter.take() {
494 if let Some(mut iter) = self.iter.take() {
480 if let Some(first) = iter.next() {
495 if let Some(first) = iter.next() {
481 first.fmt(f)?;
496 first.fmt(f)?;
482 }
497 }
483 for value in iter {
498 for value in iter {
484 self.separator.fmt(f)?;
499 self.separator.fmt(f)?;
485 value.fmt(f)?;
500 value.fmt(f)?;
486 }
501 }
487 }
502 }
488 Ok(())
503 Ok(())
489 }
504 }
490 }
505 }
@@ -1,116 +1,136 b''
1 use crate::errors::{HgError, IoErrorContext, IoResultExt};
1 use crate::errors::{HgError, IoErrorContext, IoResultExt};
2 use memmap2::{Mmap, MmapOptions};
2 use memmap2::{Mmap, MmapOptions};
3 use std::io::ErrorKind;
3 use std::io::ErrorKind;
4 use std::path::{Path, PathBuf};
4 use std::path::{Path, PathBuf};
5
5
6 /// Filesystem access abstraction for the contents of a given "base" diretory
6 /// Filesystem access abstraction for the contents of a given "base" diretory
7 #[derive(Clone, Copy)]
7 #[derive(Clone, Copy)]
8 pub struct Vfs<'a> {
8 pub struct Vfs<'a> {
9 pub(crate) base: &'a Path,
9 pub(crate) base: &'a Path,
10 }
10 }
11
11
12 struct FileNotFound(std::io::Error, PathBuf);
12 struct FileNotFound(std::io::Error, PathBuf);
13
13
14 impl Vfs<'_> {
14 impl Vfs<'_> {
15 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
15 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
16 self.base.join(relative_path)
16 self.base.join(relative_path)
17 }
17 }
18
18
19 pub fn symlink_metadata(
19 pub fn symlink_metadata(
20 &self,
20 &self,
21 relative_path: impl AsRef<Path>,
21 relative_path: impl AsRef<Path>,
22 ) -> Result<std::fs::Metadata, HgError> {
22 ) -> Result<std::fs::Metadata, HgError> {
23 let path = self.join(relative_path);
23 let path = self.join(relative_path);
24 std::fs::symlink_metadata(&path).when_reading_file(&path)
24 std::fs::symlink_metadata(&path).when_reading_file(&path)
25 }
25 }
26
26
27 pub fn read_link(
27 pub fn read_link(
28 &self,
28 &self,
29 relative_path: impl AsRef<Path>,
29 relative_path: impl AsRef<Path>,
30 ) -> Result<PathBuf, HgError> {
30 ) -> Result<PathBuf, HgError> {
31 let path = self.join(relative_path);
31 let path = self.join(relative_path);
32 std::fs::read_link(&path).when_reading_file(&path)
32 std::fs::read_link(&path).when_reading_file(&path)
33 }
33 }
34
34
35 pub fn read(
35 pub fn read(
36 &self,
36 &self,
37 relative_path: impl AsRef<Path>,
37 relative_path: impl AsRef<Path>,
38 ) -> Result<Vec<u8>, HgError> {
38 ) -> Result<Vec<u8>, HgError> {
39 let path = self.join(relative_path);
39 let path = self.join(relative_path);
40 std::fs::read(&path).when_reading_file(&path)
40 std::fs::read(&path).when_reading_file(&path)
41 }
41 }
42
42
43 fn mmap_open_gen(
43 fn mmap_open_gen(
44 &self,
44 &self,
45 relative_path: impl AsRef<Path>,
45 relative_path: impl AsRef<Path>,
46 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
46 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
47 let path = self.join(relative_path);
47 let path = self.join(relative_path);
48 let file = match std::fs::File::open(&path) {
48 let file = match std::fs::File::open(&path) {
49 Err(err) => {
49 Err(err) => {
50 if let ErrorKind::NotFound = err.kind() {
50 if let ErrorKind::NotFound = err.kind() {
51 return Ok(Err(FileNotFound(err, path)));
51 return Ok(Err(FileNotFound(err, path)));
52 };
52 };
53 return (Err(err)).when_reading_file(&path);
53 return (Err(err)).when_reading_file(&path);
54 }
54 }
55 Ok(file) => file,
55 Ok(file) => file,
56 };
56 };
57 // TODO: what are the safety requirements here?
57 // TODO: what are the safety requirements here?
58 let mmap = unsafe { MmapOptions::new().map(&file) }
58 let mmap = unsafe { MmapOptions::new().map(&file) }
59 .when_reading_file(&path)?;
59 .when_reading_file(&path)?;
60 Ok(Ok(mmap))
60 Ok(Ok(mmap))
61 }
61 }
62
62
63 pub fn mmap_open_opt(
63 pub fn mmap_open_opt(
64 &self,
64 &self,
65 relative_path: impl AsRef<Path>,
65 relative_path: impl AsRef<Path>,
66 ) -> Result<Option<Mmap>, HgError> {
66 ) -> Result<Option<Mmap>, HgError> {
67 self.mmap_open_gen(relative_path).map(|res| res.ok())
67 self.mmap_open_gen(relative_path).map(|res| res.ok())
68 }
68 }
69
69
70 pub fn mmap_open(
70 pub fn mmap_open(
71 &self,
71 &self,
72 relative_path: impl AsRef<Path>,
72 relative_path: impl AsRef<Path>,
73 ) -> Result<Mmap, HgError> {
73 ) -> Result<Mmap, HgError> {
74 match self.mmap_open_gen(relative_path)? {
74 match self.mmap_open_gen(relative_path)? {
75 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
75 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
76 Ok(res) => Ok(res),
76 Ok(res) => Ok(res),
77 }
77 }
78 }
78 }
79
79
80 pub fn rename(
80 pub fn rename(
81 &self,
81 &self,
82 relative_from: impl AsRef<Path>,
82 relative_from: impl AsRef<Path>,
83 relative_to: impl AsRef<Path>,
83 relative_to: impl AsRef<Path>,
84 ) -> Result<(), HgError> {
84 ) -> Result<(), HgError> {
85 let from = self.join(relative_from);
85 let from = self.join(relative_from);
86 let to = self.join(relative_to);
86 let to = self.join(relative_to);
87 std::fs::rename(&from, &to)
87 std::fs::rename(&from, &to)
88 .with_context(|| IoErrorContext::RenamingFile { from, to })
88 .with_context(|| IoErrorContext::RenamingFile { from, to })
89 }
89 }
90
91 pub fn remove_file(
92 &self,
93 relative_path: impl AsRef<Path>,
94 ) -> Result<(), HgError> {
95 let path = self.join(relative_path);
96 std::fs::remove_file(&path)
97 .with_context(|| IoErrorContext::RemovingFile(path))
98 }
99
100 #[cfg(unix)]
101 pub fn create_symlink(
102 &self,
103 relative_link_path: impl AsRef<Path>,
104 target_path: impl AsRef<Path>,
105 ) -> Result<(), HgError> {
106 let link_path = self.join(relative_link_path);
107 std::os::unix::fs::symlink(target_path, &link_path)
108 .with_context(|| IoErrorContext::WritingFile(link_path))
109 }
90 }
110 }
91
111
92 fn fs_metadata(
112 fn fs_metadata(
93 path: impl AsRef<Path>,
113 path: impl AsRef<Path>,
94 ) -> Result<Option<std::fs::Metadata>, HgError> {
114 ) -> Result<Option<std::fs::Metadata>, HgError> {
95 let path = path.as_ref();
115 let path = path.as_ref();
96 match std::fs::metadata(path) {
116 match std::fs::metadata(path) {
97 Ok(meta) => Ok(Some(meta)),
117 Ok(meta) => Ok(Some(meta)),
98 Err(error) => match error.kind() {
118 Err(error) => match error.kind() {
99 // TODO: when we require a Rust version where `NotADirectory` is
119 // TODO: when we require a Rust version where `NotADirectory` is
100 // stable, invert this logic and return None for it and `NotFound`
120 // stable, invert this logic and return None for it and `NotFound`
101 // and propagate any other error.
121 // and propagate any other error.
102 ErrorKind::PermissionDenied => Err(error).with_context(|| {
122 ErrorKind::PermissionDenied => Err(error).with_context(|| {
103 IoErrorContext::ReadingMetadata(path.to_owned())
123 IoErrorContext::ReadingMetadata(path.to_owned())
104 }),
124 }),
105 _ => Ok(None),
125 _ => Ok(None),
106 },
126 },
107 }
127 }
108 }
128 }
109
129
110 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
130 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
111 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
131 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
112 }
132 }
113
133
114 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
134 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
115 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
135 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
116 }
136 }
General Comments 0
You need to be logged in to leave comments. Login now