##// END OF EJS Templates
rust: introduce Repo and Vfs types for filesystem abstraction...
Simon Sapin -
r46782:8a491439 default
parent child Browse files
Show More
@@ -0,0 +1,92 b''
1 use crate::operations::{find_root, FindRootError};
2 use crate::requirements;
3 use memmap::{Mmap, MmapOptions};
4 use std::path::{Path, PathBuf};
5
6 /// A repository on disk
7 pub struct Repo {
8 working_directory: PathBuf,
9 dot_hg: PathBuf,
10 store: PathBuf,
11 }
12
13 /// Filesystem access abstraction for the contents of a given "base" diretory
14 #[derive(Clone, Copy)]
15 pub(crate) struct Vfs<'a> {
16 base: &'a Path,
17 }
18
19 impl Repo {
20 /// Returns `None` if the given path doesn’t look like a repository
21 /// (doesn’t contain a `.hg` sub-directory).
22 pub fn for_path(root: impl Into<PathBuf>) -> Self {
23 let working_directory = root.into();
24 let dot_hg = working_directory.join(".hg");
25 Self {
26 store: dot_hg.join("store"),
27 dot_hg,
28 working_directory,
29 }
30 }
31
32 pub fn find() -> Result<Self, FindRootError> {
33 find_root().map(Self::for_path)
34 }
35
36 pub fn check_requirements(
37 &self,
38 ) -> Result<(), requirements::RequirementsError> {
39 requirements::check(self)
40 }
41
42 pub fn working_directory_path(&self) -> &Path {
43 &self.working_directory
44 }
45
46 /// For accessing repository files (in `.hg`), except for the store
47 /// (`.hg/store`).
48 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
49 Vfs { base: &self.dot_hg }
50 }
51
52 /// For accessing repository store files (in `.hg/store`)
53 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
54 Vfs { base: &self.store }
55 }
56
57 /// For accessing the working copy
58
59 // The undescore prefix silences the "never used" warning. Remove before
60 // using.
61 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
62 Vfs {
63 base: &self.working_directory,
64 }
65 }
66 }
67
68 impl Vfs<'_> {
69 pub(crate) fn read(
70 &self,
71 relative_path: impl AsRef<Path>,
72 ) -> std::io::Result<Vec<u8>> {
73 std::fs::read(self.base.join(relative_path))
74 }
75
76 pub(crate) fn open(
77 &self,
78 relative_path: impl AsRef<Path>,
79 ) -> std::io::Result<std::fs::File> {
80 std::fs::File::open(self.base.join(relative_path))
81 }
82
83 pub(crate) fn mmap_open(
84 &self,
85 relative_path: impl AsRef<Path>,
86 ) -> std::io::Result<Mmap> {
87 let file = self.open(relative_path)?;
88 // TODO: what are the safety requirements here?
89 let mmap = unsafe { MmapOptions::new().map(&file) }?;
90 Ok(mmap)
91 }
92 }
@@ -23,6 +23,7 b' pub use dirstate::{'
23 23 pub mod copy_tracing;
24 24 mod filepatterns;
25 25 pub mod matchers;
26 pub mod repo;
26 27 pub mod revlog;
27 28 pub use revlog::*;
28 29 pub mod operations;
@@ -6,8 +6,9 b''
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use std::convert::From;
9 use std::path::{Path, PathBuf};
9 use std::path::PathBuf;
10 10
11 use crate::repo::Repo;
11 12 use crate::revlog::changelog::Changelog;
12 13 use crate::revlog::manifest::Manifest;
13 14 use crate::revlog::path_encode::path_encode;
@@ -75,12 +76,12 b' impl From<RevlogError> for CatRevError {'
75 76 /// * `rev`: The revision to cat the files from.
76 77 /// * `files`: The files to output.
77 78 pub fn cat(
78 root: &Path,
79 repo: &Repo,
79 80 rev: &str,
80 81 files: &[HgPathBuf],
81 82 ) -> Result<Vec<u8>, CatRevError> {
82 let changelog = Changelog::open(&root)?;
83 let manifest = Manifest::open(&root)?;
83 let changelog = Changelog::open(repo)?;
84 let manifest = Manifest::open(repo)?;
84 85
85 86 let changelog_entry = match rev.parse::<Revision>() {
86 87 Ok(rev) => changelog.get_rev(rev)?,
@@ -99,10 +100,11 b' pub fn cat('
99 100 for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
100 101 for cat_file in files.iter() {
101 102 if cat_file.as_bytes() == manifest_file.as_bytes() {
102 let index_path = store_path(root, manifest_file, b".i");
103 let data_path = store_path(root, manifest_file, b".d");
103 let index_path = store_path(manifest_file, b".i");
104 let data_path = store_path(manifest_file, b".d");
104 105
105 let file_log = Revlog::open(&index_path, Some(&data_path))?;
106 let file_log =
107 Revlog::open(repo, &index_path, Some(&data_path))?;
106 108 let file_node = Node::from_hex(node_bytes)
107 109 .map_err(|_| CatRevErrorKind::CorruptedRevlog)?;
108 110 let file_rev = file_log.get_node_rev((&file_node).into())?;
@@ -126,14 +128,8 b' pub fn cat('
126 128 Ok(bytes)
127 129 }
128 130
129 fn store_path(root: &Path, hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
131 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
130 132 let encoded_bytes =
131 133 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
132 [
133 root,
134 &Path::new(".hg/store/"),
135 get_path_from_bytes(&encoded_bytes),
136 ]
137 .iter()
138 .collect()
134 get_path_from_bytes(&encoded_bytes).into()
139 135 }
@@ -5,8 +5,7 b''
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 use std::path::Path;
9
8 use crate::repo::Repo;
10 9 use crate::revlog::revlog::{Revlog, RevlogError};
11 10 use crate::revlog::NodePrefix;
12 11 use crate::revlog::Revision;
@@ -79,15 +78,15 b' impl From<RevlogError> for DebugDataErro'
79 78
80 79 /// Dump the contents data of a revision.
81 80 pub fn debug_data(
82 root: &Path,
81 repo: &Repo,
83 82 rev: &str,
84 83 kind: DebugDataKind,
85 84 ) -> Result<Vec<u8>, DebugDataError> {
86 85 let index_file = match kind {
87 DebugDataKind::Changelog => root.join(".hg/store/00changelog.i"),
88 DebugDataKind::Manifest => root.join(".hg/store/00manifest.i"),
86 DebugDataKind::Changelog => "00changelog.i",
87 DebugDataKind::Manifest => "00manifest.i",
89 88 };
90 let revlog = Revlog::open(&index_file, None)?;
89 let revlog = Revlog::open(repo, index_file, None)?;
91 90
92 91 let data = match rev.parse::<Revision>() {
93 92 Ok(rev) => revlog.get_rev_data(rev)?,
@@ -6,6 +6,7 b''
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate;
9 use crate::repo::Repo;
9 10 use crate::revlog::changelog::Changelog;
10 11 use crate::revlog::manifest::{Manifest, ManifestEntry};
11 12 use crate::revlog::node::{Node, NodePrefix};
@@ -15,8 +16,6 b' use crate::utils::hg_path::HgPath;'
15 16 use crate::{DirstateParseError, EntryState};
16 17 use rayon::prelude::*;
17 18 use std::convert::From;
18 use std::fs;
19 use std::path::Path;
20 19
21 20 /// Kind of error encountered by `ListDirstateTrackedFiles`
22 21 #[derive(Debug)]
@@ -57,9 +56,8 b' pub struct Dirstate {'
57 56 }
58 57
59 58 impl Dirstate {
60 pub fn new(root: &Path) -> Result<Self, ListDirstateTrackedFilesError> {
61 let dirstate = root.join(".hg/dirstate");
62 let content = fs::read(&dirstate)?;
59 pub fn new(repo: &Repo) -> Result<Self, ListDirstateTrackedFilesError> {
60 let content = repo.hg_vfs().read("dirstate")?;
63 61 Ok(Self { content })
64 62 }
65 63
@@ -138,11 +136,11 b' impl From<RevlogError> for ListRevTracke'
138 136
139 137 /// List files under Mercurial control at a given revision.
140 138 pub fn list_rev_tracked_files(
141 root: &Path,
139 repo: &Repo,
142 140 rev: &str,
143 141 ) -> Result<FilesForRev, ListRevTrackedFilesError> {
144 let changelog = Changelog::open(root)?;
145 let manifest = Manifest::open(root)?;
142 let changelog = Changelog::open(repo)?;
143 let manifest = Manifest::open(repo)?;
146 144
147 145 let changelog_entry = match rev.parse::<Revision>() {
148 146 Ok(rev) => changelog.get_rev(rev)?,
@@ -1,5 +1,5 b''
1 use crate::repo::Repo;
1 2 use std::io;
2 use std::path::Path;
3 3
4 4 #[derive(Debug)]
5 5 pub enum RequirementsError {
@@ -33,8 +33,8 b' fn parse(bytes: &[u8]) -> Result<Vec<Str'
33 33 .collect()
34 34 }
35 35
36 pub fn load(repo_root: &Path) -> Result<Vec<String>, RequirementsError> {
37 match std::fs::read(repo_root.join(".hg").join("requires")) {
36 pub fn load(repo: &Repo) -> Result<Vec<String>, RequirementsError> {
37 match repo.hg_vfs().read("requires") {
38 38 Ok(bytes) => parse(&bytes).map_err(|()| RequirementsError::Corrupted),
39 39
40 40 // Treat a missing file the same as an empty file.
@@ -52,8 +52,8 b' pub fn load(repo_root: &Path) -> Result<'
52 52 }
53 53 }
54 54
55 pub fn check(repo_root: &Path) -> Result<(), RequirementsError> {
56 for feature in load(repo_root)? {
55 pub fn check(repo: &Repo) -> Result<(), RequirementsError> {
56 for feature in load(repo)? {
57 57 if !SUPPORTED.contains(&&*feature) {
58 58 return Err(RequirementsError::Unsupported { feature });
59 59 }
@@ -1,7 +1,7 b''
1 use crate::repo::Repo;
1 2 use crate::revlog::revlog::{Revlog, RevlogError};
2 3 use crate::revlog::NodePrefixRef;
3 4 use crate::revlog::Revision;
4 use std::path::Path;
5 5
6 6 /// A specialized `Revlog` to work with `changelog` data format.
7 7 pub struct Changelog {
@@ -11,9 +11,8 b' pub struct Changelog {'
11 11
12 12 impl Changelog {
13 13 /// Open the `changelog` of a repository given by its root.
14 pub fn open(root: &Path) -> Result<Self, RevlogError> {
15 let index_file = root.join(".hg/store/00changelog.i");
16 let revlog = Revlog::open(&index_file, None)?;
14 pub fn open(repo: &Repo) -> Result<Self, RevlogError> {
15 let revlog = Revlog::open(repo, "00changelog.i", None)?;
17 16 Ok(Self { revlog })
18 17 }
19 18
@@ -1,8 +1,8 b''
1 use crate::repo::Repo;
1 2 use crate::revlog::revlog::{Revlog, RevlogError};
2 3 use crate::revlog::NodePrefixRef;
3 4 use crate::revlog::Revision;
4 5 use crate::utils::hg_path::HgPath;
5 use std::path::Path;
6 6
7 7 /// A specialized `Revlog` to work with `manifest` data format.
8 8 pub struct Manifest {
@@ -12,9 +12,8 b' pub struct Manifest {'
12 12
13 13 impl Manifest {
14 14 /// Open the `manifest` of a repository given by its root.
15 pub fn open(root: &Path) -> Result<Self, RevlogError> {
16 let index_file = root.join(".hg/store/00manifest.i");
17 let revlog = Revlog::open(&index_file, None)?;
15 pub fn open(repo: &Repo) -> Result<Self, RevlogError> {
16 let revlog = Revlog::open(repo, "00manifest.i", None)?;
18 17 Ok(Self { revlog })
19 18 }
20 19
@@ -2,7 +2,8 b' use memmap::Mmap;'
2 2 use std::convert::TryInto;
3 3 use std::path::{Path, PathBuf};
4 4
5 use super::revlog::{mmap_open, RevlogError};
5 use super::revlog::RevlogError;
6 use crate::repo::Repo;
6 7 use crate::utils::strip_suffix;
7 8
8 9 const ONDISK_VERSION: u8 = 1;
@@ -23,10 +24,11 b' impl NodeMapDocket {'
23 24 /// * The docket file points to a missing (likely deleted) data file (this
24 25 /// can happen in a rare race condition).
25 26 pub fn read_from_file(
27 repo: &Repo,
26 28 index_path: &Path,
27 29 ) -> Result<Option<(Self, Mmap)>, RevlogError> {
28 30 let docket_path = index_path.with_extension("n");
29 let docket_bytes = match std::fs::read(&docket_path) {
31 let docket_bytes = match repo.store_vfs().read(&docket_path) {
30 32 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
31 33 return Ok(None)
32 34 }
@@ -60,7 +62,7 b' impl NodeMapDocket {'
60 62 let data_path = rawdata_path(&docket_path, uid);
61 63 // TODO: use `std::fs::read` here when the `persistent-nodemap.mmap`
62 64 // config is false?
63 match mmap_open(&data_path) {
65 match repo.store_vfs().mmap_open(&data_path) {
64 66 Ok(mmap) => {
65 67 if mmap.len() >= data_length {
66 68 Ok(Some((docket, mmap)))
@@ -1,5 +1,4 b''
1 1 use std::borrow::Cow;
2 use std::fs::File;
3 2 use std::io::Read;
4 3 use std::ops::Deref;
5 4 use std::path::Path;
@@ -8,7 +7,6 b' use byteorder::{BigEndian, ByteOrder};'
8 7 use crypto::digest::Digest;
9 8 use crypto::sha1::Sha1;
10 9 use flate2::read::ZlibDecoder;
11 use memmap::{Mmap, MmapOptions};
12 10 use micro_timer::timed;
13 11 use zstd;
14 12
@@ -18,6 +16,7 b' use super::nodemap;'
18 16 use super::nodemap::NodeMap;
19 17 use super::nodemap_docket::NodeMapDocket;
20 18 use super::patch;
19 use crate::repo::Repo;
21 20 use crate::revlog::Revision;
22 21
23 22 pub enum RevlogError {
@@ -30,12 +29,6 b' pub enum RevlogError {'
30 29 UnknowDataFormat(u8),
31 30 }
32 31
33 pub(super) fn mmap_open(path: &Path) -> Result<Mmap, std::io::Error> {
34 let file = File::open(path)?;
35 let mmap = unsafe { MmapOptions::new().map(&file) }?;
36 Ok(mmap)
37 }
38
39 32 /// Read only implementation of revlog.
40 33 pub struct Revlog {
41 34 /// When index and data are not interleaved: bytes of the revlog index.
@@ -55,11 +48,15 b' impl Revlog {'
55 48 /// interleaved.
56 49 #[timed]
57 50 pub fn open(
58 index_path: &Path,
51 repo: &Repo,
52 index_path: impl AsRef<Path>,
59 53 data_path: Option<&Path>,
60 54 ) -> Result<Self, RevlogError> {
61 let index_mmap =
62 mmap_open(&index_path).map_err(RevlogError::IoError)?;
55 let index_path = index_path.as_ref();
56 let index_mmap = repo
57 .store_vfs()
58 .mmap_open(&index_path)
59 .map_err(RevlogError::IoError)?;
63 60
64 61 let version = get_version(&index_mmap);
65 62 if version != 1 {
@@ -77,12 +74,14 b' impl Revlog {'
77 74 None
78 75 } else {
79 76 let data_path = data_path.unwrap_or(&default_data_path);
80 let data_mmap =
81 mmap_open(data_path).map_err(RevlogError::IoError)?;
77 let data_mmap = repo
78 .store_vfs()
79 .mmap_open(data_path)
80 .map_err(RevlogError::IoError)?;
82 81 Some(Box::new(data_mmap))
83 82 };
84 83
85 let nodemap = NodeMapDocket::read_from_file(index_path)?.map(
84 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
86 85 |(docket, data)| {
87 86 nodemap::NodeTree::load_bytes(
88 87 Box::new(data),
@@ -2,9 +2,8 b' use crate::commands::Command;'
2 2 use crate::error::{CommandError, CommandErrorKind};
3 3 use crate::ui::utf8_to_local;
4 4 use crate::ui::Ui;
5 use hg::operations::find_root;
6 5 use hg::operations::{cat, CatRevError, CatRevErrorKind};
7 use hg::requirements;
6 use hg::repo::Repo;
8 7 use hg::utils::hg_path::HgPathBuf;
9 8 use micro_timer::timed;
10 9 use std::convert::TryFrom;
@@ -32,8 +31,8 b" impl<'a> CatCommand<'a> {"
32 31 impl<'a> Command for CatCommand<'a> {
33 32 #[timed]
34 33 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
35 let root = find_root()?;
36 requirements::check(&root)?;
34 let repo = Repo::find()?;
35 repo.check_requirements()?;
37 36 let cwd = std::env::current_dir()
38 37 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
39 38
@@ -41,7 +40,7 b" impl<'a> Command for CatCommand<'a> {"
41 40 for file in self.files.iter() {
42 41 let normalized = cwd.join(&file);
43 42 let stripped = normalized
44 .strip_prefix(&root)
43 .strip_prefix(&repo.working_directory_path())
45 44 .or(Err(CommandErrorKind::Abort(None)))?;
46 45 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
47 46 .or(Err(CommandErrorKind::Abort(None)))?;
@@ -50,7 +49,7 b" impl<'a> Command for CatCommand<'a> {"
50 49
51 50 match self.rev {
52 51 Some(rev) => {
53 let data = cat(&root, rev, &files)
52 let data = cat(&repo, rev, &files)
54 53 .map_err(|e| map_rev_error(rev, e))?;
55 54 self.display(ui, &data)
56 55 }
@@ -2,10 +2,10 b' use crate::commands::Command;'
2 2 use crate::error::{CommandError, CommandErrorKind};
3 3 use crate::ui::utf8_to_local;
4 4 use crate::ui::Ui;
5 use hg::operations::find_root;
6 5 use hg::operations::{
7 6 debug_data, DebugDataError, DebugDataErrorKind, DebugDataKind,
8 7 };
8 use hg::repo::Repo;
9 9 use micro_timer::timed;
10 10
11 11 pub const HELP_TEXT: &str = "
@@ -26,8 +26,8 b" impl<'a> DebugDataCommand<'a> {"
26 26 impl<'a> Command for DebugDataCommand<'a> {
27 27 #[timed]
28 28 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
29 let root = find_root()?;
30 let data = debug_data(&root, self.rev, self.kind)
29 let repo = Repo::find()?;
30 let data = debug_data(&repo, self.rev, self.kind)
31 31 .map_err(|e| to_command_error(self.rev, e))?;
32 32
33 33 let mut stdout = ui.stdout_buffer();
@@ -1,7 +1,7 b''
1 1 use crate::commands::Command;
2 2 use crate::error::CommandError;
3 3 use crate::ui::Ui;
4 use hg::operations::find_root;
4 use hg::repo::Repo;
5 5 use hg::requirements;
6 6
7 7 pub const HELP_TEXT: &str = "
@@ -18,9 +18,9 b' impl DebugRequirementsCommand {'
18 18
19 19 impl Command for DebugRequirementsCommand {
20 20 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
21 let root = find_root()?;
21 let repo = Repo::find()?;
22 22 let mut output = String::new();
23 for req in requirements::load(&root)? {
23 for req in requirements::load(&repo)? {
24 24 output.push_str(&req);
25 25 output.push('\n');
26 26 }
@@ -2,7 +2,6 b' use crate::commands::Command;'
2 2 use crate::error::{CommandError, CommandErrorKind};
3 3 use crate::ui::utf8_to_local;
4 4 use crate::ui::Ui;
5 use hg::operations::find_root;
6 5 use hg::operations::{
7 6 list_rev_tracked_files, ListRevTrackedFilesError,
8 7 ListRevTrackedFilesErrorKind,
@@ -10,10 +9,9 b' use hg::operations::{'
10 9 use hg::operations::{
11 10 Dirstate, ListDirstateTrackedFilesError, ListDirstateTrackedFilesErrorKind,
12 11 };
13 use hg::requirements;
12 use hg::repo::Repo;
14 13 use hg::utils::files::{get_bytes_from_path, relativize_path};
15 14 use hg::utils::hg_path::{HgPath, HgPathBuf};
16 use std::path::Path;
17 15
18 16 pub const HELP_TEXT: &str = "
19 17 List tracked files.
@@ -33,13 +31,13 b" impl<'a> FilesCommand<'a> {"
33 31 fn display_files(
34 32 &self,
35 33 ui: &Ui,
36 root: &Path,
34 repo: &Repo,
37 35 files: impl IntoIterator<Item = &'a HgPath>,
38 36 ) -> Result<(), CommandError> {
39 37 let cwd = std::env::current_dir()
40 38 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
41 39 let rooted_cwd = cwd
42 .strip_prefix(root)
40 .strip_prefix(repo.working_directory_path())
43 41 .expect("cwd was already checked within the repository");
44 42 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
45 43
@@ -56,16 +54,16 b" impl<'a> FilesCommand<'a> {"
56 54
57 55 impl<'a> Command for FilesCommand<'a> {
58 56 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
59 let root = find_root()?;
60 requirements::check(&root)?;
57 let repo = Repo::find()?;
58 repo.check_requirements()?;
61 59 if let Some(rev) = self.rev {
62 let files = list_rev_tracked_files(&root, rev)
60 let files = list_rev_tracked_files(&repo, rev)
63 61 .map_err(|e| map_rev_error(rev, e))?;
64 self.display_files(ui, &root, files.iter())
62 self.display_files(ui, &repo, files.iter())
65 63 } else {
66 let distate = Dirstate::new(&root).map_err(map_dirstate_error)?;
64 let distate = Dirstate::new(&repo).map_err(map_dirstate_error)?;
67 65 let files = distate.tracked_files().map_err(map_dirstate_error)?;
68 self.display_files(ui, &root, files)
66 self.display_files(ui, &repo, files)
69 67 }
70 68 }
71 69 }
@@ -2,7 +2,7 b' use crate::commands::Command;'
2 2 use crate::error::CommandError;
3 3 use crate::ui::Ui;
4 4 use format_bytes::format_bytes;
5 use hg::operations::find_root;
5 use hg::repo::Repo;
6 6 use hg::utils::files::get_bytes_from_path;
7 7
8 8 pub const HELP_TEXT: &str = "
@@ -21,12 +21,9 b' impl RootCommand {'
21 21
22 22 impl Command for RootCommand {
23 23 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
24 let path_buf = find_root()?;
25
26 let bytes = get_bytes_from_path(path_buf);
27
24 let repo = Repo::find()?;
25 let bytes = get_bytes_from_path(repo.working_directory_path());
28 26 ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
29
30 27 Ok(())
31 28 }
32 29 }
General Comments 0
You need to be logged in to leave comments. Login now