##// END OF EJS Templates
rust: Add a Filelog struct that wraps Revlog...
Simon Sapin -
r48775:4d2a5ca0 default
parent child Browse files
Show More
@@ -0,0 +1,79 b''
1 use crate::errors::HgError;
2 use crate::repo::Repo;
3 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::revlog::{Revlog, RevlogError};
5 use crate::revlog::NodePrefix;
6 use crate::revlog::Revision;
7 use crate::utils::files::get_path_from_bytes;
8 use crate::utils::hg_path::HgPath;
9 use crate::utils::SliceExt;
10 use std::borrow::Cow;
11 use std::path::PathBuf;
12
13 /// A specialized `Revlog` to work with file data logs.
14 pub struct Filelog {
15 /// The generic `revlog` format.
16 revlog: Revlog,
17 }
18
19 impl Filelog {
20 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, RevlogError> {
21 let index_path = store_path(file_path, b".i");
22 let data_path = store_path(file_path, b".d");
23 let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
24 Ok(Self { revlog })
25 }
26
27 /// The given node ID is that of the file as found in a manifest, not of a
28 /// changeset.
29 pub fn get_node(
30 &self,
31 file_node: impl Into<NodePrefix>,
32 ) -> Result<FilelogEntry, RevlogError> {
33 let file_rev = self.revlog.get_node_rev(file_node.into())?;
34 self.get_rev(file_rev)
35 }
36
37 /// The given revision is that of the file as found in a manifest, not of a
38 /// changeset.
39 pub fn get_rev(
40 &self,
41 file_rev: Revision,
42 ) -> Result<FilelogEntry, RevlogError> {
43 let data = self.revlog.get_rev_data(file_rev)?;
44 Ok(FilelogEntry(data.into()))
45 }
46 }
47
48 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
49 let encoded_bytes =
50 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
51 get_path_from_bytes(&encoded_bytes).into()
52 }
53
54 pub struct FilelogEntry<'filelog>(Cow<'filelog, [u8]>);
55
56 impl<'filelog> FilelogEntry<'filelog> {
57 /// Split into metadata and data
58 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
59 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
60
61 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
62 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
63 Ok((Some(metadata), data))
64 } else {
65 Err(HgError::corrupted(
66 "Missing metadata end delimiter in filelog entry",
67 ))
68 }
69 } else {
70 Ok((None, &self.0))
71 }
72 }
73
74 /// Returns the file contents at this revision, stripped of any metadata
75 pub fn data(&self) -> Result<&[u8], HgError> {
76 let (_metadata, data) = self.split()?;
77 Ok(data)
78 }
79 }
@@ -1,99 +1,71 b''
1 // list_tracked_files.rs
1 // list_tracked_files.rs
2 //
2 //
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
3 // Copyright 2020 Antoine Cezar <antoine.cezar@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 use std::path::PathBuf;
9
10 use crate::repo::Repo;
8 use crate::repo::Repo;
11 use crate::revlog::path_encode::path_encode;
12 use crate::revlog::revlog::Revlog;
13 use crate::revlog::revlog::RevlogError;
9 use crate::revlog::revlog::RevlogError;
14 use crate::revlog::Node;
10 use crate::revlog::Node;
15 use crate::utils::files::get_path_from_bytes;
11
16 use crate::utils::hg_path::{HgPath, HgPathBuf};
12 use crate::utils::hg_path::HgPathBuf;
17
13
18 pub struct CatOutput {
14 pub struct CatOutput {
19 /// Whether any file in the manifest matched the paths given as CLI
15 /// Whether any file in the manifest matched the paths given as CLI
20 /// arguments
16 /// arguments
21 pub found_any: bool,
17 pub found_any: bool,
22 /// The contents of matching files, in manifest order
18 /// The contents of matching files, in manifest order
23 pub concatenated: Vec<u8>,
19 pub concatenated: Vec<u8>,
24 /// Which of the CLI arguments did not match any manifest file
20 /// Which of the CLI arguments did not match any manifest file
25 pub missing: Vec<HgPathBuf>,
21 pub missing: Vec<HgPathBuf>,
26 /// The node ID that the given revset was resolved to
22 /// The node ID that the given revset was resolved to
27 pub node: Node,
23 pub node: Node,
28 }
24 }
29
25
30 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
31
32 /// Output the given revision of files
26 /// Output the given revision of files
33 ///
27 ///
34 /// * `root`: Repository root
28 /// * `root`: Repository root
35 /// * `rev`: The revision to cat the files from.
29 /// * `rev`: The revision to cat the files from.
36 /// * `files`: The files to output.
30 /// * `files`: The files to output.
37 pub fn cat<'a>(
31 pub fn cat<'a>(
38 repo: &Repo,
32 repo: &Repo,
39 revset: &str,
33 revset: &str,
40 files: &'a [HgPathBuf],
34 files: &'a [HgPathBuf],
41 ) -> Result<CatOutput, RevlogError> {
35 ) -> Result<CatOutput, RevlogError> {
42 let rev = crate::revset::resolve_single(revset, repo)?;
36 let rev = crate::revset::resolve_single(revset, repo)?;
43 let manifest = repo.manifest(rev)?;
37 let manifest = repo.manifest(rev)?;
44 let node = *repo
38 let node = *repo
45 .changelog()?
39 .changelog()?
46 .node_from_rev(rev)
40 .node_from_rev(rev)
47 .expect("should succeed when repo.manifest did");
41 .expect("should succeed when repo.manifest did");
48 let mut bytes = vec![];
42 let mut bytes = vec![];
49 let mut matched = vec![false; files.len()];
43 let mut matched = vec![false; files.len()];
50 let mut found_any = false;
44 let mut found_any = false;
51
45
52 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
46 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
53 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
47 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
54 if cat_file.as_bytes() == manifest_file.as_bytes() {
48 if cat_file.as_bytes() == manifest_file.as_bytes() {
55 *is_matched = true;
49 *is_matched = true;
56 found_any = true;
50 found_any = true;
57 let index_path = store_path(manifest_file, b".i");
51 let file_log = repo.filelog(manifest_file)?;
58 let data_path = store_path(manifest_file, b".d");
59
60 let file_log =
61 Revlog::open(repo, &index_path, Some(&data_path))?;
62 let file_node = Node::from_hex_for_repo(node_bytes)?;
52 let file_node = Node::from_hex_for_repo(node_bytes)?;
63 let file_rev = file_log.get_node_rev(file_node.into())?;
53 let entry = file_log.get_node(file_node)?;
64 let data = file_log.get_rev_data(file_rev)?;
54 bytes.extend(entry.data()?)
65 if data.starts_with(&METADATA_DELIMITER) {
66 let end_delimiter_position = data
67 [METADATA_DELIMITER.len()..]
68 .windows(METADATA_DELIMITER.len())
69 .position(|bytes| bytes == METADATA_DELIMITER);
70 if let Some(position) = end_delimiter_position {
71 let offset = METADATA_DELIMITER.len() * 2;
72 bytes.extend(data[position + offset..].iter());
73 }
74 } else {
75 bytes.extend(data);
76 }
77 }
55 }
78 }
56 }
79 }
57 }
80
58
81 let missing: Vec<_> = files
59 let missing: Vec<_> = files
82 .iter()
60 .iter()
83 .zip(&matched)
61 .zip(&matched)
84 .filter(|pair| !*pair.1)
62 .filter(|pair| !*pair.1)
85 .map(|pair| pair.0.clone())
63 .map(|pair| pair.0.clone())
86 .collect();
64 .collect();
87 Ok(CatOutput {
65 Ok(CatOutput {
88 found_any,
66 found_any,
89 concatenated: bytes,
67 concatenated: bytes,
90 missing,
68 missing,
91 node,
69 node,
92 })
70 })
93 }
71 }
94
95 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
96 let encoded_bytes =
97 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
98 get_path_from_bytes(&encoded_bytes).into()
99 }
@@ -1,403 +1,409 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::manifest::{Manifest, Manifestlog};
8 use crate::manifest::{Manifest, Manifestlog};
9 use crate::requirements;
9 use crate::requirements;
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
11 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
12 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
13 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
14 use crate::{exit_codes, Node};
16 use crate::{exit_codes, Node};
15 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
16 use std::cell::{Cell, Ref, RefCell, RefMut};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
17 use std::collections::HashSet;
19 use std::collections::HashSet;
18 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
19
21
20 /// A repository on disk
22 /// A repository on disk
21 pub struct Repo {
23 pub struct Repo {
22 working_directory: PathBuf,
24 working_directory: PathBuf,
23 dot_hg: PathBuf,
25 dot_hg: PathBuf,
24 store: PathBuf,
26 store: PathBuf,
25 requirements: HashSet<String>,
27 requirements: HashSet<String>,
26 config: Config,
28 config: Config,
27 // None means not known/initialized yet
29 // None means not known/initialized yet
28 dirstate_parents: Cell<Option<DirstateParents>>,
30 dirstate_parents: Cell<Option<DirstateParents>>,
29 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
30 changelog: LazyCell<Changelog, RevlogError>,
32 changelog: LazyCell<Changelog, RevlogError>,
31 manifestlog: LazyCell<Manifestlog, RevlogError>,
33 manifestlog: LazyCell<Manifestlog, RevlogError>,
32 }
34 }
33
35
34 #[derive(Debug, derive_more::From)]
36 #[derive(Debug, derive_more::From)]
35 pub enum RepoError {
37 pub enum RepoError {
36 NotFound {
38 NotFound {
37 at: PathBuf,
39 at: PathBuf,
38 },
40 },
39 #[from]
41 #[from]
40 ConfigParseError(ConfigParseError),
42 ConfigParseError(ConfigParseError),
41 #[from]
43 #[from]
42 Other(HgError),
44 Other(HgError),
43 }
45 }
44
46
45 impl From<ConfigError> for RepoError {
47 impl From<ConfigError> for RepoError {
46 fn from(error: ConfigError) -> Self {
48 fn from(error: ConfigError) -> Self {
47 match error {
49 match error {
48 ConfigError::Parse(error) => error.into(),
50 ConfigError::Parse(error) => error.into(),
49 ConfigError::Other(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
50 }
52 }
51 }
53 }
52 }
54 }
53
55
54 impl Repo {
56 impl Repo {
55 /// tries to find nearest repository root in current working directory or
57 /// tries to find nearest repository root in current working directory or
56 /// its ancestors
58 /// its ancestors
57 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
58 let current_directory = crate::utils::current_dir()?;
60 let current_directory = crate::utils::current_dir()?;
59 // ancestors() is inclusive: it first yields `current_directory`
61 // ancestors() is inclusive: it first yields `current_directory`
60 // as-is.
62 // as-is.
61 for ancestor in current_directory.ancestors() {
63 for ancestor in current_directory.ancestors() {
62 if is_dir(ancestor.join(".hg"))? {
64 if is_dir(ancestor.join(".hg"))? {
63 return Ok(ancestor.to_path_buf());
65 return Ok(ancestor.to_path_buf());
64 }
66 }
65 }
67 }
66 return Err(RepoError::NotFound {
68 return Err(RepoError::NotFound {
67 at: current_directory,
69 at: current_directory,
68 });
70 });
69 }
71 }
70
72
71 /// Find a repository, either at the given path (which must contain a `.hg`
73 /// Find a repository, either at the given path (which must contain a `.hg`
72 /// sub-directory) or by searching the current directory and its
74 /// sub-directory) or by searching the current directory and its
73 /// ancestors.
75 /// ancestors.
74 ///
76 ///
75 /// A method with two very different "modes" like this usually a code smell
77 /// A method with two very different "modes" like this usually a code smell
76 /// to make two methods instead, but in this case an `Option` is what rhg
78 /// to make two methods instead, but in this case an `Option` is what rhg
77 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
78 /// Having two methods would just move that `if` to almost all callers.
80 /// Having two methods would just move that `if` to almost all callers.
79 pub fn find(
81 pub fn find(
80 config: &Config,
82 config: &Config,
81 explicit_path: Option<PathBuf>,
83 explicit_path: Option<PathBuf>,
82 ) -> Result<Self, RepoError> {
84 ) -> Result<Self, RepoError> {
83 if let Some(root) = explicit_path {
85 if let Some(root) = explicit_path {
84 if is_dir(root.join(".hg"))? {
86 if is_dir(root.join(".hg"))? {
85 Self::new_at_path(root.to_owned(), config)
87 Self::new_at_path(root.to_owned(), config)
86 } else if is_file(&root)? {
88 } else if is_file(&root)? {
87 Err(HgError::unsupported("bundle repository").into())
89 Err(HgError::unsupported("bundle repository").into())
88 } else {
90 } else {
89 Err(RepoError::NotFound {
91 Err(RepoError::NotFound {
90 at: root.to_owned(),
92 at: root.to_owned(),
91 })
93 })
92 }
94 }
93 } else {
95 } else {
94 let root = Self::find_repo_root()?;
96 let root = Self::find_repo_root()?;
95 Self::new_at_path(root, config)
97 Self::new_at_path(root, config)
96 }
98 }
97 }
99 }
98
100
99 /// To be called after checking that `.hg` is a sub-directory
101 /// To be called after checking that `.hg` is a sub-directory
100 fn new_at_path(
102 fn new_at_path(
101 working_directory: PathBuf,
103 working_directory: PathBuf,
102 config: &Config,
104 config: &Config,
103 ) -> Result<Self, RepoError> {
105 ) -> Result<Self, RepoError> {
104 let dot_hg = working_directory.join(".hg");
106 let dot_hg = working_directory.join(".hg");
105
107
106 let mut repo_config_files = Vec::new();
108 let mut repo_config_files = Vec::new();
107 repo_config_files.push(dot_hg.join("hgrc"));
109 repo_config_files.push(dot_hg.join("hgrc"));
108 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
109
111
110 let hg_vfs = Vfs { base: &dot_hg };
112 let hg_vfs = Vfs { base: &dot_hg };
111 let mut reqs = requirements::load_if_exists(hg_vfs)?;
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
112 let relative =
114 let relative =
113 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
114 let shared =
116 let shared =
115 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
116
118
117 // From `mercurial/localrepo.py`:
119 // From `mercurial/localrepo.py`:
118 //
120 //
119 // if .hg/requires contains the sharesafe requirement, it means
121 // if .hg/requires contains the sharesafe requirement, it means
120 // there exists a `.hg/store/requires` too and we should read it
122 // there exists a `.hg/store/requires` too and we should read it
121 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
122 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
123 // is not present, refer checkrequirementscompat() for that
125 // is not present, refer checkrequirementscompat() for that
124 //
126 //
125 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
126 // repository was shared the old way. We check the share source
128 // repository was shared the old way. We check the share source
127 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
128 // current repository needs to be reshared
130 // current repository needs to be reshared
129 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
130
132
131 let store_path;
133 let store_path;
132 if !shared {
134 if !shared {
133 store_path = dot_hg.join("store");
135 store_path = dot_hg.join("store");
134 } else {
136 } else {
135 let bytes = hg_vfs.read("sharedpath")?;
137 let bytes = hg_vfs.read("sharedpath")?;
136 let mut shared_path =
138 let mut shared_path =
137 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
138 .to_owned();
140 .to_owned();
139 if relative {
141 if relative {
140 shared_path = dot_hg.join(shared_path)
142 shared_path = dot_hg.join(shared_path)
141 }
143 }
142 if !is_dir(&shared_path)? {
144 if !is_dir(&shared_path)? {
143 return Err(HgError::corrupted(format!(
145 return Err(HgError::corrupted(format!(
144 ".hg/sharedpath points to nonexistent directory {}",
146 ".hg/sharedpath points to nonexistent directory {}",
145 shared_path.display()
147 shared_path.display()
146 ))
148 ))
147 .into());
149 .into());
148 }
150 }
149
151
150 store_path = shared_path.join("store");
152 store_path = shared_path.join("store");
151
153
152 let source_is_share_safe =
154 let source_is_share_safe =
153 requirements::load(Vfs { base: &shared_path })?
155 requirements::load(Vfs { base: &shared_path })?
154 .contains(requirements::SHARESAFE_REQUIREMENT);
156 .contains(requirements::SHARESAFE_REQUIREMENT);
155
157
156 if share_safe && !source_is_share_safe {
158 if share_safe && !source_is_share_safe {
157 return Err(match config
159 return Err(match config
158 .get(b"share", b"safe-mismatch.source-not-safe")
160 .get(b"share", b"safe-mismatch.source-not-safe")
159 {
161 {
160 Some(b"abort") | None => HgError::abort(
162 Some(b"abort") | None => HgError::abort(
161 "abort: share source does not support share-safe requirement\n\
163 "abort: share source does not support share-safe requirement\n\
162 (see `hg help config.format.use-share-safe` for more information)",
164 (see `hg help config.format.use-share-safe` for more information)",
163 exit_codes::ABORT,
165 exit_codes::ABORT,
164 ),
166 ),
165 _ => HgError::unsupported("share-safe downgrade"),
167 _ => HgError::unsupported("share-safe downgrade"),
166 }
168 }
167 .into());
169 .into());
168 } else if source_is_share_safe && !share_safe {
170 } else if source_is_share_safe && !share_safe {
169 return Err(
171 return Err(
170 match config.get(b"share", b"safe-mismatch.source-safe") {
172 match config.get(b"share", b"safe-mismatch.source-safe") {
171 Some(b"abort") | None => HgError::abort(
173 Some(b"abort") | None => HgError::abort(
172 "abort: version mismatch: source uses share-safe \
174 "abort: version mismatch: source uses share-safe \
173 functionality while the current share does not\n\
175 functionality while the current share does not\n\
174 (see `hg help config.format.use-share-safe` for more information)",
176 (see `hg help config.format.use-share-safe` for more information)",
175 exit_codes::ABORT,
177 exit_codes::ABORT,
176 ),
178 ),
177 _ => HgError::unsupported("share-safe upgrade"),
179 _ => HgError::unsupported("share-safe upgrade"),
178 }
180 }
179 .into(),
181 .into(),
180 );
182 );
181 }
183 }
182
184
183 if share_safe {
185 if share_safe {
184 repo_config_files.insert(0, shared_path.join("hgrc"))
186 repo_config_files.insert(0, shared_path.join("hgrc"))
185 }
187 }
186 }
188 }
187 if share_safe {
189 if share_safe {
188 reqs.extend(requirements::load(Vfs { base: &store_path })?);
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
189 }
191 }
190
192
191 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
192 config.combine_with_repo(&repo_config_files)?
194 config.combine_with_repo(&repo_config_files)?
193 } else {
195 } else {
194 config.clone()
196 config.clone()
195 };
197 };
196
198
197 let repo = Self {
199 let repo = Self {
198 requirements: reqs,
200 requirements: reqs,
199 working_directory,
201 working_directory,
200 store: store_path,
202 store: store_path,
201 dot_hg,
203 dot_hg,
202 config: repo_config,
204 config: repo_config,
203 dirstate_parents: Cell::new(None),
205 dirstate_parents: Cell::new(None),
204 dirstate_map: LazyCell::new(Self::new_dirstate_map),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
205 changelog: LazyCell::new(Changelog::open),
207 changelog: LazyCell::new(Changelog::open),
206 manifestlog: LazyCell::new(Manifestlog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
207 };
209 };
208
210
209 requirements::check(&repo)?;
211 requirements::check(&repo)?;
210
212
211 Ok(repo)
213 Ok(repo)
212 }
214 }
213
215
214 pub fn working_directory_path(&self) -> &Path {
216 pub fn working_directory_path(&self) -> &Path {
215 &self.working_directory
217 &self.working_directory
216 }
218 }
217
219
218 pub fn requirements(&self) -> &HashSet<String> {
220 pub fn requirements(&self) -> &HashSet<String> {
219 &self.requirements
221 &self.requirements
220 }
222 }
221
223
222 pub fn config(&self) -> &Config {
224 pub fn config(&self) -> &Config {
223 &self.config
225 &self.config
224 }
226 }
225
227
226 /// For accessing repository files (in `.hg`), except for the store
228 /// For accessing repository files (in `.hg`), except for the store
227 /// (`.hg/store`).
229 /// (`.hg/store`).
228 pub fn hg_vfs(&self) -> Vfs<'_> {
230 pub fn hg_vfs(&self) -> Vfs<'_> {
229 Vfs { base: &self.dot_hg }
231 Vfs { base: &self.dot_hg }
230 }
232 }
231
233
232 /// For accessing repository store files (in `.hg/store`)
234 /// For accessing repository store files (in `.hg/store`)
233 pub fn store_vfs(&self) -> Vfs<'_> {
235 pub fn store_vfs(&self) -> Vfs<'_> {
234 Vfs { base: &self.store }
236 Vfs { base: &self.store }
235 }
237 }
236
238
237 /// For accessing the working copy
239 /// For accessing the working copy
238 pub fn working_directory_vfs(&self) -> Vfs<'_> {
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
239 Vfs {
241 Vfs {
240 base: &self.working_directory,
242 base: &self.working_directory,
241 }
243 }
242 }
244 }
243
245
244 pub fn has_dirstate_v2(&self) -> bool {
246 pub fn has_dirstate_v2(&self) -> bool {
245 self.requirements
247 self.requirements
246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
247 }
249 }
248
250
249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
250 Ok(self
252 Ok(self
251 .hg_vfs()
253 .hg_vfs()
252 .read("dirstate")
254 .read("dirstate")
253 .io_not_found_as_none()?
255 .io_not_found_as_none()?
254 .unwrap_or(Vec::new()))
256 .unwrap_or(Vec::new()))
255 }
257 }
256
258
257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
258 if let Some(parents) = self.dirstate_parents.get() {
260 if let Some(parents) = self.dirstate_parents.get() {
259 return Ok(parents);
261 return Ok(parents);
260 }
262 }
261 let dirstate = self.dirstate_file_contents()?;
263 let dirstate = self.dirstate_file_contents()?;
262 let parents = if dirstate.is_empty() {
264 let parents = if dirstate.is_empty() {
263 DirstateParents::NULL
265 DirstateParents::NULL
264 } else if self.has_dirstate_v2() {
266 } else if self.has_dirstate_v2() {
265 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
266 } else {
268 } else {
267 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
268 .clone()
270 .clone()
269 };
271 };
270 self.dirstate_parents.set(Some(parents));
272 self.dirstate_parents.set(Some(parents));
271 Ok(parents)
273 Ok(parents)
272 }
274 }
273
275
274 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
275 let dirstate_file_contents = self.dirstate_file_contents()?;
277 let dirstate_file_contents = self.dirstate_file_contents()?;
276 if dirstate_file_contents.is_empty() {
278 if dirstate_file_contents.is_empty() {
277 self.dirstate_parents.set(Some(DirstateParents::NULL));
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
278 Ok(OwningDirstateMap::new_empty(Vec::new()))
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
279 } else if self.has_dirstate_v2() {
281 } else if self.has_dirstate_v2() {
280 let docket = crate::dirstate_tree::on_disk::read_docket(
282 let docket = crate::dirstate_tree::on_disk::read_docket(
281 &dirstate_file_contents,
283 &dirstate_file_contents,
282 )?;
284 )?;
283 self.dirstate_parents.set(Some(docket.parents()));
285 self.dirstate_parents.set(Some(docket.parents()));
284 let data_size = docket.data_size();
286 let data_size = docket.data_size();
285 let metadata = docket.tree_metadata();
287 let metadata = docket.tree_metadata();
286 let mut map = if let Some(data_mmap) = self
288 let mut map = if let Some(data_mmap) = self
287 .hg_vfs()
289 .hg_vfs()
288 .mmap_open(docket.data_filename())
290 .mmap_open(docket.data_filename())
289 .io_not_found_as_none()?
291 .io_not_found_as_none()?
290 {
292 {
291 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
293 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
292 } else {
294 } else {
293 OwningDirstateMap::new_empty(Vec::new())
295 OwningDirstateMap::new_empty(Vec::new())
294 };
296 };
295 let (on_disk, placeholder) = map.get_mut_pair();
297 let (on_disk, placeholder) = map.get_mut_pair();
296 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
297 Ok(map)
299 Ok(map)
298 } else {
300 } else {
299 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
300 let (on_disk, placeholder) = map.get_mut_pair();
302 let (on_disk, placeholder) = map.get_mut_pair();
301 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
302 self.dirstate_parents
304 self.dirstate_parents
303 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
304 *placeholder = inner;
306 *placeholder = inner;
305 Ok(map)
307 Ok(map)
306 }
308 }
307 }
309 }
308
310
309 pub fn dirstate_map(
311 pub fn dirstate_map(
310 &self,
312 &self,
311 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
312 self.dirstate_map.get_or_init(self)
314 self.dirstate_map.get_or_init(self)
313 }
315 }
314
316
315 pub fn dirstate_map_mut(
317 pub fn dirstate_map_mut(
316 &self,
318 &self,
317 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
318 self.dirstate_map.get_mut_or_init(self)
320 self.dirstate_map.get_mut_or_init(self)
319 }
321 }
320
322
321 pub fn changelog(&self) -> Result<Ref<Changelog>, RevlogError> {
323 pub fn changelog(&self) -> Result<Ref<Changelog>, RevlogError> {
322 self.changelog.get_or_init(self)
324 self.changelog.get_or_init(self)
323 }
325 }
324
326
325 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, RevlogError> {
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, RevlogError> {
326 self.changelog.get_mut_or_init(self)
328 self.changelog.get_mut_or_init(self)
327 }
329 }
328
330
329 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, RevlogError> {
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, RevlogError> {
330 self.manifestlog.get_or_init(self)
332 self.manifestlog.get_or_init(self)
331 }
333 }
332
334
333 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, RevlogError> {
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, RevlogError> {
334 self.manifestlog.get_mut_or_init(self)
336 self.manifestlog.get_mut_or_init(self)
335 }
337 }
336
338
337 /// Returns the manifest of the given revision
339 /// Returns the manifest of the given revision
338 pub fn manifest(
340 pub fn manifest(
339 &self,
341 &self,
340 revision: Revision,
342 revision: Revision,
341 ) -> Result<Manifest, RevlogError> {
343 ) -> Result<Manifest, RevlogError> {
342 let changelog = self.changelog()?;
344 let changelog = self.changelog()?;
343 let manifest = self.manifestlog()?;
345 let manifest = self.manifestlog()?;
344 let changelog_entry = changelog.get_rev(revision)?;
346 let changelog_entry = changelog.get_rev(revision)?;
345 let manifest_node =
347 let manifest_node =
346 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
348 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
347 manifest.get_node(manifest_node.into())
349 manifest.get_node(manifest_node.into())
348 }
350 }
351
352 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, RevlogError> {
353 Filelog::open(self, path)
354 }
349 }
355 }
350
356
351 /// Lazily-initialized component of `Repo` with interior mutability
357 /// Lazily-initialized component of `Repo` with interior mutability
352 ///
358 ///
353 /// This differs from `OnceCell` in that the value can still be "deinitialized"
359 /// This differs from `OnceCell` in that the value can still be "deinitialized"
354 /// later by setting its inner `Option` to `None`.
360 /// later by setting its inner `Option` to `None`.
355 struct LazyCell<T, E> {
361 struct LazyCell<T, E> {
356 value: RefCell<Option<T>>,
362 value: RefCell<Option<T>>,
357 // `Fn`s that don’t capture environment are zero-size, so this box does
363 // `Fn`s that don’t capture environment are zero-size, so this box does
358 // not allocate:
364 // not allocate:
359 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
365 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
360 }
366 }
361
367
362 impl<T, E> LazyCell<T, E> {
368 impl<T, E> LazyCell<T, E> {
363 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
369 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
364 Self {
370 Self {
365 value: RefCell::new(None),
371 value: RefCell::new(None),
366 init: Box::new(init),
372 init: Box::new(init),
367 }
373 }
368 }
374 }
369
375
370 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
376 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
371 let mut borrowed = self.value.borrow();
377 let mut borrowed = self.value.borrow();
372 if borrowed.is_none() {
378 if borrowed.is_none() {
373 drop(borrowed);
379 drop(borrowed);
374 // Only use `borrow_mut` if it is really needed to avoid panic in
380 // Only use `borrow_mut` if it is really needed to avoid panic in
375 // case there is another outstanding borrow but mutation is not
381 // case there is another outstanding borrow but mutation is not
376 // needed.
382 // needed.
377 *self.value.borrow_mut() = Some((self.init)(repo)?);
383 *self.value.borrow_mut() = Some((self.init)(repo)?);
378 borrowed = self.value.borrow()
384 borrowed = self.value.borrow()
379 }
385 }
380 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
386 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
381 }
387 }
382
388
383 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
389 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
384 let mut borrowed = self.value.borrow_mut();
390 let mut borrowed = self.value.borrow_mut();
385 if borrowed.is_none() {
391 if borrowed.is_none() {
386 *borrowed = Some((self.init)(repo)?);
392 *borrowed = Some((self.init)(repo)?);
387 }
393 }
388 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
394 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
389 }
395 }
390 }
396 }
391
397
392 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
398 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
393 struct MmapWrapper(memmap2::Mmap);
399 struct MmapWrapper(memmap2::Mmap);
394
400
395 impl std::ops::Deref for MmapWrapper {
401 impl std::ops::Deref for MmapWrapper {
396 type Target = [u8];
402 type Target = [u8];
397
403
398 fn deref(&self) -> &[u8] {
404 fn deref(&self) -> &[u8] {
399 self.0.deref()
405 self.0.deref()
400 }
406 }
401 }
407 }
402
408
403 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
409 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
@@ -1,71 +1,72 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 //! Mercurial concepts for handling revision history
6 //! Mercurial concepts for handling revision history
7
7
8 pub mod node;
8 pub mod node;
9 pub mod nodemap;
9 pub mod nodemap;
10 mod nodemap_docket;
10 mod nodemap_docket;
11 pub mod path_encode;
11 pub mod path_encode;
12 pub use node::{FromHexError, Node, NodePrefix};
12 pub use node::{FromHexError, Node, NodePrefix};
13 pub mod changelog;
13 pub mod changelog;
14 pub mod filelog;
14 pub mod index;
15 pub mod index;
15 pub mod manifest;
16 pub mod manifest;
16 pub mod patch;
17 pub mod patch;
17 pub mod revlog;
18 pub mod revlog;
18
19
19 /// Mercurial revision numbers
20 /// Mercurial revision numbers
20 ///
21 ///
21 /// As noted in revlog.c, revision numbers are actually encoded in
22 /// As noted in revlog.c, revision numbers are actually encoded in
22 /// 4 bytes, and are liberally converted to ints, whence the i32
23 /// 4 bytes, and are liberally converted to ints, whence the i32
23 pub type Revision = i32;
24 pub type Revision = i32;
24
25
25 /// Marker expressing the absence of a parent
26 /// Marker expressing the absence of a parent
26 ///
27 ///
27 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
28 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
28 /// to be smaller than all existing revisions.
29 /// to be smaller than all existing revisions.
29 pub const NULL_REVISION: Revision = -1;
30 pub const NULL_REVISION: Revision = -1;
30
31
31 /// Same as `mercurial.node.wdirrev`
32 /// Same as `mercurial.node.wdirrev`
32 ///
33 ///
33 /// This is also equal to `i32::max_value()`, but it's better to spell
34 /// This is also equal to `i32::max_value()`, but it's better to spell
34 /// it out explicitely, same as in `mercurial.node`
35 /// it out explicitely, same as in `mercurial.node`
35 #[allow(clippy::unreadable_literal)]
36 #[allow(clippy::unreadable_literal)]
36 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
37 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
37
38
38 pub const WORKING_DIRECTORY_HEX: &str =
39 pub const WORKING_DIRECTORY_HEX: &str =
39 "ffffffffffffffffffffffffffffffffffffffff";
40 "ffffffffffffffffffffffffffffffffffffffff";
40
41
41 /// The simplest expression of what we need of Mercurial DAGs.
42 /// The simplest expression of what we need of Mercurial DAGs.
42 pub trait Graph {
43 pub trait Graph {
43 /// Return the two parents of the given `Revision`.
44 /// Return the two parents of the given `Revision`.
44 ///
45 ///
45 /// Each of the parents can be independently `NULL_REVISION`
46 /// Each of the parents can be independently `NULL_REVISION`
46 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
47 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
47 }
48 }
48
49
49 #[derive(Clone, Debug, PartialEq)]
50 #[derive(Clone, Debug, PartialEq)]
50 pub enum GraphError {
51 pub enum GraphError {
51 ParentOutOfRange(Revision),
52 ParentOutOfRange(Revision),
52 WorkingDirectoryUnsupported,
53 WorkingDirectoryUnsupported,
53 }
54 }
54
55
55 /// The Mercurial Revlog Index
56 /// The Mercurial Revlog Index
56 ///
57 ///
57 /// This is currently limited to the minimal interface that is needed for
58 /// This is currently limited to the minimal interface that is needed for
58 /// the [`nodemap`](nodemap/index.html) module
59 /// the [`nodemap`](nodemap/index.html) module
59 pub trait RevlogIndex {
60 pub trait RevlogIndex {
60 /// Total number of Revisions referenced in this index
61 /// Total number of Revisions referenced in this index
61 fn len(&self) -> usize;
62 fn len(&self) -> usize;
62
63
63 fn is_empty(&self) -> bool {
64 fn is_empty(&self) -> bool {
64 self.len() == 0
65 self.len() == 0
65 }
66 }
66
67
67 /// Return a reference to the Node or `None` if rev is out of bounds
68 /// Return a reference to the Node or `None` if rev is out of bounds
68 ///
69 ///
69 /// `NULL_REVISION` is not considered to be out of bounds.
70 /// `NULL_REVISION` is not considered to be out of bounds.
70 fn node(&self, rev: Revision) -> Option<&Node>;
71 fn node(&self, rev: Revision) -> Option<&Node>;
71 }
72 }
@@ -1,481 +1,490 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 }
78 }
78
79
79 impl SliceExt for [u8] {
80 impl SliceExt for [u8] {
80 fn trim_end(&self) -> &[u8] {
81 fn trim_end(&self) -> &[u8] {
81 self.trim_end_matches(|byte| byte.is_ascii_whitespace())
82 self.trim_end_matches(|byte| byte.is_ascii_whitespace())
82 }
83 }
83
84
84 fn trim_start(&self) -> &[u8] {
85 fn trim_start(&self) -> &[u8] {
85 self.trim_start_matches(|byte| byte.is_ascii_whitespace())
86 self.trim_start_matches(|byte| byte.is_ascii_whitespace())
86 }
87 }
87
88
88 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 {
89 if let Some(last) = self.iter().rposition(|&byte| !f(byte)) {
90 if let Some(last) = self.iter().rposition(|&byte| !f(byte)) {
90 &self[..=last]
91 &self[..=last]
91 } else {
92 } else {
92 &[]
93 &[]
93 }
94 }
94 }
95 }
95
96
96 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 {
97 if let Some(first) = self.iter().position(|&byte| !f(byte)) {
98 if let Some(first) = self.iter().position(|&byte| !f(byte)) {
98 &self[first..]
99 &self[first..]
99 } else {
100 } else {
100 &[]
101 &[]
101 }
102 }
102 }
103 }
103
104
104 /// ```
105 /// ```
105 /// use hg::utils::SliceExt;
106 /// use hg::utils::SliceExt;
106 /// assert_eq!(
107 /// assert_eq!(
107 /// b" to trim ".trim(),
108 /// b" to trim ".trim(),
108 /// b"to trim"
109 /// b"to trim"
109 /// );
110 /// );
110 /// assert_eq!(
111 /// assert_eq!(
111 /// b"to trim ".trim(),
112 /// b"to trim ".trim(),
112 /// b"to trim"
113 /// b"to trim"
113 /// );
114 /// );
114 /// assert_eq!(
115 /// assert_eq!(
115 /// b" to trim".trim(),
116 /// b" to trim".trim(),
116 /// b"to trim"
117 /// b"to trim"
117 /// );
118 /// );
118 /// ```
119 /// ```
119 fn trim(&self) -> &[u8] {
120 fn trim(&self) -> &[u8] {
120 self.trim_start().trim_end()
121 self.trim_start().trim_end()
121 }
122 }
122
123
123 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
124 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
124 if self.starts_with(needle) {
125 if self.starts_with(needle) {
125 Some(&self[needle.len()..])
126 Some(&self[needle.len()..])
126 } else {
127 } else {
127 None
128 None
128 }
129 }
129 }
130 }
130
131
131 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
132 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
132 let mut iter = self.splitn(2, |&byte| byte == separator);
133 let mut iter = self.splitn(2, |&byte| byte == separator);
133 let a = iter.next()?;
134 let a = iter.next()?;
134 let b = iter.next()?;
135 let b = iter.next()?;
135 Some((a, b))
136 Some((a, b))
136 }
137 }
138
139 fn split_2_by_slice(&self, separator: &[u8]) -> Option<(&[u8], &[u8])> {
140 if let Some(pos) = find_slice_in_slice(self, separator) {
141 Some((&self[..pos], &self[pos + separator.len()..]))
142 } else {
143 None
144 }
145 }
137 }
146 }
138
147
139 pub trait Escaped {
148 pub trait Escaped {
140 /// Return bytes escaped for display to the user
149 /// Return bytes escaped for display to the user
141 fn escaped_bytes(&self) -> Vec<u8>;
150 fn escaped_bytes(&self) -> Vec<u8>;
142 }
151 }
143
152
144 impl Escaped for u8 {
153 impl Escaped for u8 {
145 fn escaped_bytes(&self) -> Vec<u8> {
154 fn escaped_bytes(&self) -> Vec<u8> {
146 let mut acc = vec![];
155 let mut acc = vec![];
147 match self {
156 match self {
148 c @ b'\'' | c @ b'\\' => {
157 c @ b'\'' | c @ b'\\' => {
149 acc.push(b'\\');
158 acc.push(b'\\');
150 acc.push(*c);
159 acc.push(*c);
151 }
160 }
152 b'\t' => {
161 b'\t' => {
153 acc.extend(br"\\t");
162 acc.extend(br"\\t");
154 }
163 }
155 b'\n' => {
164 b'\n' => {
156 acc.extend(br"\\n");
165 acc.extend(br"\\n");
157 }
166 }
158 b'\r' => {
167 b'\r' => {
159 acc.extend(br"\\r");
168 acc.extend(br"\\r");
160 }
169 }
161 c if (*c < b' ' || *c >= 127) => {
170 c if (*c < b' ' || *c >= 127) => {
162 write!(acc, "\\x{:x}", self).unwrap();
171 write!(acc, "\\x{:x}", self).unwrap();
163 }
172 }
164 c => {
173 c => {
165 acc.push(*c);
174 acc.push(*c);
166 }
175 }
167 }
176 }
168 acc
177 acc
169 }
178 }
170 }
179 }
171
180
172 impl<'a, T: Escaped> Escaped for &'a [T] {
181 impl<'a, T: Escaped> Escaped for &'a [T] {
173 fn escaped_bytes(&self) -> Vec<u8> {
182 fn escaped_bytes(&self) -> Vec<u8> {
174 self.iter().flat_map(Escaped::escaped_bytes).collect()
183 self.iter().flat_map(Escaped::escaped_bytes).collect()
175 }
184 }
176 }
185 }
177
186
178 impl<T: Escaped> Escaped for Vec<T> {
187 impl<T: Escaped> Escaped for Vec<T> {
179 fn escaped_bytes(&self) -> Vec<u8> {
188 fn escaped_bytes(&self) -> Vec<u8> {
180 self.deref().escaped_bytes()
189 self.deref().escaped_bytes()
181 }
190 }
182 }
191 }
183
192
184 impl<'a> Escaped for &'a HgPath {
193 impl<'a> Escaped for &'a HgPath {
185 fn escaped_bytes(&self) -> Vec<u8> {
194 fn escaped_bytes(&self) -> Vec<u8> {
186 self.as_bytes().escaped_bytes()
195 self.as_bytes().escaped_bytes()
187 }
196 }
188 }
197 }
189
198
190 // TODO: use the str method when we require Rust 1.45
199 // TODO: use the str method when we require Rust 1.45
191 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
200 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
192 if s.ends_with(suffix) {
201 if s.ends_with(suffix) {
193 Some(&s[..s.len() - suffix.len()])
202 Some(&s[..s.len() - suffix.len()])
194 } else {
203 } else {
195 None
204 None
196 }
205 }
197 }
206 }
198
207
199 #[cfg(unix)]
208 #[cfg(unix)]
200 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
209 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
201 // TODO: Use the `matches!` macro when we require Rust 1.42+
210 // TODO: Use the `matches!` macro when we require Rust 1.42+
202 if value.iter().all(|&byte| match byte {
211 if value.iter().all(|&byte| match byte {
203 b'a'..=b'z'
212 b'a'..=b'z'
204 | b'A'..=b'Z'
213 | b'A'..=b'Z'
205 | b'0'..=b'9'
214 | b'0'..=b'9'
206 | b'.'
215 | b'.'
207 | b'_'
216 | b'_'
208 | b'/'
217 | b'/'
209 | b'+'
218 | b'+'
210 | b'-' => true,
219 | b'-' => true,
211 _ => false,
220 _ => false,
212 }) {
221 }) {
213 value.to_owned()
222 value.to_owned()
214 } else {
223 } else {
215 let mut quoted = Vec::with_capacity(value.len() + 2);
224 let mut quoted = Vec::with_capacity(value.len() + 2);
216 quoted.push(b'\'');
225 quoted.push(b'\'');
217 for &byte in value {
226 for &byte in value {
218 if byte == b'\'' {
227 if byte == b'\'' {
219 quoted.push(b'\\');
228 quoted.push(b'\\');
220 }
229 }
221 quoted.push(byte);
230 quoted.push(byte);
222 }
231 }
223 quoted.push(b'\'');
232 quoted.push(b'\'');
224 quoted
233 quoted
225 }
234 }
226 }
235 }
227
236
228 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
237 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
229 std::env::current_dir().map_err(|error| HgError::IoError {
238 std::env::current_dir().map_err(|error| HgError::IoError {
230 error,
239 error,
231 context: IoErrorContext::CurrentDir,
240 context: IoErrorContext::CurrentDir,
232 })
241 })
233 }
242 }
234
243
235 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
244 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
236 std::env::current_exe().map_err(|error| HgError::IoError {
245 std::env::current_exe().map_err(|error| HgError::IoError {
237 error,
246 error,
238 context: IoErrorContext::CurrentExe,
247 context: IoErrorContext::CurrentExe,
239 })
248 })
240 }
249 }
241
250
242 /// Expand `$FOO` and `${FOO}` environment variables in the given byte string
251 /// Expand `$FOO` and `${FOO}` environment variables in the given byte string
243 pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> {
252 pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> {
244 lazy_static::lazy_static! {
253 lazy_static::lazy_static! {
245 /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301
254 /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301
246 /// The `x` makes whitespace ignored.
255 /// The `x` makes whitespace ignored.
247 /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag.
256 /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag.
248 static ref VAR_RE: regex::bytes::Regex =
257 static ref VAR_RE: regex::bytes::Regex =
249 regex::bytes::Regex::new(r"(?x-u)
258 regex::bytes::Regex::new(r"(?x-u)
250 \$
259 \$
251 (?:
260 (?:
252 (\w+)
261 (\w+)
253 |
262 |
254 \{
263 \{
255 ([^}]*)
264 ([^}]*)
256 \}
265 \}
257 )
266 )
258 ").unwrap();
267 ").unwrap();
259 }
268 }
260 VAR_RE.replace_all(s, |captures: &regex::bytes::Captures| {
269 VAR_RE.replace_all(s, |captures: &regex::bytes::Captures| {
261 let var_name = files::get_os_str_from_bytes(
270 let var_name = files::get_os_str_from_bytes(
262 captures
271 captures
263 .get(1)
272 .get(1)
264 .or_else(|| captures.get(2))
273 .or_else(|| captures.get(2))
265 .expect("either side of `|` must participate in match")
274 .expect("either side of `|` must participate in match")
266 .as_bytes(),
275 .as_bytes(),
267 );
276 );
268 std::env::var_os(var_name)
277 std::env::var_os(var_name)
269 .map(files::get_bytes_from_os_str)
278 .map(files::get_bytes_from_os_str)
270 .unwrap_or_else(|| {
279 .unwrap_or_else(|| {
271 // Referencing an environment variable that does not exist.
280 // Referencing an environment variable that does not exist.
272 // Leave the $FOO reference as-is.
281 // Leave the $FOO reference as-is.
273 captures[0].to_owned()
282 captures[0].to_owned()
274 })
283 })
275 })
284 })
276 }
285 }
277
286
278 #[test]
287 #[test]
279 fn test_expand_vars() {
288 fn test_expand_vars() {
280 // Modifying process-global state in a test isn’t great,
289 // Modifying process-global state in a test isn’t great,
281 // but hopefully this won’t collide with anything.
290 // but hopefully this won’t collide with anything.
282 std::env::set_var("TEST_EXPAND_VAR", "1");
291 std::env::set_var("TEST_EXPAND_VAR", "1");
283 assert_eq!(
292 assert_eq!(
284 expand_vars(b"before/$TEST_EXPAND_VAR/after"),
293 expand_vars(b"before/$TEST_EXPAND_VAR/after"),
285 &b"before/1/after"[..]
294 &b"before/1/after"[..]
286 );
295 );
287 assert_eq!(
296 assert_eq!(
288 expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"),
297 expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"),
289 &b"before111after"[..]
298 &b"before111after"[..]
290 );
299 );
291 let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after";
300 let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after";
292 assert_eq!(expand_vars(s), &s[..]);
301 assert_eq!(expand_vars(s), &s[..]);
293 }
302 }
294
303
295 pub(crate) enum MergeResult<V> {
304 pub(crate) enum MergeResult<V> {
296 UseLeftValue,
305 UseLeftValue,
297 UseRightValue,
306 UseRightValue,
298 UseNewValue(V),
307 UseNewValue(V),
299 }
308 }
300
309
301 /// Return the union of the two given maps,
310 /// Return the union of the two given maps,
302 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
311 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
303 /// both.
312 /// both.
304 ///
313 ///
305 /// CC https://github.com/bodil/im-rs/issues/166
314 /// CC https://github.com/bodil/im-rs/issues/166
306 pub(crate) fn ordmap_union_with_merge<K, V>(
315 pub(crate) fn ordmap_union_with_merge<K, V>(
307 left: OrdMap<K, V>,
316 left: OrdMap<K, V>,
308 right: OrdMap<K, V>,
317 right: OrdMap<K, V>,
309 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
318 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
310 ) -> OrdMap<K, V>
319 ) -> OrdMap<K, V>
311 where
320 where
312 K: Clone + Ord,
321 K: Clone + Ord,
313 V: Clone + PartialEq,
322 V: Clone + PartialEq,
314 {
323 {
315 if left.ptr_eq(&right) {
324 if left.ptr_eq(&right) {
316 // One of the two maps is an unmodified clone of the other
325 // One of the two maps is an unmodified clone of the other
317 left
326 left
318 } else if left.len() / 2 > right.len() {
327 } else if left.len() / 2 > right.len() {
319 // When two maps have different sizes,
328 // When two maps have different sizes,
320 // their size difference is a lower bound on
329 // their size difference is a lower bound on
321 // how many keys of the larger map are not also in the smaller map.
330 // how many keys of the larger map are not also in the smaller map.
322 // This in turn is a lower bound on the number of differences in
331 // This in turn is a lower bound on the number of differences in
323 // `OrdMap::diff` and the "amount of work" that would be done
332 // `OrdMap::diff` and the "amount of work" that would be done
324 // by `ordmap_union_with_merge_by_diff`.
333 // by `ordmap_union_with_merge_by_diff`.
325 //
334 //
326 // Here `left` is more than twice the size of `right`,
335 // Here `left` is more than twice the size of `right`,
327 // so the number of differences is more than the total size of
336 // so the number of differences is more than the total size of
328 // `right`. Therefore an algorithm based on iterating `right`
337 // `right`. Therefore an algorithm based on iterating `right`
329 // is more efficient.
338 // is more efficient.
330 //
339 //
331 // This helps a lot when a tiny (or empty) map is merged
340 // This helps a lot when a tiny (or empty) map is merged
332 // with a large one.
341 // with a large one.
333 ordmap_union_with_merge_by_iter(left, right, merge)
342 ordmap_union_with_merge_by_iter(left, right, merge)
334 } else if left.len() < right.len() / 2 {
343 } else if left.len() < right.len() / 2 {
335 // Same as above but with `left` and `right` swapped
344 // Same as above but with `left` and `right` swapped
336 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
345 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
337 // Also swapped in `merge` arguments:
346 // Also swapped in `merge` arguments:
338 match merge(key, b, a) {
347 match merge(key, b, a) {
339 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
348 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
340 // … and swap back in `merge` result:
349 // … and swap back in `merge` result:
341 MergeResult::UseLeftValue => MergeResult::UseRightValue,
350 MergeResult::UseLeftValue => MergeResult::UseRightValue,
342 MergeResult::UseRightValue => MergeResult::UseLeftValue,
351 MergeResult::UseRightValue => MergeResult::UseLeftValue,
343 }
352 }
344 })
353 })
345 } else {
354 } else {
346 // For maps of similar size, use the algorithm based on `OrdMap::diff`
355 // For maps of similar size, use the algorithm based on `OrdMap::diff`
347 ordmap_union_with_merge_by_diff(left, right, merge)
356 ordmap_union_with_merge_by_diff(left, right, merge)
348 }
357 }
349 }
358 }
350
359
351 /// Efficient if `right` is much smaller than `left`
360 /// Efficient if `right` is much smaller than `left`
352 fn ordmap_union_with_merge_by_iter<K, V>(
361 fn ordmap_union_with_merge_by_iter<K, V>(
353 mut left: OrdMap<K, V>,
362 mut left: OrdMap<K, V>,
354 right: OrdMap<K, V>,
363 right: OrdMap<K, V>,
355 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
364 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
356 ) -> OrdMap<K, V>
365 ) -> OrdMap<K, V>
357 where
366 where
358 K: Clone + Ord,
367 K: Clone + Ord,
359 V: Clone,
368 V: Clone,
360 {
369 {
361 for (key, right_value) in right {
370 for (key, right_value) in right {
362 match left.get(&key) {
371 match left.get(&key) {
363 None => {
372 None => {
364 left.insert(key, right_value);
373 left.insert(key, right_value);
365 }
374 }
366 Some(left_value) => match merge(&key, left_value, &right_value) {
375 Some(left_value) => match merge(&key, left_value, &right_value) {
367 MergeResult::UseLeftValue => {}
376 MergeResult::UseLeftValue => {}
368 MergeResult::UseRightValue => {
377 MergeResult::UseRightValue => {
369 left.insert(key, right_value);
378 left.insert(key, right_value);
370 }
379 }
371 MergeResult::UseNewValue(new_value) => {
380 MergeResult::UseNewValue(new_value) => {
372 left.insert(key, new_value);
381 left.insert(key, new_value);
373 }
382 }
374 },
383 },
375 }
384 }
376 }
385 }
377 left
386 left
378 }
387 }
379
388
380 /// Fallback when both maps are of similar size
389 /// Fallback when both maps are of similar size
381 fn ordmap_union_with_merge_by_diff<K, V>(
390 fn ordmap_union_with_merge_by_diff<K, V>(
382 mut left: OrdMap<K, V>,
391 mut left: OrdMap<K, V>,
383 mut right: OrdMap<K, V>,
392 mut right: OrdMap<K, V>,
384 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
393 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
385 ) -> OrdMap<K, V>
394 ) -> OrdMap<K, V>
386 where
395 where
387 K: Clone + Ord,
396 K: Clone + Ord,
388 V: Clone + PartialEq,
397 V: Clone + PartialEq,
389 {
398 {
390 // (key, value) pairs that would need to be inserted in either map
399 // (key, value) pairs that would need to be inserted in either map
391 // in order to turn it into the union.
400 // in order to turn it into the union.
392 //
401 //
393 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
402 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
394 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
403 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
395 // with `left_updates` only borrowing from `right` and `right_updates` from
404 // with `left_updates` only borrowing from `right` and `right_updates` from
396 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
405 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
397 //
406 //
398 // This would allow moving all `.clone()` calls to after we’ve decided
407 // This would allow moving all `.clone()` calls to after we’ve decided
399 // which of `right_updates` or `left_updates` to use
408 // which of `right_updates` or `left_updates` to use
400 // (value ones becoming `Cow::into_owned`),
409 // (value ones becoming `Cow::into_owned`),
401 // and avoid making clones we don’t end up using.
410 // and avoid making clones we don’t end up using.
402 let mut left_updates = Vec::new();
411 let mut left_updates = Vec::new();
403 let mut right_updates = Vec::new();
412 let mut right_updates = Vec::new();
404
413
405 for difference in left.diff(&right) {
414 for difference in left.diff(&right) {
406 match difference {
415 match difference {
407 DiffItem::Add(key, value) => {
416 DiffItem::Add(key, value) => {
408 left_updates.push((key.clone(), value.clone()))
417 left_updates.push((key.clone(), value.clone()))
409 }
418 }
410 DiffItem::Remove(key, value) => {
419 DiffItem::Remove(key, value) => {
411 right_updates.push((key.clone(), value.clone()))
420 right_updates.push((key.clone(), value.clone()))
412 }
421 }
413 DiffItem::Update {
422 DiffItem::Update {
414 old: (key, left_value),
423 old: (key, left_value),
415 new: (_, right_value),
424 new: (_, right_value),
416 } => match merge(key, left_value, right_value) {
425 } => match merge(key, left_value, right_value) {
417 MergeResult::UseLeftValue => {
426 MergeResult::UseLeftValue => {
418 right_updates.push((key.clone(), left_value.clone()))
427 right_updates.push((key.clone(), left_value.clone()))
419 }
428 }
420 MergeResult::UseRightValue => {
429 MergeResult::UseRightValue => {
421 left_updates.push((key.clone(), right_value.clone()))
430 left_updates.push((key.clone(), right_value.clone()))
422 }
431 }
423 MergeResult::UseNewValue(new_value) => {
432 MergeResult::UseNewValue(new_value) => {
424 left_updates.push((key.clone(), new_value.clone()));
433 left_updates.push((key.clone(), new_value.clone()));
425 right_updates.push((key.clone(), new_value))
434 right_updates.push((key.clone(), new_value))
426 }
435 }
427 },
436 },
428 }
437 }
429 }
438 }
430 if left_updates.len() < right_updates.len() {
439 if left_updates.len() < right_updates.len() {
431 for (key, value) in left_updates {
440 for (key, value) in left_updates {
432 left.insert(key, value);
441 left.insert(key, value);
433 }
442 }
434 left
443 left
435 } else {
444 } else {
436 for (key, value) in right_updates {
445 for (key, value) in right_updates {
437 right.insert(key, value);
446 right.insert(key, value);
438 }
447 }
439 right
448 right
440 }
449 }
441 }
450 }
442
451
443 /// Join items of the iterable with the given separator, similar to Python’s
452 /// Join items of the iterable with the given separator, similar to Python’s
444 /// `separator.join(iter)`.
453 /// `separator.join(iter)`.
445 ///
454 ///
446 /// Formatting the return value consumes the iterator.
455 /// Formatting the return value consumes the iterator.
447 /// Formatting it again will produce an empty string.
456 /// Formatting it again will produce an empty string.
448 pub fn join_display(
457 pub fn join_display(
449 iter: impl IntoIterator<Item = impl fmt::Display>,
458 iter: impl IntoIterator<Item = impl fmt::Display>,
450 separator: impl fmt::Display,
459 separator: impl fmt::Display,
451 ) -> impl fmt::Display {
460 ) -> impl fmt::Display {
452 JoinDisplay {
461 JoinDisplay {
453 iter: Cell::new(Some(iter.into_iter())),
462 iter: Cell::new(Some(iter.into_iter())),
454 separator,
463 separator,
455 }
464 }
456 }
465 }
457
466
458 struct JoinDisplay<I, S> {
467 struct JoinDisplay<I, S> {
459 iter: Cell<Option<I>>,
468 iter: Cell<Option<I>>,
460 separator: S,
469 separator: S,
461 }
470 }
462
471
463 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
472 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
464 where
473 where
465 I: Iterator<Item = T>,
474 I: Iterator<Item = T>,
466 T: fmt::Display,
475 T: fmt::Display,
467 S: fmt::Display,
476 S: fmt::Display,
468 {
477 {
469 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470 if let Some(mut iter) = self.iter.take() {
479 if let Some(mut iter) = self.iter.take() {
471 if let Some(first) = iter.next() {
480 if let Some(first) = iter.next() {
472 first.fmt(f)?;
481 first.fmt(f)?;
473 }
482 }
474 for value in iter {
483 for value in iter {
475 self.separator.fmt(f)?;
484 self.separator.fmt(f)?;
476 value.fmt(f)?;
485 value.fmt(f)?;
477 }
486 }
478 }
487 }
479 Ok(())
488 Ok(())
480 }
489 }
481 }
490 }
General Comments 0
You need to be logged in to leave comments. Login now