##// END OF EJS Templates
rust: Add Repo::manifest(revision)...
Simon Sapin -
r48774:cfb6e669 default
parent child Browse files
Show More
@@ -1,103 +1,99 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use std::path::PathBuf;
9 9
10 10 use crate::repo::Repo;
11 11 use crate::revlog::path_encode::path_encode;
12 12 use crate::revlog::revlog::Revlog;
13 13 use crate::revlog::revlog::RevlogError;
14 14 use crate::revlog::Node;
15 15 use crate::utils::files::get_path_from_bytes;
16 16 use crate::utils::hg_path::{HgPath, HgPathBuf};
17 17
18 18 pub struct CatOutput {
19 19 /// Whether any file in the manifest matched the paths given as CLI
20 20 /// arguments
21 21 pub found_any: bool,
22 22 /// The contents of matching files, in manifest order
23 23 pub concatenated: Vec<u8>,
24 24 /// Which of the CLI arguments did not match any manifest file
25 25 pub missing: Vec<HgPathBuf>,
26 26 /// The node ID that the given revset was resolved to
27 27 pub node: Node,
28 28 }
29 29
30 30 const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];
31 31
32 32 /// Output the given revision of files
33 33 ///
34 34 /// * `root`: Repository root
35 35 /// * `rev`: The revision to cat the files from.
36 36 /// * `files`: The files to output.
37 37 pub fn cat<'a>(
38 38 repo: &Repo,
39 39 revset: &str,
40 40 files: &'a [HgPathBuf],
41 41 ) -> Result<CatOutput, RevlogError> {
42 42 let rev = crate::revset::resolve_single(revset, repo)?;
43 let changelog = repo.changelog()?;
44 let manifest = repo.manifestlog()?;
45 let changelog_entry = changelog.get_rev(rev)?;
46 let node = *changelog
43 let manifest = repo.manifest(rev)?;
44 let node = *repo
45 .changelog()?
47 46 .node_from_rev(rev)
48 .expect("should succeed when changelog.get_rev did");
49 let manifest_node =
50 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
51 let manifest_entry = manifest.get_node(manifest_node.into())?;
47 .expect("should succeed when repo.manifest did");
52 48 let mut bytes = vec![];
53 49 let mut matched = vec![false; files.len()];
54 50 let mut found_any = false;
55 51
56 for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
52 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
57 53 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
58 54 if cat_file.as_bytes() == manifest_file.as_bytes() {
59 55 *is_matched = true;
60 56 found_any = true;
61 57 let index_path = store_path(manifest_file, b".i");
62 58 let data_path = store_path(manifest_file, b".d");
63 59
64 60 let file_log =
65 61 Revlog::open(repo, &index_path, Some(&data_path))?;
66 62 let file_node = Node::from_hex_for_repo(node_bytes)?;
67 63 let file_rev = file_log.get_node_rev(file_node.into())?;
68 64 let data = file_log.get_rev_data(file_rev)?;
69 65 if data.starts_with(&METADATA_DELIMITER) {
70 66 let end_delimiter_position = data
71 67 [METADATA_DELIMITER.len()..]
72 68 .windows(METADATA_DELIMITER.len())
73 69 .position(|bytes| bytes == METADATA_DELIMITER);
74 70 if let Some(position) = end_delimiter_position {
75 71 let offset = METADATA_DELIMITER.len() * 2;
76 72 bytes.extend(data[position + offset..].iter());
77 73 }
78 74 } else {
79 75 bytes.extend(data);
80 76 }
81 77 }
82 78 }
83 79 }
84 80
85 81 let missing: Vec<_> = files
86 82 .iter()
87 83 .zip(&matched)
88 84 .filter(|pair| !*pair.1)
89 85 .map(|pair| pair.0.clone())
90 86 .collect();
91 87 Ok(CatOutput {
92 88 found_any,
93 89 concatenated: bytes,
94 90 missing,
95 91 node,
96 92 })
97 93 }
98 94
99 95 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
100 96 let encoded_bytes =
101 97 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
102 98 get_path_from_bytes(&encoded_bytes).into()
103 99 }
@@ -1,89 +1,82 b''
1 1 // list_tracked_files.rs
2 2 //
3 3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate_entries;
9 9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 10 use crate::errors::HgError;
11 11 use crate::repo::Repo;
12 12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::node::Node;
14 13 use crate::revlog::revlog::RevlogError;
15 14 use crate::utils::hg_path::HgPath;
16 15 use crate::DirstateError;
17 16 use rayon::prelude::*;
18 17
19 18 /// List files under Mercurial control in the working directory
20 19 /// by reading the dirstate
21 20 pub struct Dirstate {
22 21 /// The `dirstate` content.
23 22 content: Vec<u8>,
24 23 v2_metadata: Option<Vec<u8>>,
25 24 }
26 25
27 26 impl Dirstate {
28 27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
29 28 let mut content = repo.hg_vfs().read("dirstate")?;
30 29 let v2_metadata = if repo.has_dirstate_v2() {
31 30 let docket = read_docket(&content)?;
32 31 let meta = docket.tree_metadata().to_vec();
33 32 content = repo.hg_vfs().read(docket.data_filename())?;
34 33 Some(meta)
35 34 } else {
36 35 None
37 36 };
38 37 Ok(Self {
39 38 content,
40 39 v2_metadata,
41 40 })
42 41 }
43 42
44 43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
45 44 let mut files = Vec::new();
46 45 if !self.content.is_empty() {
47 46 if let Some(meta) = &self.v2_metadata {
48 47 for_each_tracked_path(&self.content, meta, |path| {
49 48 files.push(path)
50 49 })?
51 50 } else {
52 51 let _parents = parse_dirstate_entries(
53 52 &self.content,
54 53 |path, entry, _copy_source| {
55 54 if entry.state.is_tracked() {
56 55 files.push(path)
57 56 }
58 57 Ok(())
59 58 },
60 59 )?;
61 60 }
62 61 }
63 62 files.par_sort_unstable();
64 63 Ok(files)
65 64 }
66 65 }
67 66
68 67 /// List files under Mercurial control at a given revision.
69 68 pub fn list_rev_tracked_files(
70 69 repo: &Repo,
71 70 revset: &str,
72 71 ) -> Result<FilesForRev, RevlogError> {
73 72 let rev = crate::revset::resolve_single(revset, repo)?;
74 let changelog = repo.changelog()?;
75 let manifest = repo.manifestlog()?;
76 let changelog_entry = changelog.get_rev(rev)?;
77 let manifest_node =
78 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
79 let manifest_entry = manifest.get_node(manifest_node.into())?;
80 Ok(FilesForRev(manifest_entry))
73 Ok(FilesForRev(repo.manifest(rev)?))
81 74 }
82 75
83 76 pub struct FilesForRev(Manifest);
84 77
85 78 impl FilesForRev {
86 79 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
87 80 self.0.files()
88 81 }
89 82 }
@@ -1,390 +1,403 b''
1 1 use crate::changelog::Changelog;
2 2 use crate::config::{Config, ConfigError, ConfigParseError};
3 3 use crate::dirstate::DirstateParents;
4 4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 6 use crate::errors::HgError;
7 7 use crate::errors::HgResultExt;
8 use crate::exit_codes;
9 use crate::manifest::Manifestlog;
8 use crate::manifest::{Manifest, Manifestlog};
10 9 use crate::requirements;
11 10 use crate::revlog::revlog::RevlogError;
12 11 use crate::utils::files::get_path_from_bytes;
13 12 use crate::utils::SliceExt;
14 13 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::DirstateError;
14 use crate::{exit_codes, Node};
15 use crate::{DirstateError, Revision};
16 16 use std::cell::{Cell, Ref, RefCell, RefMut};
17 17 use std::collections::HashSet;
18 18 use std::path::{Path, PathBuf};
19 19
20 20 /// A repository on disk
21 21 pub struct Repo {
22 22 working_directory: PathBuf,
23 23 dot_hg: PathBuf,
24 24 store: PathBuf,
25 25 requirements: HashSet<String>,
26 26 config: Config,
27 27 // None means not known/initialized yet
28 28 dirstate_parents: Cell<Option<DirstateParents>>,
29 29 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
30 30 changelog: LazyCell<Changelog, RevlogError>,
31 31 manifestlog: LazyCell<Manifestlog, RevlogError>,
32 32 }
33 33
34 34 #[derive(Debug, derive_more::From)]
35 35 pub enum RepoError {
36 36 NotFound {
37 37 at: PathBuf,
38 38 },
39 39 #[from]
40 40 ConfigParseError(ConfigParseError),
41 41 #[from]
42 42 Other(HgError),
43 43 }
44 44
45 45 impl From<ConfigError> for RepoError {
46 46 fn from(error: ConfigError) -> Self {
47 47 match error {
48 48 ConfigError::Parse(error) => error.into(),
49 49 ConfigError::Other(error) => error.into(),
50 50 }
51 51 }
52 52 }
53 53
54 54 impl Repo {
55 55 /// tries to find nearest repository root in current working directory or
56 56 /// its ancestors
57 57 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
58 58 let current_directory = crate::utils::current_dir()?;
59 59 // ancestors() is inclusive: it first yields `current_directory`
60 60 // as-is.
61 61 for ancestor in current_directory.ancestors() {
62 62 if is_dir(ancestor.join(".hg"))? {
63 63 return Ok(ancestor.to_path_buf());
64 64 }
65 65 }
66 66 return Err(RepoError::NotFound {
67 67 at: current_directory,
68 68 });
69 69 }
70 70
71 71 /// Find a repository, either at the given path (which must contain a `.hg`
72 72 /// sub-directory) or by searching the current directory and its
73 73 /// ancestors.
74 74 ///
75 75 /// A method with two very different "modes" like this usually a code smell
76 76 /// to make two methods instead, but in this case an `Option` is what rhg
77 77 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
78 78 /// Having two methods would just move that `if` to almost all callers.
79 79 pub fn find(
80 80 config: &Config,
81 81 explicit_path: Option<PathBuf>,
82 82 ) -> Result<Self, RepoError> {
83 83 if let Some(root) = explicit_path {
84 84 if is_dir(root.join(".hg"))? {
85 85 Self::new_at_path(root.to_owned(), config)
86 86 } else if is_file(&root)? {
87 87 Err(HgError::unsupported("bundle repository").into())
88 88 } else {
89 89 Err(RepoError::NotFound {
90 90 at: root.to_owned(),
91 91 })
92 92 }
93 93 } else {
94 94 let root = Self::find_repo_root()?;
95 95 Self::new_at_path(root, config)
96 96 }
97 97 }
98 98
99 99 /// To be called after checking that `.hg` is a sub-directory
100 100 fn new_at_path(
101 101 working_directory: PathBuf,
102 102 config: &Config,
103 103 ) -> Result<Self, RepoError> {
104 104 let dot_hg = working_directory.join(".hg");
105 105
106 106 let mut repo_config_files = Vec::new();
107 107 repo_config_files.push(dot_hg.join("hgrc"));
108 108 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
109 109
110 110 let hg_vfs = Vfs { base: &dot_hg };
111 111 let mut reqs = requirements::load_if_exists(hg_vfs)?;
112 112 let relative =
113 113 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
114 114 let shared =
115 115 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
116 116
117 117 // From `mercurial/localrepo.py`:
118 118 //
119 119 // if .hg/requires contains the sharesafe requirement, it means
120 120 // there exists a `.hg/store/requires` too and we should read it
121 121 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
122 122 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
123 123 // is not present, refer checkrequirementscompat() for that
124 124 //
125 125 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
126 126 // repository was shared the old way. We check the share source
127 127 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
128 128 // current repository needs to be reshared
129 129 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
130 130
131 131 let store_path;
132 132 if !shared {
133 133 store_path = dot_hg.join("store");
134 134 } else {
135 135 let bytes = hg_vfs.read("sharedpath")?;
136 136 let mut shared_path =
137 137 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
138 138 .to_owned();
139 139 if relative {
140 140 shared_path = dot_hg.join(shared_path)
141 141 }
142 142 if !is_dir(&shared_path)? {
143 143 return Err(HgError::corrupted(format!(
144 144 ".hg/sharedpath points to nonexistent directory {}",
145 145 shared_path.display()
146 146 ))
147 147 .into());
148 148 }
149 149
150 150 store_path = shared_path.join("store");
151 151
152 152 let source_is_share_safe =
153 153 requirements::load(Vfs { base: &shared_path })?
154 154 .contains(requirements::SHARESAFE_REQUIREMENT);
155 155
156 156 if share_safe && !source_is_share_safe {
157 157 return Err(match config
158 158 .get(b"share", b"safe-mismatch.source-not-safe")
159 159 {
160 160 Some(b"abort") | None => HgError::abort(
161 161 "abort: share source does not support share-safe requirement\n\
162 162 (see `hg help config.format.use-share-safe` for more information)",
163 163 exit_codes::ABORT,
164 164 ),
165 165 _ => HgError::unsupported("share-safe downgrade"),
166 166 }
167 167 .into());
168 168 } else if source_is_share_safe && !share_safe {
169 169 return Err(
170 170 match config.get(b"share", b"safe-mismatch.source-safe") {
171 171 Some(b"abort") | None => HgError::abort(
172 172 "abort: version mismatch: source uses share-safe \
173 173 functionality while the current share does not\n\
174 174 (see `hg help config.format.use-share-safe` for more information)",
175 175 exit_codes::ABORT,
176 176 ),
177 177 _ => HgError::unsupported("share-safe upgrade"),
178 178 }
179 179 .into(),
180 180 );
181 181 }
182 182
183 183 if share_safe {
184 184 repo_config_files.insert(0, shared_path.join("hgrc"))
185 185 }
186 186 }
187 187 if share_safe {
188 188 reqs.extend(requirements::load(Vfs { base: &store_path })?);
189 189 }
190 190
191 191 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
192 192 config.combine_with_repo(&repo_config_files)?
193 193 } else {
194 194 config.clone()
195 195 };
196 196
197 197 let repo = Self {
198 198 requirements: reqs,
199 199 working_directory,
200 200 store: store_path,
201 201 dot_hg,
202 202 config: repo_config,
203 203 dirstate_parents: Cell::new(None),
204 204 dirstate_map: LazyCell::new(Self::new_dirstate_map),
205 205 changelog: LazyCell::new(Changelog::open),
206 206 manifestlog: LazyCell::new(Manifestlog::open),
207 207 };
208 208
209 209 requirements::check(&repo)?;
210 210
211 211 Ok(repo)
212 212 }
213 213
214 214 pub fn working_directory_path(&self) -> &Path {
215 215 &self.working_directory
216 216 }
217 217
218 218 pub fn requirements(&self) -> &HashSet<String> {
219 219 &self.requirements
220 220 }
221 221
222 222 pub fn config(&self) -> &Config {
223 223 &self.config
224 224 }
225 225
226 226 /// For accessing repository files (in `.hg`), except for the store
227 227 /// (`.hg/store`).
228 228 pub fn hg_vfs(&self) -> Vfs<'_> {
229 229 Vfs { base: &self.dot_hg }
230 230 }
231 231
232 232 /// For accessing repository store files (in `.hg/store`)
233 233 pub fn store_vfs(&self) -> Vfs<'_> {
234 234 Vfs { base: &self.store }
235 235 }
236 236
237 237 /// For accessing the working copy
238 238 pub fn working_directory_vfs(&self) -> Vfs<'_> {
239 239 Vfs {
240 240 base: &self.working_directory,
241 241 }
242 242 }
243 243
244 244 pub fn has_dirstate_v2(&self) -> bool {
245 245 self.requirements
246 246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
247 247 }
248 248
249 249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
250 250 Ok(self
251 251 .hg_vfs()
252 252 .read("dirstate")
253 253 .io_not_found_as_none()?
254 254 .unwrap_or(Vec::new()))
255 255 }
256 256
257 257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
258 258 if let Some(parents) = self.dirstate_parents.get() {
259 259 return Ok(parents);
260 260 }
261 261 let dirstate = self.dirstate_file_contents()?;
262 262 let parents = if dirstate.is_empty() {
263 263 DirstateParents::NULL
264 264 } else if self.has_dirstate_v2() {
265 265 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
266 266 } else {
267 267 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
268 268 .clone()
269 269 };
270 270 self.dirstate_parents.set(Some(parents));
271 271 Ok(parents)
272 272 }
273 273
274 274 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
275 275 let dirstate_file_contents = self.dirstate_file_contents()?;
276 276 if dirstate_file_contents.is_empty() {
277 277 self.dirstate_parents.set(Some(DirstateParents::NULL));
278 278 Ok(OwningDirstateMap::new_empty(Vec::new()))
279 279 } else if self.has_dirstate_v2() {
280 280 let docket = crate::dirstate_tree::on_disk::read_docket(
281 281 &dirstate_file_contents,
282 282 )?;
283 283 self.dirstate_parents.set(Some(docket.parents()));
284 284 let data_size = docket.data_size();
285 285 let metadata = docket.tree_metadata();
286 286 let mut map = if let Some(data_mmap) = self
287 287 .hg_vfs()
288 288 .mmap_open(docket.data_filename())
289 289 .io_not_found_as_none()?
290 290 {
291 291 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
292 292 } else {
293 293 OwningDirstateMap::new_empty(Vec::new())
294 294 };
295 295 let (on_disk, placeholder) = map.get_mut_pair();
296 296 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
297 297 Ok(map)
298 298 } else {
299 299 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
300 300 let (on_disk, placeholder) = map.get_mut_pair();
301 301 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
302 302 self.dirstate_parents
303 303 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
304 304 *placeholder = inner;
305 305 Ok(map)
306 306 }
307 307 }
308 308
309 309 pub fn dirstate_map(
310 310 &self,
311 311 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
312 312 self.dirstate_map.get_or_init(self)
313 313 }
314 314
315 315 pub fn dirstate_map_mut(
316 316 &self,
317 317 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
318 318 self.dirstate_map.get_mut_or_init(self)
319 319 }
320 320
321 321 pub fn changelog(&self) -> Result<Ref<Changelog>, RevlogError> {
322 322 self.changelog.get_or_init(self)
323 323 }
324 324
325 325 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, RevlogError> {
326 326 self.changelog.get_mut_or_init(self)
327 327 }
328 328
329 329 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, RevlogError> {
330 330 self.manifestlog.get_or_init(self)
331 331 }
332 332
333 333 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, RevlogError> {
334 334 self.manifestlog.get_mut_or_init(self)
335 335 }
336
337 /// Returns the manifest of the given revision
338 pub fn manifest(
339 &self,
340 revision: Revision,
341 ) -> Result<Manifest, RevlogError> {
342 let changelog = self.changelog()?;
343 let manifest = self.manifestlog()?;
344 let changelog_entry = changelog.get_rev(revision)?;
345 let manifest_node =
346 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
347 manifest.get_node(manifest_node.into())
348 }
336 349 }
337 350
338 351 /// Lazily-initialized component of `Repo` with interior mutability
339 352 ///
340 353 /// This differs from `OnceCell` in that the value can still be "deinitialized"
341 354 /// later by setting its inner `Option` to `None`.
342 355 struct LazyCell<T, E> {
343 356 value: RefCell<Option<T>>,
344 357 // `Fn`s that don’t capture environment are zero-size, so this box does
345 358 // not allocate:
346 359 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
347 360 }
348 361
349 362 impl<T, E> LazyCell<T, E> {
350 363 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
351 364 Self {
352 365 value: RefCell::new(None),
353 366 init: Box::new(init),
354 367 }
355 368 }
356 369
357 370 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
358 371 let mut borrowed = self.value.borrow();
359 372 if borrowed.is_none() {
360 373 drop(borrowed);
361 374 // Only use `borrow_mut` if it is really needed to avoid panic in
362 375 // case there is another outstanding borrow but mutation is not
363 376 // needed.
364 377 *self.value.borrow_mut() = Some((self.init)(repo)?);
365 378 borrowed = self.value.borrow()
366 379 }
367 380 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
368 381 }
369 382
370 383 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
371 384 let mut borrowed = self.value.borrow_mut();
372 385 if borrowed.is_none() {
373 386 *borrowed = Some((self.init)(repo)?);
374 387 }
375 388 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
376 389 }
377 390 }
378 391
379 392 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
380 393 struct MmapWrapper(memmap2::Mmap);
381 394
382 395 impl std::ops::Deref for MmapWrapper {
383 396 type Target = [u8];
384 397
385 398 fn deref(&self) -> &[u8] {
386 399 self.0.deref()
387 400 }
388 401 }
389 402
390 403 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
General Comments 0
You need to be logged in to leave comments. Login now