##// END OF EJS Templates
rhg: Reuse manifest when checking status of multiple ambiguous files...
Simon Sapin -
r48778:796206e7 default
parent child Browse files
Show More
@@ -1,71 +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 crate::repo::Repo;
8 use crate::repo::Repo;
9 use crate::revlog::revlog::RevlogError;
9 use crate::revlog::revlog::RevlogError;
10 use crate::revlog::Node;
10 use crate::revlog::Node;
11
11
12 use crate::utils::hg_path::HgPathBuf;
12 use crate::utils::hg_path::HgPathBuf;
13
13
14 pub struct CatOutput {
14 pub struct CatOutput {
15 /// 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
16 /// arguments
16 /// arguments
17 pub found_any: bool,
17 pub found_any: bool,
18 /// The contents of matching files, in manifest order
18 /// The contents of matching files, in manifest order
19 pub concatenated: Vec<u8>,
19 pub concatenated: Vec<u8>,
20 /// Which of the CLI arguments did not match any manifest file
20 /// Which of the CLI arguments did not match any manifest file
21 pub missing: Vec<HgPathBuf>,
21 pub missing: Vec<HgPathBuf>,
22 /// The node ID that the given revset was resolved to
22 /// The node ID that the given revset was resolved to
23 pub node: Node,
23 pub node: Node,
24 }
24 }
25
25
26 /// Output the given revision of files
26 /// Output the given revision of files
27 ///
27 ///
28 /// * `root`: Repository root
28 /// * `root`: Repository root
29 /// * `rev`: The revision to cat the files from.
29 /// * `rev`: The revision to cat the files from.
30 /// * `files`: The files to output.
30 /// * `files`: The files to output.
31 pub fn cat<'a>(
31 pub fn cat<'a>(
32 repo: &Repo,
32 repo: &Repo,
33 revset: &str,
33 revset: &str,
34 files: &'a [HgPathBuf],
34 files: &'a [HgPathBuf],
35 ) -> Result<CatOutput, RevlogError> {
35 ) -> Result<CatOutput, RevlogError> {
36 let rev = crate::revset::resolve_single(revset, repo)?;
36 let rev = crate::revset::resolve_single(revset, repo)?;
37 let manifest = repo.manifest(rev)?;
37 let manifest = repo.manifest_for_rev(rev)?;
38 let node = *repo
38 let node = *repo
39 .changelog()?
39 .changelog()?
40 .node_from_rev(rev)
40 .node_from_rev(rev)
41 .expect("should succeed when repo.manifest did");
41 .expect("should succeed when repo.manifest did");
42 let mut bytes = vec![];
42 let mut bytes = vec![];
43 let mut matched = vec![false; files.len()];
43 let mut matched = vec![false; files.len()];
44 let mut found_any = false;
44 let mut found_any = false;
45
45
46 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
46 for (manifest_file, node_bytes) in manifest.files_with_nodes() {
47 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
47 for (cat_file, is_matched) in files.iter().zip(&mut matched) {
48 if cat_file.as_bytes() == manifest_file.as_bytes() {
48 if cat_file.as_bytes() == manifest_file.as_bytes() {
49 *is_matched = true;
49 *is_matched = true;
50 found_any = true;
50 found_any = true;
51 let file_log = repo.filelog(manifest_file)?;
51 let file_log = repo.filelog(manifest_file)?;
52 let file_node = Node::from_hex_for_repo(node_bytes)?;
52 let file_node = Node::from_hex_for_repo(node_bytes)?;
53 let entry = file_log.get_node(file_node)?;
53 let entry = file_log.get_node(file_node)?;
54 bytes.extend(entry.data()?)
54 bytes.extend(entry.data()?)
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 let missing: Vec<_> = files
59 let missing: Vec<_> = files
60 .iter()
60 .iter()
61 .zip(&matched)
61 .zip(&matched)
62 .filter(|pair| !*pair.1)
62 .filter(|pair| !*pair.1)
63 .map(|pair| pair.0.clone())
63 .map(|pair| pair.0.clone())
64 .collect();
64 .collect();
65 Ok(CatOutput {
65 Ok(CatOutput {
66 found_any,
66 found_any,
67 concatenated: bytes,
67 concatenated: bytes,
68 missing,
68 missing,
69 node,
69 node,
70 })
70 })
71 }
71 }
@@ -1,82 +1,82 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 crate::dirstate::parsers::parse_dirstate_entries;
8 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 use crate::errors::HgError;
10 use crate::errors::HgError;
11 use crate::repo::Repo;
11 use crate::repo::Repo;
12 use crate::revlog::manifest::Manifest;
12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::revlog::RevlogError;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::DirstateError;
15 use crate::DirstateError;
16 use rayon::prelude::*;
16 use rayon::prelude::*;
17
17
18 /// List files under Mercurial control in the working directory
18 /// List files under Mercurial control in the working directory
19 /// by reading the dirstate
19 /// by reading the dirstate
20 pub struct Dirstate {
20 pub struct Dirstate {
21 /// The `dirstate` content.
21 /// The `dirstate` content.
22 content: Vec<u8>,
22 content: Vec<u8>,
23 v2_metadata: Option<Vec<u8>>,
23 v2_metadata: Option<Vec<u8>>,
24 }
24 }
25
25
26 impl Dirstate {
26 impl Dirstate {
27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
28 let mut content = repo.hg_vfs().read("dirstate")?;
28 let mut content = repo.hg_vfs().read("dirstate")?;
29 let v2_metadata = if repo.has_dirstate_v2() {
29 let v2_metadata = if repo.has_dirstate_v2() {
30 let docket = read_docket(&content)?;
30 let docket = read_docket(&content)?;
31 let meta = docket.tree_metadata().to_vec();
31 let meta = docket.tree_metadata().to_vec();
32 content = repo.hg_vfs().read(docket.data_filename())?;
32 content = repo.hg_vfs().read(docket.data_filename())?;
33 Some(meta)
33 Some(meta)
34 } else {
34 } else {
35 None
35 None
36 };
36 };
37 Ok(Self {
37 Ok(Self {
38 content,
38 content,
39 v2_metadata,
39 v2_metadata,
40 })
40 })
41 }
41 }
42
42
43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
44 let mut files = Vec::new();
44 let mut files = Vec::new();
45 if !self.content.is_empty() {
45 if !self.content.is_empty() {
46 if let Some(meta) = &self.v2_metadata {
46 if let Some(meta) = &self.v2_metadata {
47 for_each_tracked_path(&self.content, meta, |path| {
47 for_each_tracked_path(&self.content, meta, |path| {
48 files.push(path)
48 files.push(path)
49 })?
49 })?
50 } else {
50 } else {
51 let _parents = parse_dirstate_entries(
51 let _parents = parse_dirstate_entries(
52 &self.content,
52 &self.content,
53 |path, entry, _copy_source| {
53 |path, entry, _copy_source| {
54 if entry.state.is_tracked() {
54 if entry.state.is_tracked() {
55 files.push(path)
55 files.push(path)
56 }
56 }
57 Ok(())
57 Ok(())
58 },
58 },
59 )?;
59 )?;
60 }
60 }
61 }
61 }
62 files.par_sort_unstable();
62 files.par_sort_unstable();
63 Ok(files)
63 Ok(files)
64 }
64 }
65 }
65 }
66
66
67 /// List files under Mercurial control at a given revision.
67 /// List files under Mercurial control at a given revision.
68 pub fn list_rev_tracked_files(
68 pub fn list_rev_tracked_files(
69 repo: &Repo,
69 repo: &Repo,
70 revset: &str,
70 revset: &str,
71 ) -> Result<FilesForRev, RevlogError> {
71 ) -> Result<FilesForRev, RevlogError> {
72 let rev = crate::revset::resolve_single(revset, repo)?;
72 let rev = crate::revset::resolve_single(revset, repo)?;
73 Ok(FilesForRev(repo.manifest(rev)?))
73 Ok(FilesForRev(repo.manifest_for_rev(rev)?))
74 }
74 }
75
75
76 pub struct FilesForRev(Manifest);
76 pub struct FilesForRev(Manifest);
77
77
78 impl FilesForRev {
78 impl FilesForRev {
79 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
79 pub fn iter(&self) -> impl Iterator<Item = &HgPath> {
80 self.0.files()
80 self.0.files()
81 }
81 }
82 }
82 }
@@ -1,409 +1,419 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgError;
6 use crate::errors::HgError;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::exit_codes;
8 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
9 use crate::requirements;
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
15 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::{exit_codes, Node};
16 use crate::{requirements, NodePrefix};
17 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
19 use std::collections::HashSet;
19 use std::collections::HashSet;
20 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
21
21
22 /// A repository on disk
22 /// A repository on disk
23 pub struct Repo {
23 pub struct Repo {
24 working_directory: PathBuf,
24 working_directory: PathBuf,
25 dot_hg: PathBuf,
25 dot_hg: PathBuf,
26 store: PathBuf,
26 store: PathBuf,
27 requirements: HashSet<String>,
27 requirements: HashSet<String>,
28 config: Config,
28 config: Config,
29 // None means not known/initialized yet
29 // None means not known/initialized yet
30 dirstate_parents: Cell<Option<DirstateParents>>,
30 dirstate_parents: Cell<Option<DirstateParents>>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 changelog: LazyCell<Changelog, HgError>,
32 changelog: LazyCell<Changelog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
34 }
34 }
35
35
36 #[derive(Debug, derive_more::From)]
36 #[derive(Debug, derive_more::From)]
37 pub enum RepoError {
37 pub enum RepoError {
38 NotFound {
38 NotFound {
39 at: PathBuf,
39 at: PathBuf,
40 },
40 },
41 #[from]
41 #[from]
42 ConfigParseError(ConfigParseError),
42 ConfigParseError(ConfigParseError),
43 #[from]
43 #[from]
44 Other(HgError),
44 Other(HgError),
45 }
45 }
46
46
47 impl From<ConfigError> for RepoError {
47 impl From<ConfigError> for RepoError {
48 fn from(error: ConfigError) -> Self {
48 fn from(error: ConfigError) -> Self {
49 match error {
49 match error {
50 ConfigError::Parse(error) => error.into(),
50 ConfigError::Parse(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
52 }
52 }
53 }
53 }
54 }
54 }
55
55
56 impl Repo {
56 impl Repo {
57 /// tries to find nearest repository root in current working directory or
57 /// tries to find nearest repository root in current working directory or
58 /// its ancestors
58 /// its ancestors
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 let current_directory = crate::utils::current_dir()?;
60 let current_directory = crate::utils::current_dir()?;
61 // ancestors() is inclusive: it first yields `current_directory`
61 // ancestors() is inclusive: it first yields `current_directory`
62 // as-is.
62 // as-is.
63 for ancestor in current_directory.ancestors() {
63 for ancestor in current_directory.ancestors() {
64 if is_dir(ancestor.join(".hg"))? {
64 if is_dir(ancestor.join(".hg"))? {
65 return Ok(ancestor.to_path_buf());
65 return Ok(ancestor.to_path_buf());
66 }
66 }
67 }
67 }
68 return Err(RepoError::NotFound {
68 return Err(RepoError::NotFound {
69 at: current_directory,
69 at: current_directory,
70 });
70 });
71 }
71 }
72
72
73 /// 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`
74 /// sub-directory) or by searching the current directory and its
74 /// sub-directory) or by searching the current directory and its
75 /// ancestors.
75 /// ancestors.
76 ///
76 ///
77 /// 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
78 /// 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
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// 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.
81 pub fn find(
81 pub fn find(
82 config: &Config,
82 config: &Config,
83 explicit_path: Option<PathBuf>,
83 explicit_path: Option<PathBuf>,
84 ) -> Result<Self, RepoError> {
84 ) -> Result<Self, RepoError> {
85 if let Some(root) = explicit_path {
85 if let Some(root) = explicit_path {
86 if is_dir(root.join(".hg"))? {
86 if is_dir(root.join(".hg"))? {
87 Self::new_at_path(root.to_owned(), config)
87 Self::new_at_path(root.to_owned(), config)
88 } else if is_file(&root)? {
88 } else if is_file(&root)? {
89 Err(HgError::unsupported("bundle repository").into())
89 Err(HgError::unsupported("bundle repository").into())
90 } else {
90 } else {
91 Err(RepoError::NotFound {
91 Err(RepoError::NotFound {
92 at: root.to_owned(),
92 at: root.to_owned(),
93 })
93 })
94 }
94 }
95 } else {
95 } else {
96 let root = Self::find_repo_root()?;
96 let root = Self::find_repo_root()?;
97 Self::new_at_path(root, config)
97 Self::new_at_path(root, config)
98 }
98 }
99 }
99 }
100
100
101 /// To be called after checking that `.hg` is a sub-directory
101 /// To be called after checking that `.hg` is a sub-directory
102 fn new_at_path(
102 fn new_at_path(
103 working_directory: PathBuf,
103 working_directory: PathBuf,
104 config: &Config,
104 config: &Config,
105 ) -> Result<Self, RepoError> {
105 ) -> Result<Self, RepoError> {
106 let dot_hg = working_directory.join(".hg");
106 let dot_hg = working_directory.join(".hg");
107
107
108 let mut repo_config_files = Vec::new();
108 let mut repo_config_files = Vec::new();
109 repo_config_files.push(dot_hg.join("hgrc"));
109 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111
111
112 let hg_vfs = Vfs { base: &dot_hg };
112 let hg_vfs = Vfs { base: &dot_hg };
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let relative =
114 let relative =
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 let shared =
116 let shared =
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118
118
119 // From `mercurial/localrepo.py`:
119 // From `mercurial/localrepo.py`:
120 //
120 //
121 // if .hg/requires contains the sharesafe requirement, it means
121 // if .hg/requires contains the sharesafe requirement, it means
122 // 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
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // 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
125 // is not present, refer checkrequirementscompat() for that
125 // is not present, refer checkrequirementscompat() for that
126 //
126 //
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // repository was shared the old way. We check the share source
128 // repository was shared the old way. We check the share source
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // current repository needs to be reshared
130 // current repository needs to be reshared
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132
132
133 let store_path;
133 let store_path;
134 if !shared {
134 if !shared {
135 store_path = dot_hg.join("store");
135 store_path = dot_hg.join("store");
136 } else {
136 } else {
137 let bytes = hg_vfs.read("sharedpath")?;
137 let bytes = hg_vfs.read("sharedpath")?;
138 let mut shared_path =
138 let mut shared_path =
139 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'))
140 .to_owned();
140 .to_owned();
141 if relative {
141 if relative {
142 shared_path = dot_hg.join(shared_path)
142 shared_path = dot_hg.join(shared_path)
143 }
143 }
144 if !is_dir(&shared_path)? {
144 if !is_dir(&shared_path)? {
145 return Err(HgError::corrupted(format!(
145 return Err(HgError::corrupted(format!(
146 ".hg/sharedpath points to nonexistent directory {}",
146 ".hg/sharedpath points to nonexistent directory {}",
147 shared_path.display()
147 shared_path.display()
148 ))
148 ))
149 .into());
149 .into());
150 }
150 }
151
151
152 store_path = shared_path.join("store");
152 store_path = shared_path.join("store");
153
153
154 let source_is_share_safe =
154 let source_is_share_safe =
155 requirements::load(Vfs { base: &shared_path })?
155 requirements::load(Vfs { base: &shared_path })?
156 .contains(requirements::SHARESAFE_REQUIREMENT);
156 .contains(requirements::SHARESAFE_REQUIREMENT);
157
157
158 if share_safe && !source_is_share_safe {
158 if share_safe && !source_is_share_safe {
159 return Err(match config
159 return Err(match config
160 .get(b"share", b"safe-mismatch.source-not-safe")
160 .get(b"share", b"safe-mismatch.source-not-safe")
161 {
161 {
162 Some(b"abort") | None => HgError::abort(
162 Some(b"abort") | None => HgError::abort(
163 "abort: share source does not support share-safe requirement\n\
163 "abort: share source does not support share-safe requirement\n\
164 (see `hg help config.format.use-share-safe` for more information)",
164 (see `hg help config.format.use-share-safe` for more information)",
165 exit_codes::ABORT,
165 exit_codes::ABORT,
166 ),
166 ),
167 _ => HgError::unsupported("share-safe downgrade"),
167 _ => HgError::unsupported("share-safe downgrade"),
168 }
168 }
169 .into());
169 .into());
170 } else if source_is_share_safe && !share_safe {
170 } else if source_is_share_safe && !share_safe {
171 return Err(
171 return Err(
172 match config.get(b"share", b"safe-mismatch.source-safe") {
172 match config.get(b"share", b"safe-mismatch.source-safe") {
173 Some(b"abort") | None => HgError::abort(
173 Some(b"abort") | None => HgError::abort(
174 "abort: version mismatch: source uses share-safe \
174 "abort: version mismatch: source uses share-safe \
175 functionality while the current share does not\n\
175 functionality while the current share does not\n\
176 (see `hg help config.format.use-share-safe` for more information)",
176 (see `hg help config.format.use-share-safe` for more information)",
177 exit_codes::ABORT,
177 exit_codes::ABORT,
178 ),
178 ),
179 _ => HgError::unsupported("share-safe upgrade"),
179 _ => HgError::unsupported("share-safe upgrade"),
180 }
180 }
181 .into(),
181 .into(),
182 );
182 );
183 }
183 }
184
184
185 if share_safe {
185 if share_safe {
186 repo_config_files.insert(0, shared_path.join("hgrc"))
186 repo_config_files.insert(0, shared_path.join("hgrc"))
187 }
187 }
188 }
188 }
189 if share_safe {
189 if share_safe {
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 }
191 }
192
192
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 config.combine_with_repo(&repo_config_files)?
194 config.combine_with_repo(&repo_config_files)?
195 } else {
195 } else {
196 config.clone()
196 config.clone()
197 };
197 };
198
198
199 let repo = Self {
199 let repo = Self {
200 requirements: reqs,
200 requirements: reqs,
201 working_directory,
201 working_directory,
202 store: store_path,
202 store: store_path,
203 dot_hg,
203 dot_hg,
204 config: repo_config,
204 config: repo_config,
205 dirstate_parents: Cell::new(None),
205 dirstate_parents: Cell::new(None),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 changelog: LazyCell::new(Changelog::open),
207 changelog: LazyCell::new(Changelog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
209 };
209 };
210
210
211 requirements::check(&repo)?;
211 requirements::check(&repo)?;
212
212
213 Ok(repo)
213 Ok(repo)
214 }
214 }
215
215
216 pub fn working_directory_path(&self) -> &Path {
216 pub fn working_directory_path(&self) -> &Path {
217 &self.working_directory
217 &self.working_directory
218 }
218 }
219
219
220 pub fn requirements(&self) -> &HashSet<String> {
220 pub fn requirements(&self) -> &HashSet<String> {
221 &self.requirements
221 &self.requirements
222 }
222 }
223
223
224 pub fn config(&self) -> &Config {
224 pub fn config(&self) -> &Config {
225 &self.config
225 &self.config
226 }
226 }
227
227
228 /// For accessing repository files (in `.hg`), except for the store
228 /// For accessing repository files (in `.hg`), except for the store
229 /// (`.hg/store`).
229 /// (`.hg/store`).
230 pub fn hg_vfs(&self) -> Vfs<'_> {
230 pub fn hg_vfs(&self) -> Vfs<'_> {
231 Vfs { base: &self.dot_hg }
231 Vfs { base: &self.dot_hg }
232 }
232 }
233
233
234 /// For accessing repository store files (in `.hg/store`)
234 /// For accessing repository store files (in `.hg/store`)
235 pub fn store_vfs(&self) -> Vfs<'_> {
235 pub fn store_vfs(&self) -> Vfs<'_> {
236 Vfs { base: &self.store }
236 Vfs { base: &self.store }
237 }
237 }
238
238
239 /// For accessing the working copy
239 /// For accessing the working copy
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 Vfs {
241 Vfs {
242 base: &self.working_directory,
242 base: &self.working_directory,
243 }
243 }
244 }
244 }
245
245
246 pub fn has_dirstate_v2(&self) -> bool {
246 pub fn has_dirstate_v2(&self) -> bool {
247 self.requirements
247 self.requirements
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
249 }
249 }
250
250
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
252 Ok(self
252 Ok(self
253 .hg_vfs()
253 .hg_vfs()
254 .read("dirstate")
254 .read("dirstate")
255 .io_not_found_as_none()?
255 .io_not_found_as_none()?
256 .unwrap_or(Vec::new()))
256 .unwrap_or(Vec::new()))
257 }
257 }
258
258
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
260 if let Some(parents) = self.dirstate_parents.get() {
260 if let Some(parents) = self.dirstate_parents.get() {
261 return Ok(parents);
261 return Ok(parents);
262 }
262 }
263 let dirstate = self.dirstate_file_contents()?;
263 let dirstate = self.dirstate_file_contents()?;
264 let parents = if dirstate.is_empty() {
264 let parents = if dirstate.is_empty() {
265 DirstateParents::NULL
265 DirstateParents::NULL
266 } else if self.has_dirstate_v2() {
266 } else if self.has_dirstate_v2() {
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
268 } else {
268 } else {
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
270 .clone()
270 .clone()
271 };
271 };
272 self.dirstate_parents.set(Some(parents));
272 self.dirstate_parents.set(Some(parents));
273 Ok(parents)
273 Ok(parents)
274 }
274 }
275
275
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
277 let dirstate_file_contents = self.dirstate_file_contents()?;
277 let dirstate_file_contents = self.dirstate_file_contents()?;
278 if dirstate_file_contents.is_empty() {
278 if dirstate_file_contents.is_empty() {
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
281 } else if self.has_dirstate_v2() {
281 } else if self.has_dirstate_v2() {
282 let docket = crate::dirstate_tree::on_disk::read_docket(
282 let docket = crate::dirstate_tree::on_disk::read_docket(
283 &dirstate_file_contents,
283 &dirstate_file_contents,
284 )?;
284 )?;
285 self.dirstate_parents.set(Some(docket.parents()));
285 self.dirstate_parents.set(Some(docket.parents()));
286 let data_size = docket.data_size();
286 let data_size = docket.data_size();
287 let metadata = docket.tree_metadata();
287 let metadata = docket.tree_metadata();
288 let mut map = if let Some(data_mmap) = self
288 let mut map = if let Some(data_mmap) = self
289 .hg_vfs()
289 .hg_vfs()
290 .mmap_open(docket.data_filename())
290 .mmap_open(docket.data_filename())
291 .io_not_found_as_none()?
291 .io_not_found_as_none()?
292 {
292 {
293 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
293 OwningDirstateMap::new_empty(MmapWrapper(data_mmap))
294 } else {
294 } else {
295 OwningDirstateMap::new_empty(Vec::new())
295 OwningDirstateMap::new_empty(Vec::new())
296 };
296 };
297 let (on_disk, placeholder) = map.get_mut_pair();
297 let (on_disk, placeholder) = map.get_mut_pair();
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
299 Ok(map)
299 Ok(map)
300 } else {
300 } else {
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
302 let (on_disk, placeholder) = map.get_mut_pair();
302 let (on_disk, placeholder) = map.get_mut_pair();
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
304 self.dirstate_parents
304 self.dirstate_parents
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
306 *placeholder = inner;
306 *placeholder = inner;
307 Ok(map)
307 Ok(map)
308 }
308 }
309 }
309 }
310
310
311 pub fn dirstate_map(
311 pub fn dirstate_map(
312 &self,
312 &self,
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
314 self.dirstate_map.get_or_init(self)
314 self.dirstate_map.get_or_init(self)
315 }
315 }
316
316
317 pub fn dirstate_map_mut(
317 pub fn dirstate_map_mut(
318 &self,
318 &self,
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
320 self.dirstate_map.get_mut_or_init(self)
320 self.dirstate_map.get_mut_or_init(self)
321 }
321 }
322
322
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
324 self.changelog.get_or_init(self)
324 self.changelog.get_or_init(self)
325 }
325 }
326
326
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
328 self.changelog.get_mut_or_init(self)
328 self.changelog.get_mut_or_init(self)
329 }
329 }
330
330
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
332 self.manifestlog.get_or_init(self)
332 self.manifestlog.get_or_init(self)
333 }
333 }
334
334
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
336 self.manifestlog.get_mut_or_init(self)
336 self.manifestlog.get_mut_or_init(self)
337 }
337 }
338
338
339 /// Returns the manifest of the given node ID
340 pub fn manifest_for_node(
341 &self,
342 node: impl Into<NodePrefix>,
343 ) -> Result<Manifest, RevlogError> {
344 self.manifestlog()?.get_node(
345 self.changelog()?
346 .get_node(node.into())?
347 .manifest_node()?
348 .into(),
349 )
350 }
351
339 /// Returns the manifest of the given revision
352 /// Returns the manifest of the given revision
340 pub fn manifest(
353 pub fn manifest_for_rev(
341 &self,
354 &self,
342 revision: Revision,
355 revision: Revision,
343 ) -> Result<Manifest, RevlogError> {
356 ) -> Result<Manifest, RevlogError> {
344 let changelog = self.changelog()?;
357 self.manifestlog()?.get_node(
345 let manifest = self.manifestlog()?;
358 self.changelog()?.get_rev(revision)?.manifest_node()?.into(),
346 let changelog_entry = changelog.get_rev(revision)?;
359 )
347 let manifest_node =
348 Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?;
349 manifest.get_node(manifest_node.into())
350 }
360 }
351
361
352 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
362 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
353 Filelog::open(self, path)
363 Filelog::open(self, path)
354 }
364 }
355 }
365 }
356
366
357 /// Lazily-initialized component of `Repo` with interior mutability
367 /// Lazily-initialized component of `Repo` with interior mutability
358 ///
368 ///
359 /// This differs from `OnceCell` in that the value can still be "deinitialized"
369 /// This differs from `OnceCell` in that the value can still be "deinitialized"
360 /// later by setting its inner `Option` to `None`.
370 /// later by setting its inner `Option` to `None`.
361 struct LazyCell<T, E> {
371 struct LazyCell<T, E> {
362 value: RefCell<Option<T>>,
372 value: RefCell<Option<T>>,
363 // `Fn`s that don’t capture environment are zero-size, so this box does
373 // `Fn`s that don’t capture environment are zero-size, so this box does
364 // not allocate:
374 // not allocate:
365 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
375 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
366 }
376 }
367
377
368 impl<T, E> LazyCell<T, E> {
378 impl<T, E> LazyCell<T, E> {
369 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
379 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
370 Self {
380 Self {
371 value: RefCell::new(None),
381 value: RefCell::new(None),
372 init: Box::new(init),
382 init: Box::new(init),
373 }
383 }
374 }
384 }
375
385
376 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
386 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
377 let mut borrowed = self.value.borrow();
387 let mut borrowed = self.value.borrow();
378 if borrowed.is_none() {
388 if borrowed.is_none() {
379 drop(borrowed);
389 drop(borrowed);
380 // Only use `borrow_mut` if it is really needed to avoid panic in
390 // Only use `borrow_mut` if it is really needed to avoid panic in
381 // case there is another outstanding borrow but mutation is not
391 // case there is another outstanding borrow but mutation is not
382 // needed.
392 // needed.
383 *self.value.borrow_mut() = Some((self.init)(repo)?);
393 *self.value.borrow_mut() = Some((self.init)(repo)?);
384 borrowed = self.value.borrow()
394 borrowed = self.value.borrow()
385 }
395 }
386 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
396 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
387 }
397 }
388
398
389 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
399 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
390 let mut borrowed = self.value.borrow_mut();
400 let mut borrowed = self.value.borrow_mut();
391 if borrowed.is_none() {
401 if borrowed.is_none() {
392 *borrowed = Some((self.init)(repo)?);
402 *borrowed = Some((self.init)(repo)?);
393 }
403 }
394 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
404 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
395 }
405 }
396 }
406 }
397
407
398 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
408 // TODO: remove this when https://github.com/RazrFalcon/memmap2-rs/pull/22 is on crates.io
399 struct MmapWrapper(memmap2::Mmap);
409 struct MmapWrapper(memmap2::Mmap);
400
410
401 impl std::ops::Deref for MmapWrapper {
411 impl std::ops::Deref for MmapWrapper {
402 type Target = [u8];
412 type Target = [u8];
403
413
404 fn deref(&self) -> &[u8] {
414 fn deref(&self) -> &[u8] {
405 self.0.deref()
415 self.0.deref()
406 }
416 }
407 }
417 }
408
418
409 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
419 unsafe impl stable_deref_trait::StableDeref for MmapWrapper {}
@@ -1,65 +1,67 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::revlog::{Revlog, RevlogError};
3 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::Revision;
4 use crate::revlog::Revision;
5 use crate::revlog::{Node, NodePrefix};
5 use crate::revlog::{Node, NodePrefix};
6
6
7 /// A specialized `Revlog` to work with `changelog` data format.
7 /// A specialized `Revlog` to work with `changelog` data format.
8 pub struct Changelog {
8 pub struct Changelog {
9 /// The generic `revlog` format.
9 /// The generic `revlog` format.
10 pub(crate) revlog: Revlog,
10 pub(crate) revlog: Revlog,
11 }
11 }
12
12
13 impl Changelog {
13 impl Changelog {
14 /// Open the `changelog` of a repository given by its root.
14 /// Open the `changelog` of a repository given by its root.
15 pub fn open(repo: &Repo) -> Result<Self, HgError> {
15 pub fn open(repo: &Repo) -> Result<Self, HgError> {
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
17 Ok(Self { revlog })
17 Ok(Self { revlog })
18 }
18 }
19
19
20 /// Return the `ChangelogEntry` a given node id.
20 /// Return the `ChangelogEntry` a given node id.
21 pub fn get_node(
21 pub fn get_node(
22 &self,
22 &self,
23 node: NodePrefix,
23 node: NodePrefix,
24 ) -> Result<ChangelogEntry, RevlogError> {
24 ) -> Result<ChangelogEntry, RevlogError> {
25 let rev = self.revlog.get_node_rev(node)?;
25 let rev = self.revlog.get_node_rev(node)?;
26 self.get_rev(rev)
26 self.get_rev(rev)
27 }
27 }
28
28
29 /// Return the `ChangelogEntry` of a given node revision.
29 /// Return the `ChangelogEntry` of a given node revision.
30 pub fn get_rev(
30 pub fn get_rev(
31 &self,
31 &self,
32 rev: Revision,
32 rev: Revision,
33 ) -> Result<ChangelogEntry, RevlogError> {
33 ) -> Result<ChangelogEntry, RevlogError> {
34 let bytes = self.revlog.get_rev_data(rev)?;
34 let bytes = self.revlog.get_rev_data(rev)?;
35 Ok(ChangelogEntry { bytes })
35 Ok(ChangelogEntry { bytes })
36 }
36 }
37
37
38 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
38 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
39 Some(self.revlog.index.get_entry(rev)?.hash())
39 Some(self.revlog.index.get_entry(rev)?.hash())
40 }
40 }
41 }
41 }
42
42
43 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
43 /// `Changelog` entry which knows how to interpret the `changelog` data bytes.
44 #[derive(Debug)]
44 #[derive(Debug)]
45 pub struct ChangelogEntry {
45 pub struct ChangelogEntry {
46 /// The data bytes of the `changelog` entry.
46 /// The data bytes of the `changelog` entry.
47 bytes: Vec<u8>,
47 bytes: Vec<u8>,
48 }
48 }
49
49
50 impl ChangelogEntry {
50 impl ChangelogEntry {
51 /// Return an iterator over the lines of the entry.
51 /// Return an iterator over the lines of the entry.
52 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
52 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
53 self.bytes
53 self.bytes
54 .split(|b| b == &b'\n')
54 .split(|b| b == &b'\n')
55 .filter(|line| !line.is_empty())
55 .filter(|line| !line.is_empty())
56 }
56 }
57
57
58 /// Return the node id of the `manifest` referenced by this `changelog`
58 /// Return the node id of the `manifest` referenced by this `changelog`
59 /// entry.
59 /// entry.
60 pub fn manifest_node(&self) -> Result<&[u8], RevlogError> {
60 pub fn manifest_node(&self) -> Result<Node, HgError> {
61 self.lines()
61 Node::from_hex_for_repo(
62 .next()
62 self.lines()
63 .ok_or_else(|| HgError::corrupted("empty changelog entry").into())
63 .next()
64 .ok_or_else(|| HgError::corrupted("empty changelog entry"))?,
65 )
64 }
66 }
65 }
67 }
@@ -1,71 +1,84 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::revlog::{Revlog, RevlogError};
3 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::NodePrefix;
5 use crate::revlog::Revision;
4 use crate::revlog::Revision;
5 use crate::revlog::{Node, NodePrefix};
6 use crate::utils::hg_path::HgPath;
6 use crate::utils::hg_path::HgPath;
7
7
8 /// A specialized `Revlog` to work with `manifest` data format.
8 /// A specialized `Revlog` to work with `manifest` data format.
9 pub struct Manifestlog {
9 pub struct Manifestlog {
10 /// The generic `revlog` format.
10 /// The generic `revlog` format.
11 revlog: Revlog,
11 revlog: Revlog,
12 }
12 }
13
13
14 impl Manifestlog {
14 impl Manifestlog {
15 /// Open the `manifest` of a repository given by its root.
15 /// Open the `manifest` of a repository given by its root.
16 pub fn open(repo: &Repo) -> Result<Self, HgError> {
16 pub fn open(repo: &Repo) -> Result<Self, HgError> {
17 let revlog = Revlog::open(repo, "00manifest.i", None)?;
17 let revlog = Revlog::open(repo, "00manifest.i", None)?;
18 Ok(Self { revlog })
18 Ok(Self { revlog })
19 }
19 }
20
20
21 /// Return the `ManifestEntry` of a given node id.
21 /// Return the `ManifestEntry` of a given node id.
22 pub fn get_node(&self, node: NodePrefix) -> Result<Manifest, RevlogError> {
22 pub fn get_node(&self, node: NodePrefix) -> Result<Manifest, RevlogError> {
23 let rev = self.revlog.get_node_rev(node)?;
23 let rev = self.revlog.get_node_rev(node)?;
24 self.get_rev(rev)
24 self.get_rev(rev)
25 }
25 }
26
26
27 /// Return the `ManifestEntry` of a given node revision.
27 /// Return the `ManifestEntry` of a given node revision.
28 pub fn get_rev(&self, rev: Revision) -> Result<Manifest, RevlogError> {
28 pub fn get_rev(&self, rev: Revision) -> Result<Manifest, RevlogError> {
29 let bytes = self.revlog.get_rev_data(rev)?;
29 let bytes = self.revlog.get_rev_data(rev)?;
30 Ok(Manifest { bytes })
30 Ok(Manifest { bytes })
31 }
31 }
32 }
32 }
33
33
34 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
34 /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes.
35 #[derive(Debug)]
35 #[derive(Debug)]
36 pub struct Manifest {
36 pub struct Manifest {
37 bytes: Vec<u8>,
37 bytes: Vec<u8>,
38 }
38 }
39
39
40 impl Manifest {
40 impl Manifest {
41 /// Return an iterator over the lines of the entry.
41 /// Return an iterator over the lines of the entry.
42 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
42 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
43 self.bytes
43 self.bytes
44 .split(|b| b == &b'\n')
44 .split(|b| b == &b'\n')
45 .filter(|line| !line.is_empty())
45 .filter(|line| !line.is_empty())
46 }
46 }
47
47
48 /// Return an iterator over the files of the entry.
48 /// Return an iterator over the files of the entry.
49 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
49 pub fn files(&self) -> impl Iterator<Item = &HgPath> {
50 self.lines().filter(|line| !line.is_empty()).map(|line| {
50 self.lines().filter(|line| !line.is_empty()).map(|line| {
51 let pos = line
51 let pos = line
52 .iter()
52 .iter()
53 .position(|x| x == &b'\0')
53 .position(|x| x == &b'\0')
54 .expect("manifest line should contain \\0");
54 .expect("manifest line should contain \\0");
55 HgPath::new(&line[..pos])
55 HgPath::new(&line[..pos])
56 })
56 })
57 }
57 }
58
58
59 /// Return an iterator over the files of the entry.
59 /// Return an iterator over the files of the entry.
60 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
60 pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> {
61 self.lines().filter(|line| !line.is_empty()).map(|line| {
61 self.lines().filter(|line| !line.is_empty()).map(|line| {
62 let pos = line
62 let pos = line
63 .iter()
63 .iter()
64 .position(|x| x == &b'\0')
64 .position(|x| x == &b'\0')
65 .expect("manifest line should contain \\0");
65 .expect("manifest line should contain \\0");
66 let hash_start = pos + 1;
66 let hash_start = pos + 1;
67 let hash_end = hash_start + 40;
67 let hash_end = hash_start + 40;
68 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
68 (HgPath::new(&line[..pos]), &line[hash_start..hash_end])
69 })
69 })
70 }
70 }
71
72 /// If the given path is in this manifest, return its filelog node ID
73 pub fn find_file(&self, path: &HgPath) -> Result<Option<Node>, HgError> {
74 // TODO: use binary search instead of linear scan. This may involve
75 // building (and caching) an index of the byte indicex of each manifest
76 // line.
77 for (manifest_path, node) in self.files_with_nodes() {
78 if manifest_path == path {
79 return Ok(Some(Node::from_hex_for_repo(node)?));
80 }
81 }
82 Ok(None)
83 }
71 }
84 }
@@ -1,305 +1,304 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@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 crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::dirstate_tree::dispatch::DirstateMapMethods;
12 use hg::dirstate_tree::dispatch::DirstateMapMethods;
13 use hg::errors::IoResultExt;
13 use hg::errors::{HgError, IoResultExt};
14 use hg::manifest::Manifest;
14 use hg::matchers::AlwaysMatcher;
15 use hg::matchers::AlwaysMatcher;
15 use hg::operations::cat;
16 use hg::repo::Repo;
16 use hg::repo::Repo;
17 use hg::revlog::node::Node;
18 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
19 use hg::StatusError;
20 use hg::{HgPathCow, StatusOptions};
18 use hg::{HgPathCow, StatusOptions};
21 use log::{info, warn};
19 use log::{info, warn};
22 use std::convert::TryInto;
20 use std::convert::TryInto;
23 use std::fs;
21 use std::fs;
24 use std::io::BufReader;
22 use std::io::BufReader;
25 use std::io::Read;
23 use std::io::Read;
26
24
27 pub const HELP_TEXT: &str = "
25 pub const HELP_TEXT: &str = "
28 Show changed files in the working directory
26 Show changed files in the working directory
29
27
30 This is a pure Rust version of `hg status`.
28 This is a pure Rust version of `hg status`.
31
29
32 Some options might be missing, check the list below.
30 Some options might be missing, check the list below.
33 ";
31 ";
34
32
35 pub fn args() -> clap::App<'static, 'static> {
33 pub fn args() -> clap::App<'static, 'static> {
36 SubCommand::with_name("status")
34 SubCommand::with_name("status")
37 .alias("st")
35 .alias("st")
38 .about(HELP_TEXT)
36 .about(HELP_TEXT)
39 .arg(
37 .arg(
40 Arg::with_name("all")
38 Arg::with_name("all")
41 .help("show status of all files")
39 .help("show status of all files")
42 .short("-A")
40 .short("-A")
43 .long("--all"),
41 .long("--all"),
44 )
42 )
45 .arg(
43 .arg(
46 Arg::with_name("modified")
44 Arg::with_name("modified")
47 .help("show only modified files")
45 .help("show only modified files")
48 .short("-m")
46 .short("-m")
49 .long("--modified"),
47 .long("--modified"),
50 )
48 )
51 .arg(
49 .arg(
52 Arg::with_name("added")
50 Arg::with_name("added")
53 .help("show only added files")
51 .help("show only added files")
54 .short("-a")
52 .short("-a")
55 .long("--added"),
53 .long("--added"),
56 )
54 )
57 .arg(
55 .arg(
58 Arg::with_name("removed")
56 Arg::with_name("removed")
59 .help("show only removed files")
57 .help("show only removed files")
60 .short("-r")
58 .short("-r")
61 .long("--removed"),
59 .long("--removed"),
62 )
60 )
63 .arg(
61 .arg(
64 Arg::with_name("clean")
62 Arg::with_name("clean")
65 .help("show only clean files")
63 .help("show only clean files")
66 .short("-c")
64 .short("-c")
67 .long("--clean"),
65 .long("--clean"),
68 )
66 )
69 .arg(
67 .arg(
70 Arg::with_name("deleted")
68 Arg::with_name("deleted")
71 .help("show only deleted files")
69 .help("show only deleted files")
72 .short("-d")
70 .short("-d")
73 .long("--deleted"),
71 .long("--deleted"),
74 )
72 )
75 .arg(
73 .arg(
76 Arg::with_name("unknown")
74 Arg::with_name("unknown")
77 .help("show only unknown (not tracked) files")
75 .help("show only unknown (not tracked) files")
78 .short("-u")
76 .short("-u")
79 .long("--unknown"),
77 .long("--unknown"),
80 )
78 )
81 .arg(
79 .arg(
82 Arg::with_name("ignored")
80 Arg::with_name("ignored")
83 .help("show only ignored files")
81 .help("show only ignored files")
84 .short("-i")
82 .short("-i")
85 .long("--ignored"),
83 .long("--ignored"),
86 )
84 )
87 }
85 }
88
86
89 /// Pure data type allowing the caller to specify file states to display
87 /// Pure data type allowing the caller to specify file states to display
90 #[derive(Copy, Clone, Debug)]
88 #[derive(Copy, Clone, Debug)]
91 pub struct DisplayStates {
89 pub struct DisplayStates {
92 pub modified: bool,
90 pub modified: bool,
93 pub added: bool,
91 pub added: bool,
94 pub removed: bool,
92 pub removed: bool,
95 pub clean: bool,
93 pub clean: bool,
96 pub deleted: bool,
94 pub deleted: bool,
97 pub unknown: bool,
95 pub unknown: bool,
98 pub ignored: bool,
96 pub ignored: bool,
99 }
97 }
100
98
101 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
99 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
102 modified: true,
100 modified: true,
103 added: true,
101 added: true,
104 removed: true,
102 removed: true,
105 clean: false,
103 clean: false,
106 deleted: true,
104 deleted: true,
107 unknown: true,
105 unknown: true,
108 ignored: false,
106 ignored: false,
109 };
107 };
110
108
111 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
109 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
112 modified: true,
110 modified: true,
113 added: true,
111 added: true,
114 removed: true,
112 removed: true,
115 clean: true,
113 clean: true,
116 deleted: true,
114 deleted: true,
117 unknown: true,
115 unknown: true,
118 ignored: true,
116 ignored: true,
119 };
117 };
120
118
121 impl DisplayStates {
119 impl DisplayStates {
122 pub fn is_empty(&self) -> bool {
120 pub fn is_empty(&self) -> bool {
123 !(self.modified
121 !(self.modified
124 || self.added
122 || self.added
125 || self.removed
123 || self.removed
126 || self.clean
124 || self.clean
127 || self.deleted
125 || self.deleted
128 || self.unknown
126 || self.unknown
129 || self.ignored)
127 || self.ignored)
130 }
128 }
131 }
129 }
132
130
133 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
131 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
134 let status_enabled_default = false;
132 let status_enabled_default = false;
135 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
133 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
136 if !status_enabled.unwrap_or(status_enabled_default) {
134 if !status_enabled.unwrap_or(status_enabled_default) {
137 return Err(CommandError::unsupported(
135 return Err(CommandError::unsupported(
138 "status is experimental in rhg (enable it with 'rhg.status = true' \
136 "status is experimental in rhg (enable it with 'rhg.status = true' \
139 or enable fallback with 'rhg.on-unsupported = fallback')"
137 or enable fallback with 'rhg.on-unsupported = fallback')"
140 ));
138 ));
141 }
139 }
142
140
143 let ui = invocation.ui;
141 let ui = invocation.ui;
144 let args = invocation.subcommand_args;
142 let args = invocation.subcommand_args;
145 let display_states = if args.is_present("all") {
143 let display_states = if args.is_present("all") {
146 // TODO when implementing `--quiet`: it excludes clean files
144 // TODO when implementing `--quiet`: it excludes clean files
147 // from `--all`
145 // from `--all`
148 ALL_DISPLAY_STATES
146 ALL_DISPLAY_STATES
149 } else {
147 } else {
150 let requested = DisplayStates {
148 let requested = DisplayStates {
151 modified: args.is_present("modified"),
149 modified: args.is_present("modified"),
152 added: args.is_present("added"),
150 added: args.is_present("added"),
153 removed: args.is_present("removed"),
151 removed: args.is_present("removed"),
154 clean: args.is_present("clean"),
152 clean: args.is_present("clean"),
155 deleted: args.is_present("deleted"),
153 deleted: args.is_present("deleted"),
156 unknown: args.is_present("unknown"),
154 unknown: args.is_present("unknown"),
157 ignored: args.is_present("ignored"),
155 ignored: args.is_present("ignored"),
158 };
156 };
159 if requested.is_empty() {
157 if requested.is_empty() {
160 DEFAULT_DISPLAY_STATES
158 DEFAULT_DISPLAY_STATES
161 } else {
159 } else {
162 requested
160 requested
163 }
161 }
164 };
162 };
165
163
166 let repo = invocation.repo?;
164 let repo = invocation.repo?;
167 let mut dmap = repo.dirstate_map_mut()?;
165 let mut dmap = repo.dirstate_map_mut()?;
168
166
169 let options = StatusOptions {
167 let options = StatusOptions {
170 // TODO should be provided by the dirstate parsing and
168 // TODO should be provided by the dirstate parsing and
171 // hence be stored on dmap. Using a value that assumes we aren't
169 // hence be stored on dmap. Using a value that assumes we aren't
172 // below the time resolution granularity of the FS and the
170 // below the time resolution granularity of the FS and the
173 // dirstate.
171 // dirstate.
174 last_normal_time: 0,
172 last_normal_time: 0,
175 // we're currently supporting file systems with exec flags only
173 // we're currently supporting file systems with exec flags only
176 // anyway
174 // anyway
177 check_exec: true,
175 check_exec: true,
178 list_clean: display_states.clean,
176 list_clean: display_states.clean,
179 list_unknown: display_states.unknown,
177 list_unknown: display_states.unknown,
180 list_ignored: display_states.ignored,
178 list_ignored: display_states.ignored,
181 collect_traversed_dirs: false,
179 collect_traversed_dirs: false,
182 };
180 };
183 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
181 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
184 let (mut ds_status, pattern_warnings) = dmap.status(
182 let (mut ds_status, pattern_warnings) = dmap.status(
185 &AlwaysMatcher,
183 &AlwaysMatcher,
186 repo.working_directory_path().to_owned(),
184 repo.working_directory_path().to_owned(),
187 vec![ignore_file],
185 vec![ignore_file],
188 options,
186 options,
189 )?;
187 )?;
190 if !pattern_warnings.is_empty() {
188 if !pattern_warnings.is_empty() {
191 warn!("Pattern warnings: {:?}", &pattern_warnings);
189 warn!("Pattern warnings: {:?}", &pattern_warnings);
192 }
190 }
193
191
194 if !ds_status.bad.is_empty() {
192 if !ds_status.bad.is_empty() {
195 warn!("Bad matches {:?}", &(ds_status.bad))
193 warn!("Bad matches {:?}", &(ds_status.bad))
196 }
194 }
197 if !ds_status.unsure.is_empty() {
195 if !ds_status.unsure.is_empty() {
198 info!(
196 info!(
199 "Files to be rechecked by retrieval from filelog: {:?}",
197 "Files to be rechecked by retrieval from filelog: {:?}",
200 &ds_status.unsure
198 &ds_status.unsure
201 );
199 );
202 }
200 }
203 if !ds_status.unsure.is_empty()
201 if !ds_status.unsure.is_empty()
204 && (display_states.modified || display_states.clean)
202 && (display_states.modified || display_states.clean)
205 {
203 {
206 let p1: Node = repo.dirstate_parents()?.p1.into();
204 let p1 = repo.dirstate_parents()?.p1;
207 let p1_hex = format!("{:x}", p1);
205 let manifest = repo.manifest_for_node(p1).map_err(|e| {
206 CommandError::from((e, &*format!("{:x}", p1.short())))
207 })?;
208 for to_check in ds_status.unsure {
208 for to_check in ds_status.unsure {
209 if cat_file_is_modified(repo, &to_check, &p1_hex)? {
209 if cat_file_is_modified(repo, &manifest, &to_check)? {
210 if display_states.modified {
210 if display_states.modified {
211 ds_status.modified.push(to_check);
211 ds_status.modified.push(to_check);
212 }
212 }
213 } else {
213 } else {
214 if display_states.clean {
214 if display_states.clean {
215 ds_status.clean.push(to_check);
215 ds_status.clean.push(to_check);
216 }
216 }
217 }
217 }
218 }
218 }
219 }
219 }
220 if display_states.modified {
220 if display_states.modified {
221 display_status_paths(ui, &mut ds_status.modified, b"M")?;
221 display_status_paths(ui, &mut ds_status.modified, b"M")?;
222 }
222 }
223 if display_states.added {
223 if display_states.added {
224 display_status_paths(ui, &mut ds_status.added, b"A")?;
224 display_status_paths(ui, &mut ds_status.added, b"A")?;
225 }
225 }
226 if display_states.removed {
226 if display_states.removed {
227 display_status_paths(ui, &mut ds_status.removed, b"R")?;
227 display_status_paths(ui, &mut ds_status.removed, b"R")?;
228 }
228 }
229 if display_states.deleted {
229 if display_states.deleted {
230 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
230 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
231 }
231 }
232 if display_states.unknown {
232 if display_states.unknown {
233 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
233 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
234 }
234 }
235 if display_states.ignored {
235 if display_states.ignored {
236 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
236 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
237 }
237 }
238 if display_states.clean {
238 if display_states.clean {
239 display_status_paths(ui, &mut ds_status.clean, b"C")?;
239 display_status_paths(ui, &mut ds_status.clean, b"C")?;
240 }
240 }
241 Ok(())
241 Ok(())
242 }
242 }
243
243
244 // Probably more elegant to use a Deref or Borrow trait rather than
244 // Probably more elegant to use a Deref or Borrow trait rather than
245 // harcode HgPathBuf, but probably not really useful at this point
245 // harcode HgPathBuf, but probably not really useful at this point
246 fn display_status_paths(
246 fn display_status_paths(
247 ui: &Ui,
247 ui: &Ui,
248 paths: &mut [HgPathCow],
248 paths: &mut [HgPathCow],
249 status_prefix: &[u8],
249 status_prefix: &[u8],
250 ) -> Result<(), CommandError> {
250 ) -> Result<(), CommandError> {
251 paths.sort_unstable();
251 paths.sort_unstable();
252 for path in paths {
252 for path in paths {
253 // Same TODO as in commands::root
253 // Same TODO as in commands::root
254 let bytes: &[u8] = path.as_bytes();
254 let bytes: &[u8] = path.as_bytes();
255 // TODO optim, probably lots of unneeded copies here, especially
255 // TODO optim, probably lots of unneeded copies here, especially
256 // if out stream is buffered
256 // if out stream is buffered
257 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
257 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
258 }
258 }
259 Ok(())
259 Ok(())
260 }
260 }
261
261
262 /// Check if a file is modified by comparing actual repo store and file system.
262 /// Check if a file is modified by comparing actual repo store and file system.
263 ///
263 ///
264 /// This meant to be used for those that the dirstate cannot resolve, due
264 /// This meant to be used for those that the dirstate cannot resolve, due
265 /// to time resolution limits.
265 /// to time resolution limits.
266 ///
266 ///
267 /// TODO: detect permission bits and similar metadata modifications
267 /// TODO: detect permission bits and similar metadata modifications
268 fn cat_file_is_modified(
268 fn cat_file_is_modified(
269 repo: &Repo,
269 repo: &Repo,
270 manifest: &Manifest,
270 hg_path: &HgPath,
271 hg_path: &HgPath,
271 rev: &str,
272 ) -> Result<bool, HgError> {
272 ) -> Result<bool, CommandError> {
273 let file_node = manifest
273 // TODO CatRev expects &[HgPathBuf], something like
274 .find_file(hg_path)?
274 // &[impl Deref<HgPath>] would be nicer and should avoid the copy
275 .expect("ambgious file not in p1");
275 let path_bufs = [hg_path.into()];
276 let filelog = repo.filelog(hg_path)?;
276 // TODO IIUC CatRev returns a simple Vec<u8> for all files
277 let filelog_entry = filelog.get_node(file_node).map_err(|_| {
277 // being able to tell them apart as (path, bytes) would be nicer
278 HgError::corrupted("filelog missing node from manifest")
278 // and OPTIM would allow manifest resolution just once.
279 })?;
279 let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?;
280 let contents_in_p1 = filelog_entry.data()?;
280
281
281 let fs_path = repo
282 let fs_path = repo
282 .working_directory_vfs()
283 .working_directory_vfs()
283 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
284 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
284 let hg_data_len: u64 = match output.concatenated.len().try_into() {
285 let hg_data_len: u64 = match contents_in_p1.len().try_into() {
285 Ok(v) => v,
286 Ok(v) => v,
286 Err(_) => {
287 Err(_) => {
287 // conversion of data length to u64 failed,
288 // conversion of data length to u64 failed,
288 // good luck for any file to have this content
289 // good luck for any file to have this content
289 return Ok(true);
290 return Ok(true);
290 }
291 }
291 };
292 };
292 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
293 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
293 if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len
294 if fobj.metadata().when_reading_file(&fs_path)?.len() != hg_data_len {
294 {
295 return Ok(true);
295 return Ok(true);
296 }
296 }
297 for (fs_byte, hg_byte) in
297 for (fs_byte, &hg_byte) in BufReader::new(fobj).bytes().zip(contents_in_p1)
298 BufReader::new(fobj).bytes().zip(output.concatenated)
299 {
298 {
300 if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte {
299 if fs_byte.when_reading_file(&fs_path)? != hg_byte {
301 return Ok(true);
300 return Ok(true);
302 }
301 }
303 }
302 }
304 Ok(false)
303 Ok(false)
305 }
304 }
General Comments 0
You need to be logged in to leave comments. Login now