##// END OF EJS Templates
rust: Fold find_root and check_requirements into Repo::find...
Simon Sapin -
r47175:1dcd9c99 default
parent child Browse files
Show More
@@ -1,197 +1,196 b''
1 // config.rs
1 // config.rs
2 //
2 //
3 // Copyright 2020
3 // Copyright 2020
4 // Valentin Gatien-Baron,
4 // Valentin Gatien-Baron,
5 // Raphaël Gomès <rgomes@octobus.net>
5 // Raphaël Gomès <rgomes@octobus.net>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 use super::layer;
10 use super::layer;
11 use crate::config::layer::{ConfigError, ConfigLayer, ConfigValue};
11 use crate::config::layer::{ConfigError, ConfigLayer, ConfigValue};
12 use std::path::PathBuf;
12 use std::path::PathBuf;
13
13
14 use crate::operations::find_root;
14 use crate::repo::Repo;
15 use crate::utils::files::read_whole_file;
15 use crate::utils::files::read_whole_file;
16
16
17 /// Holds the config values for the current repository
17 /// Holds the config values for the current repository
18 /// TODO update this docstring once we support more sources
18 /// TODO update this docstring once we support more sources
19 pub struct Config {
19 pub struct Config {
20 layers: Vec<layer::ConfigLayer>,
20 layers: Vec<layer::ConfigLayer>,
21 }
21 }
22
22
23 impl std::fmt::Debug for Config {
23 impl std::fmt::Debug for Config {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 for (index, layer) in self.layers.iter().rev().enumerate() {
25 for (index, layer) in self.layers.iter().rev().enumerate() {
26 write!(
26 write!(
27 f,
27 f,
28 "==== Layer {} (trusted: {}) ====\n{:?}",
28 "==== Layer {} (trusted: {}) ====\n{:?}",
29 index, layer.trusted, layer
29 index, layer.trusted, layer
30 )?;
30 )?;
31 }
31 }
32 Ok(())
32 Ok(())
33 }
33 }
34 }
34 }
35
35
36 pub enum ConfigSource {
36 pub enum ConfigSource {
37 /// Absolute path to a config file
37 /// Absolute path to a config file
38 AbsPath(PathBuf),
38 AbsPath(PathBuf),
39 /// Already parsed (from the CLI, env, Python resources, etc.)
39 /// Already parsed (from the CLI, env, Python resources, etc.)
40 Parsed(layer::ConfigLayer),
40 Parsed(layer::ConfigLayer),
41 }
41 }
42
42
43 pub fn parse_bool(v: &[u8]) -> Option<bool> {
43 pub fn parse_bool(v: &[u8]) -> Option<bool> {
44 match v.to_ascii_lowercase().as_slice() {
44 match v.to_ascii_lowercase().as_slice() {
45 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
45 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
46 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
46 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
47 _ => None,
47 _ => None,
48 }
48 }
49 }
49 }
50
50
51 impl Config {
51 impl Config {
52 /// Loads in order, which means that the precedence is the same
52 /// Loads in order, which means that the precedence is the same
53 /// as the order of `sources`.
53 /// as the order of `sources`.
54 pub fn load_from_explicit_sources(
54 pub fn load_from_explicit_sources(
55 sources: Vec<ConfigSource>,
55 sources: Vec<ConfigSource>,
56 ) -> Result<Self, ConfigError> {
56 ) -> Result<Self, ConfigError> {
57 let mut layers = vec![];
57 let mut layers = vec![];
58
58
59 for source in sources.into_iter() {
59 for source in sources.into_iter() {
60 match source {
60 match source {
61 ConfigSource::Parsed(c) => layers.push(c),
61 ConfigSource::Parsed(c) => layers.push(c),
62 ConfigSource::AbsPath(c) => {
62 ConfigSource::AbsPath(c) => {
63 // TODO check if it should be trusted
63 // TODO check if it should be trusted
64 // mercurial/ui.py:427
64 // mercurial/ui.py:427
65 let data = match read_whole_file(&c) {
65 let data = match read_whole_file(&c) {
66 Err(_) => continue, // same as the python code
66 Err(_) => continue, // same as the python code
67 Ok(data) => data,
67 Ok(data) => data,
68 };
68 };
69 layers.extend(ConfigLayer::parse(&c, &data)?)
69 layers.extend(ConfigLayer::parse(&c, &data)?)
70 }
70 }
71 }
71 }
72 }
72 }
73
73
74 Ok(Config { layers })
74 Ok(Config { layers })
75 }
75 }
76
76
77 /// Loads the local config. In a future version, this will also load the
77 /// Loads the local config. In a future version, this will also load the
78 /// `$HOME/.hgrc` and more to mirror the Python implementation.
78 /// `$HOME/.hgrc` and more to mirror the Python implementation.
79 pub fn load() -> Result<Self, ConfigError> {
79 pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> {
80 let root = find_root().unwrap();
81 Ok(Self::load_from_explicit_sources(vec![
80 Ok(Self::load_from_explicit_sources(vec![
82 ConfigSource::AbsPath(root.join(".hg/hgrc")),
81 ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")),
83 ])?)
82 ])?)
84 }
83 }
85
84
86 /// Returns an `Err` if the first value found is not a valid boolean.
85 /// Returns an `Err` if the first value found is not a valid boolean.
87 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
86 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
88 /// found, or `None`.
87 /// found, or `None`.
89 pub fn get_option(
88 pub fn get_option(
90 &self,
89 &self,
91 section: &[u8],
90 section: &[u8],
92 item: &[u8],
91 item: &[u8],
93 ) -> Result<Option<bool>, ConfigError> {
92 ) -> Result<Option<bool>, ConfigError> {
94 match self.get_inner(&section, &item) {
93 match self.get_inner(&section, &item) {
95 Some((layer, v)) => match parse_bool(&v.bytes) {
94 Some((layer, v)) => match parse_bool(&v.bytes) {
96 Some(b) => Ok(Some(b)),
95 Some(b) => Ok(Some(b)),
97 None => Err(ConfigError::Parse {
96 None => Err(ConfigError::Parse {
98 origin: layer.origin.to_owned(),
97 origin: layer.origin.to_owned(),
99 line: v.line,
98 line: v.line,
100 bytes: v.bytes.to_owned(),
99 bytes: v.bytes.to_owned(),
101 }),
100 }),
102 },
101 },
103 None => Ok(None),
102 None => Ok(None),
104 }
103 }
105 }
104 }
106
105
107 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
106 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
108 /// if the value is not found, an `Err` if it's not a valid boolean.
107 /// if the value is not found, an `Err` if it's not a valid boolean.
109 pub fn get_bool(
108 pub fn get_bool(
110 &self,
109 &self,
111 section: &[u8],
110 section: &[u8],
112 item: &[u8],
111 item: &[u8],
113 ) -> Result<bool, ConfigError> {
112 ) -> Result<bool, ConfigError> {
114 Ok(self.get_option(section, item)?.unwrap_or(false))
113 Ok(self.get_option(section, item)?.unwrap_or(false))
115 }
114 }
116
115
117 /// Returns the raw value bytes of the first one found, or `None`.
116 /// Returns the raw value bytes of the first one found, or `None`.
118 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
117 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
119 self.get_inner(section, item)
118 self.get_inner(section, item)
120 .map(|(_, value)| value.bytes.as_ref())
119 .map(|(_, value)| value.bytes.as_ref())
121 }
120 }
122
121
123 /// Returns the layer and the value of the first one found, or `None`.
122 /// Returns the layer and the value of the first one found, or `None`.
124 fn get_inner(
123 fn get_inner(
125 &self,
124 &self,
126 section: &[u8],
125 section: &[u8],
127 item: &[u8],
126 item: &[u8],
128 ) -> Option<(&ConfigLayer, &ConfigValue)> {
127 ) -> Option<(&ConfigLayer, &ConfigValue)> {
129 for layer in self.layers.iter().rev() {
128 for layer in self.layers.iter().rev() {
130 if !layer.trusted {
129 if !layer.trusted {
131 continue;
130 continue;
132 }
131 }
133 if let Some(v) = layer.get(&section, &item) {
132 if let Some(v) = layer.get(&section, &item) {
134 return Some((&layer, v));
133 return Some((&layer, v));
135 }
134 }
136 }
135 }
137 None
136 None
138 }
137 }
139
138
140 /// Get raw values bytes from all layers (even untrusted ones) in order
139 /// Get raw values bytes from all layers (even untrusted ones) in order
141 /// of precedence.
140 /// of precedence.
142 #[cfg(test)]
141 #[cfg(test)]
143 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
142 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
144 let mut res = vec![];
143 let mut res = vec![];
145 for layer in self.layers.iter().rev() {
144 for layer in self.layers.iter().rev() {
146 if let Some(v) = layer.get(&section, &item) {
145 if let Some(v) = layer.get(&section, &item) {
147 res.push(v.bytes.as_ref());
146 res.push(v.bytes.as_ref());
148 }
147 }
149 }
148 }
150 res
149 res
151 }
150 }
152 }
151 }
153
152
154 #[cfg(test)]
153 #[cfg(test)]
155 mod tests {
154 mod tests {
156 use super::*;
155 use super::*;
157 use pretty_assertions::assert_eq;
156 use pretty_assertions::assert_eq;
158 use std::fs::File;
157 use std::fs::File;
159 use std::io::Write;
158 use std::io::Write;
160
159
161 #[test]
160 #[test]
162 fn test_include_layer_ordering() {
161 fn test_include_layer_ordering() {
163 let tmpdir = tempfile::tempdir().unwrap();
162 let tmpdir = tempfile::tempdir().unwrap();
164 let tmpdir_path = tmpdir.path();
163 let tmpdir_path = tmpdir.path();
165 let mut included_file =
164 let mut included_file =
166 File::create(&tmpdir_path.join("included.rc")).unwrap();
165 File::create(&tmpdir_path.join("included.rc")).unwrap();
167
166
168 included_file.write_all(b"[section]\nitem=value1").unwrap();
167 included_file.write_all(b"[section]\nitem=value1").unwrap();
169 let base_config_path = tmpdir_path.join("base.rc");
168 let base_config_path = tmpdir_path.join("base.rc");
170 let mut config_file = File::create(&base_config_path).unwrap();
169 let mut config_file = File::create(&base_config_path).unwrap();
171 let data =
170 let data =
172 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
171 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
173 config_file.write_all(data).unwrap();
172 config_file.write_all(data).unwrap();
174
173
175 let sources = vec![ConfigSource::AbsPath(base_config_path)];
174 let sources = vec![ConfigSource::AbsPath(base_config_path)];
176 let config = Config::load_from_explicit_sources(sources)
175 let config = Config::load_from_explicit_sources(sources)
177 .expect("expected valid config");
176 .expect("expected valid config");
178
177
179 dbg!(&config);
178 dbg!(&config);
180
179
181 let (_, value) = config.get_inner(b"section", b"item").unwrap();
180 let (_, value) = config.get_inner(b"section", b"item").unwrap();
182 assert_eq!(
181 assert_eq!(
183 value,
182 value,
184 &ConfigValue {
183 &ConfigValue {
185 bytes: b"value2".to_vec(),
184 bytes: b"value2".to_vec(),
186 line: Some(4)
185 line: Some(4)
187 }
186 }
188 );
187 );
189
188
190 let value = config.get(b"section", b"item").unwrap();
189 let value = config.get(b"section", b"item").unwrap();
191 assert_eq!(value, b"value2",);
190 assert_eq!(value, b"value2",);
192 assert_eq!(
191 assert_eq!(
193 config.get_all(b"section", b"item"),
192 config.get_all(b"section", b"item"),
194 [b"value2", b"value1", b"value0"]
193 [b"value2", b"value1", b"value0"]
195 );
194 );
196 }
195 }
197 }
196 }
@@ -1,14 +1,12 b''
1 //! A distinction is made between operations and commands.
1 //! A distinction is made between operations and commands.
2 //! An operation is what can be done whereas a command is what is exposed by
2 //! An operation is what can be done whereas a command is what is exposed by
3 //! the cli. A single command can use several operations to achieve its goal.
3 //! the cli. A single command can use several operations to achieve its goal.
4
4
5 mod cat;
5 mod cat;
6 mod debugdata;
6 mod debugdata;
7 mod dirstate_status;
7 mod dirstate_status;
8 mod find_root;
9 mod list_tracked_files;
8 mod list_tracked_files;
10 pub use cat::cat;
9 pub use cat::cat;
11 pub use debugdata::{debug_data, DebugDataKind};
10 pub use debugdata::{debug_data, DebugDataKind};
12 pub use find_root::{find_root, find_root_from_path, FindRootError};
13 pub use list_tracked_files::Dirstate;
11 pub use list_tracked_files::Dirstate;
14 pub use list_tracked_files::{list_rev_tracked_files, FilesForRev};
12 pub use list_tracked_files::{list_rev_tracked_files, FilesForRev};
@@ -1,86 +1,100 b''
1 use crate::errors::{HgError, IoResultExt};
1 use crate::errors::{HgError, IoResultExt};
2 use crate::operations::{find_root, FindRootError};
3 use crate::requirements;
2 use crate::requirements;
4 use memmap::{Mmap, MmapOptions};
3 use memmap::{Mmap, MmapOptions};
5 use std::path::{Path, PathBuf};
4 use std::path::{Path, PathBuf};
6
5
7 /// A repository on disk
6 /// A repository on disk
8 pub struct Repo {
7 pub struct Repo {
9 working_directory: PathBuf,
8 working_directory: PathBuf,
10 dot_hg: PathBuf,
9 dot_hg: PathBuf,
11 store: PathBuf,
10 store: PathBuf,
12 }
11 }
13
12
13 #[derive(Debug, derive_more::From)]
14 pub enum RepoFindError {
15 NotFoundInCurrentDirectoryOrAncestors {
16 current_directory: PathBuf,
17 },
18 #[from]
19 Other(HgError),
20 }
21
14 /// Filesystem access abstraction for the contents of a given "base" diretory
22 /// Filesystem access abstraction for the contents of a given "base" diretory
15 #[derive(Clone, Copy)]
23 #[derive(Clone, Copy)]
16 pub(crate) struct Vfs<'a> {
24 pub(crate) struct Vfs<'a> {
17 base: &'a Path,
25 base: &'a Path,
18 }
26 }
19
27
20 impl Repo {
28 impl Repo {
21 /// Returns `None` if the given path doesn’t look like a repository
29 /// Search the current directory and its ancestores for a repository:
22 /// (doesn’t contain a `.hg` sub-directory).
30 /// a working directory that contains a `.hg` sub-directory.
23 pub fn for_path(root: impl Into<PathBuf>) -> Self {
31 pub fn find() -> Result<Self, RepoFindError> {
24 let working_directory = root.into();
32 let current_directory = crate::utils::current_dir()?;
25 let dot_hg = working_directory.join(".hg");
33 // ancestors() is inclusive: it first yields `current_directory` as-is.
26 Self {
34 for ancestor in current_directory.ancestors() {
27 store: dot_hg.join("store"),
35 let dot_hg = ancestor.join(".hg");
28 dot_hg,
36 if dot_hg.is_dir() {
29 working_directory,
37 let repo = Self {
38 store: dot_hg.join("store"),
39 dot_hg,
40 working_directory: ancestor.to_owned(),
41 };
42 requirements::check(&repo)?;
43 return Ok(repo);
44 }
30 }
45 }
31 }
46 Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
32
47 current_directory,
33 pub fn find() -> Result<Self, FindRootError> {
48 })
34 find_root().map(Self::for_path)
35 }
36
37 pub fn check_requirements(&self) -> Result<(), HgError> {
38 requirements::check(self)
39 }
49 }
40
50
41 pub fn working_directory_path(&self) -> &Path {
51 pub fn working_directory_path(&self) -> &Path {
42 &self.working_directory
52 &self.working_directory
43 }
53 }
44
54
45 /// For accessing repository files (in `.hg`), except for the store
55 /// For accessing repository files (in `.hg`), except for the store
46 /// (`.hg/store`).
56 /// (`.hg/store`).
47 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
57 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
48 Vfs { base: &self.dot_hg }
58 Vfs { base: &self.dot_hg }
49 }
59 }
50
60
51 /// For accessing repository store files (in `.hg/store`)
61 /// For accessing repository store files (in `.hg/store`)
52 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
62 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
53 Vfs { base: &self.store }
63 Vfs { base: &self.store }
54 }
64 }
55
65
56 /// For accessing the working copy
66 /// For accessing the working copy
57
67
58 // The undescore prefix silences the "never used" warning. Remove before
68 // The undescore prefix silences the "never used" warning. Remove before
59 // using.
69 // using.
60 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
70 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
61 Vfs {
71 Vfs {
62 base: &self.working_directory,
72 base: &self.working_directory,
63 }
73 }
64 }
74 }
65 }
75 }
66
76
67 impl Vfs<'_> {
77 impl Vfs<'_> {
78 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
79 self.base.join(relative_path)
80 }
81
68 pub(crate) fn read(
82 pub(crate) fn read(
69 &self,
83 &self,
70 relative_path: impl AsRef<Path>,
84 relative_path: impl AsRef<Path>,
71 ) -> Result<Vec<u8>, HgError> {
85 ) -> Result<Vec<u8>, HgError> {
72 let path = self.base.join(relative_path);
86 let path = self.join(relative_path);
73 std::fs::read(&path).for_file(&path)
87 std::fs::read(&path).for_file(&path)
74 }
88 }
75
89
76 pub(crate) fn mmap_open(
90 pub(crate) fn mmap_open(
77 &self,
91 &self,
78 relative_path: impl AsRef<Path>,
92 relative_path: impl AsRef<Path>,
79 ) -> Result<Mmap, HgError> {
93 ) -> Result<Mmap, HgError> {
80 let path = self.base.join(relative_path);
94 let path = self.base.join(relative_path);
81 let file = std::fs::File::open(&path).for_file(&path)?;
95 let file = std::fs::File::open(&path).for_file(&path)?;
82 // TODO: what are the safety requirements here?
96 // TODO: what are the safety requirements here?
83 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
97 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
84 Ok(mmap)
98 Ok(mmap)
85 }
99 }
86 }
100 }
@@ -1,58 +1,57 b''
1 use crate::commands::Command;
1 use crate::commands::Command;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::Ui;
3 use crate::ui::Ui;
4 use hg::operations::cat;
4 use hg::operations::cat;
5 use hg::repo::Repo;
5 use hg::repo::Repo;
6 use hg::utils::hg_path::HgPathBuf;
6 use hg::utils::hg_path::HgPathBuf;
7 use micro_timer::timed;
7 use micro_timer::timed;
8 use std::convert::TryFrom;
8 use std::convert::TryFrom;
9
9
10 pub const HELP_TEXT: &str = "
10 pub const HELP_TEXT: &str = "
11 Output the current or given revision of files
11 Output the current or given revision of files
12 ";
12 ";
13
13
14 pub struct CatCommand<'a> {
14 pub struct CatCommand<'a> {
15 rev: Option<&'a str>,
15 rev: Option<&'a str>,
16 files: Vec<&'a str>,
16 files: Vec<&'a str>,
17 }
17 }
18
18
19 impl<'a> CatCommand<'a> {
19 impl<'a> CatCommand<'a> {
20 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
20 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
21 Self { rev, files }
21 Self { rev, files }
22 }
22 }
23
23
24 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
24 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
25 ui.write_stdout(data)?;
25 ui.write_stdout(data)?;
26 Ok(())
26 Ok(())
27 }
27 }
28 }
28 }
29
29
30 impl<'a> Command for CatCommand<'a> {
30 impl<'a> Command for CatCommand<'a> {
31 #[timed]
31 #[timed]
32 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
32 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
33 let repo = Repo::find()?;
33 let repo = Repo::find()?;
34 repo.check_requirements()?;
35 let cwd = hg::utils::current_dir()?;
34 let cwd = hg::utils::current_dir()?;
36
35
37 let mut files = vec![];
36 let mut files = vec![];
38 for file in self.files.iter() {
37 for file in self.files.iter() {
39 // TODO: actually normalize `..` path segments etc?
38 // TODO: actually normalize `..` path segments etc?
40 let normalized = cwd.join(&file);
39 let normalized = cwd.join(&file);
41 let stripped = normalized
40 let stripped = normalized
42 .strip_prefix(&repo.working_directory_path())
41 .strip_prefix(&repo.working_directory_path())
43 // TODO: error message for path arguments outside of the repo
42 // TODO: error message for path arguments outside of the repo
44 .map_err(|_| CommandError::abort(""))?;
43 .map_err(|_| CommandError::abort(""))?;
45 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
44 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
46 .map_err(|e| CommandError::abort(e.to_string()))?;
45 .map_err(|e| CommandError::abort(e.to_string()))?;
47 files.push(hg_file);
46 files.push(hg_file);
48 }
47 }
49
48
50 match self.rev {
49 match self.rev {
51 Some(rev) => {
50 Some(rev) => {
52 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
51 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
53 self.display(ui, &data)
52 self.display(ui, &data)
54 }
53 }
55 None => Err(CommandError::Unimplemented.into()),
54 None => Err(CommandError::Unimplemented.into()),
56 }
55 }
57 }
56 }
58 }
57 }
@@ -1,62 +1,61 b''
1 use crate::commands::Command;
1 use crate::commands::Command;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::Ui;
3 use crate::ui::Ui;
4 use hg::operations::list_rev_tracked_files;
4 use hg::operations::list_rev_tracked_files;
5 use hg::operations::Dirstate;
5 use hg::operations::Dirstate;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::files::{get_bytes_from_path, relativize_path};
7 use hg::utils::files::{get_bytes_from_path, relativize_path};
8 use hg::utils::hg_path::{HgPath, HgPathBuf};
8 use hg::utils::hg_path::{HgPath, HgPathBuf};
9
9
10 pub const HELP_TEXT: &str = "
10 pub const HELP_TEXT: &str = "
11 List tracked files.
11 List tracked files.
12
12
13 Returns 0 on success.
13 Returns 0 on success.
14 ";
14 ";
15
15
16 pub struct FilesCommand<'a> {
16 pub struct FilesCommand<'a> {
17 rev: Option<&'a str>,
17 rev: Option<&'a str>,
18 }
18 }
19
19
20 impl<'a> FilesCommand<'a> {
20 impl<'a> FilesCommand<'a> {
21 pub fn new(rev: Option<&'a str>) -> Self {
21 pub fn new(rev: Option<&'a str>) -> Self {
22 FilesCommand { rev }
22 FilesCommand { rev }
23 }
23 }
24
24
25 fn display_files(
25 fn display_files(
26 &self,
26 &self,
27 ui: &Ui,
27 ui: &Ui,
28 repo: &Repo,
28 repo: &Repo,
29 files: impl IntoIterator<Item = &'a HgPath>,
29 files: impl IntoIterator<Item = &'a HgPath>,
30 ) -> Result<(), CommandError> {
30 ) -> Result<(), CommandError> {
31 let cwd = hg::utils::current_dir()?;
31 let cwd = hg::utils::current_dir()?;
32 let rooted_cwd = cwd
32 let rooted_cwd = cwd
33 .strip_prefix(repo.working_directory_path())
33 .strip_prefix(repo.working_directory_path())
34 .expect("cwd was already checked within the repository");
34 .expect("cwd was already checked within the repository");
35 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
35 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
36
36
37 let mut stdout = ui.stdout_buffer();
37 let mut stdout = ui.stdout_buffer();
38
38
39 for file in files {
39 for file in files {
40 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
40 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
41 stdout.write_all(b"\n")?;
41 stdout.write_all(b"\n")?;
42 }
42 }
43 stdout.flush()?;
43 stdout.flush()?;
44 Ok(())
44 Ok(())
45 }
45 }
46 }
46 }
47
47
48 impl<'a> Command for FilesCommand<'a> {
48 impl<'a> Command for FilesCommand<'a> {
49 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
49 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
50 let repo = Repo::find()?;
50 let repo = Repo::find()?;
51 repo.check_requirements()?;
52 if let Some(rev) = self.rev {
51 if let Some(rev) = self.rev {
53 let files =
52 let files =
54 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
53 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
55 self.display_files(ui, &repo, files.iter())
54 self.display_files(ui, &repo, files.iter())
56 } else {
55 } else {
57 let distate = Dirstate::new(&repo)?;
56 let distate = Dirstate::new(&repo)?;
58 let files = distate.tracked_files()?;
57 let files = distate.tracked_files()?;
59 self.display_files(ui, &repo, files)
58 self.display_files(ui, &repo, files)
60 }
59 }
61 }
60 }
62 }
61 }
@@ -1,81 +1,83 b''
1 use crate::ui::utf8_to_local;
1 use crate::ui::utf8_to_local;
2 use crate::ui::UiError;
2 use crate::ui::UiError;
3 use hg::errors::{HgError, IoErrorContext};
3 use format_bytes::format_bytes;
4 use hg::operations::FindRootError;
4 use hg::errors::HgError;
5 use hg::repo::RepoFindError;
5 use hg::revlog::revlog::RevlogError;
6 use hg::revlog::revlog::RevlogError;
7 use hg::utils::files::get_bytes_from_path;
6 use std::convert::From;
8 use std::convert::From;
7
9
8 /// The kind of command error
10 /// The kind of command error
9 #[derive(Debug)]
11 #[derive(Debug)]
10 pub enum CommandError {
12 pub enum CommandError {
11 /// Exit with an error message and "standard" failure exit code.
13 /// Exit with an error message and "standard" failure exit code.
12 Abort { message: Vec<u8> },
14 Abort { message: Vec<u8> },
13
15
14 /// A mercurial capability as not been implemented.
16 /// A mercurial capability as not been implemented.
15 ///
17 ///
16 /// There is no error message printed in this case.
18 /// There is no error message printed in this case.
17 /// Instead, we exit with a specic status code and a wrapper script may
19 /// Instead, we exit with a specic status code and a wrapper script may
18 /// fallback to Python-based Mercurial.
20 /// fallback to Python-based Mercurial.
19 Unimplemented,
21 Unimplemented,
20 }
22 }
21
23
22 impl CommandError {
24 impl CommandError {
23 pub fn abort(message: impl AsRef<str>) -> Self {
25 pub fn abort(message: impl AsRef<str>) -> Self {
24 CommandError::Abort {
26 CommandError::Abort {
25 // TODO: bytes-based (instead of Unicode-based) formatting
27 // TODO: bytes-based (instead of Unicode-based) formatting
26 // of error messages to handle non-UTF-8 filenames etc:
28 // of error messages to handle non-UTF-8 filenames etc:
27 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
29 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
28 message: utf8_to_local(message.as_ref()).into(),
30 message: utf8_to_local(message.as_ref()).into(),
29 }
31 }
30 }
32 }
31 }
33 }
32
34
33 impl From<HgError> for CommandError {
35 impl From<HgError> for CommandError {
34 fn from(error: HgError) -> Self {
36 fn from(error: HgError) -> Self {
35 match error {
37 match error {
36 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
38 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
37 _ => CommandError::abort(error.to_string()),
39 _ => CommandError::abort(error.to_string()),
38 }
40 }
39 }
41 }
40 }
42 }
41
43
42 impl From<UiError> for CommandError {
44 impl From<UiError> for CommandError {
43 fn from(_error: UiError) -> Self {
45 fn from(_error: UiError) -> Self {
44 // If we already failed writing to stdout or stderr,
46 // If we already failed writing to stdout or stderr,
45 // writing an error message to stderr about it would be likely to fail
47 // writing an error message to stderr about it would be likely to fail
46 // too.
48 // too.
47 CommandError::abort("")
49 CommandError::abort("")
48 }
50 }
49 }
51 }
50
52
51 impl From<FindRootError> for CommandError {
53 impl From<RepoFindError> for CommandError {
52 fn from(err: FindRootError) -> Self {
54 fn from(error: RepoFindError) -> Self {
53 match err {
55 match error {
54 FindRootError::RootNotFound(path) => CommandError::abort(format!(
56 RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
55 "no repository found in '{}' (.hg not found)!",
57 current_directory,
56 path.display()
58 } => CommandError::Abort {
57 )),
59 message: format_bytes!(
58 FindRootError::GetCurrentDirError(error) => HgError::IoError {
60 b"no repository found in '{}' (.hg not found)!",
59 error,
61 get_bytes_from_path(current_directory)
60 context: IoErrorContext::CurrentDir,
62 ),
61 }
63 },
62 .into(),
64 RepoFindError::Other(error) => error.into(),
63 }
65 }
64 }
66 }
65 }
67 }
66
68
67 impl From<(RevlogError, &str)> for CommandError {
69 impl From<(RevlogError, &str)> for CommandError {
68 fn from((err, rev): (RevlogError, &str)) -> CommandError {
70 fn from((err, rev): (RevlogError, &str)) -> CommandError {
69 match err {
71 match err {
70 RevlogError::InvalidRevision => CommandError::abort(format!(
72 RevlogError::InvalidRevision => CommandError::abort(format!(
71 "invalid revision identifier {}",
73 "invalid revision identifier {}",
72 rev
74 rev
73 )),
75 )),
74 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
76 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
75 "ambiguous revision identifier {}",
77 "ambiguous revision identifier {}",
76 rev
78 rev
77 )),
79 )),
78 RevlogError::Other(error) => error.into(),
80 RevlogError::Other(error) => error.into(),
79 }
81 }
80 }
82 }
81 }
83 }
@@ -1,204 +1,198 b''
1 #require rust
1 #require rust
2
2
3 Define an rhg function that will only run if rhg exists
3 Define an rhg function that will only run if rhg exists
4 $ rhg() {
4 $ rhg() {
5 > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then
5 > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then
6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
7 > else
7 > else
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 > exit 80
9 > exit 80
10 > fi
10 > fi
11 > }
11 > }
12
12
13 Unimplemented command
13 Unimplemented command
14 $ rhg unimplemented-command
14 $ rhg unimplemented-command
15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16
16
17 USAGE:
17 USAGE:
18 rhg <SUBCOMMAND>
18 rhg <SUBCOMMAND>
19
19
20 For more information try --help
20 For more information try --help
21 [252]
21 [252]
22
22
23 Finding root
23 Finding root
24 $ rhg root
24 $ rhg root
25 abort: no repository found in '$TESTTMP' (.hg not found)!
25 abort: no repository found in '$TESTTMP' (.hg not found)!
26 [255]
26 [255]
27
27
28 $ hg init repository
28 $ hg init repository
29 $ cd repository
29 $ cd repository
30 $ rhg root
30 $ rhg root
31 $TESTTMP/repository
31 $TESTTMP/repository
32
32
33 Unwritable file descriptor
33 Unwritable file descriptor
34 $ rhg root > /dev/full
34 $ rhg root > /dev/full
35 abort: No space left on device (os error 28)
35 abort: No space left on device (os error 28)
36 [255]
36 [255]
37
37
38 Deleted repository
38 Deleted repository
39 $ rm -rf `pwd`
39 $ rm -rf `pwd`
40 $ rhg root
40 $ rhg root
41 abort: $ENOENT$: current directory
41 abort: $ENOENT$: current directory
42 [255]
42 [255]
43
43
44 Listing tracked files
44 Listing tracked files
45 $ cd $TESTTMP
45 $ cd $TESTTMP
46 $ hg init repository
46 $ hg init repository
47 $ cd repository
47 $ cd repository
48 $ for i in 1 2 3; do
48 $ for i in 1 2 3; do
49 > echo $i >> file$i
49 > echo $i >> file$i
50 > hg add file$i
50 > hg add file$i
51 > done
51 > done
52 > hg commit -m "commit $i" -q
52 > hg commit -m "commit $i" -q
53
53
54 Listing tracked files from root
54 Listing tracked files from root
55 $ rhg files
55 $ rhg files
56 file1
56 file1
57 file2
57 file2
58 file3
58 file3
59
59
60 Listing tracked files from subdirectory
60 Listing tracked files from subdirectory
61 $ mkdir -p path/to/directory
61 $ mkdir -p path/to/directory
62 $ cd path/to/directory
62 $ cd path/to/directory
63 $ rhg files
63 $ rhg files
64 ../../../file1
64 ../../../file1
65 ../../../file2
65 ../../../file2
66 ../../../file3
66 ../../../file3
67
67
68 Listing tracked files through broken pipe
68 Listing tracked files through broken pipe
69 $ rhg files | head -n 1
69 $ rhg files | head -n 1
70 ../../../file1
70 ../../../file1
71
71
72 Debuging data in inline index
72 Debuging data in inline index
73 $ cd $TESTTMP
73 $ cd $TESTTMP
74 $ rm -rf repository
74 $ rm -rf repository
75 $ hg init repository
75 $ hg init repository
76 $ cd repository
76 $ cd repository
77 $ for i in 1 2 3 4 5 6; do
77 $ for i in 1 2 3 4 5 6; do
78 > echo $i >> file-$i
78 > echo $i >> file-$i
79 > hg add file-$i
79 > hg add file-$i
80 > hg commit -m "Commit $i" -q
80 > hg commit -m "Commit $i" -q
81 > done
81 > done
82 $ rhg debugdata -c 2
82 $ rhg debugdata -c 2
83 8d0267cb034247ebfa5ee58ce59e22e57a492297
83 8d0267cb034247ebfa5ee58ce59e22e57a492297
84 test
84 test
85 0 0
85 0 0
86 file-3
86 file-3
87
87
88 Commit 3 (no-eol)
88 Commit 3 (no-eol)
89 $ rhg debugdata -m 2
89 $ rhg debugdata -m 2
90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93
93
94 Debuging with full node id
94 Debuging with full node id
95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 d1d1c679d3053e8926061b6f45ca52009f011e3f
96 d1d1c679d3053e8926061b6f45ca52009f011e3f
97 test
97 test
98 0 0
98 0 0
99 file-1
99 file-1
100
100
101 Commit 1 (no-eol)
101 Commit 1 (no-eol)
102
102
103 Specifying revisions by changeset ID
103 Specifying revisions by changeset ID
104 $ hg log -T '{node}\n'
104 $ hg log -T '{node}\n'
105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
106 d654274993d0149eecc3cc03214f598320211900
106 d654274993d0149eecc3cc03214f598320211900
107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
110 6ae9681c6d30389694d8701faf24b583cf3ccafe
110 6ae9681c6d30389694d8701faf24b583cf3ccafe
111 $ rhg files -r cf8b83
111 $ rhg files -r cf8b83
112 file-1
112 file-1
113 file-2
113 file-2
114 file-3
114 file-3
115 $ rhg cat -r cf8b83 file-2
115 $ rhg cat -r cf8b83 file-2
116 2
116 2
117 $ rhg cat -r c file-2
117 $ rhg cat -r c file-2
118 abort: ambiguous revision identifier c
118 abort: ambiguous revision identifier c
119 [255]
119 [255]
120 $ rhg cat -r d file-2
120 $ rhg cat -r d file-2
121 2
121 2
122
122
123 Cat files
123 Cat files
124 $ cd $TESTTMP
124 $ cd $TESTTMP
125 $ rm -rf repository
125 $ rm -rf repository
126 $ hg init repository
126 $ hg init repository
127 $ cd repository
127 $ cd repository
128 $ echo "original content" > original
128 $ echo "original content" > original
129 $ hg add original
129 $ hg add original
130 $ hg commit -m "add original" original
130 $ hg commit -m "add original" original
131 $ rhg cat -r 0 original
131 $ rhg cat -r 0 original
132 original content
132 original content
133 Cat copied file should not display copy metadata
133 Cat copied file should not display copy metadata
134 $ hg copy original copy_of_original
134 $ hg copy original copy_of_original
135 $ hg commit -m "add copy of original"
135 $ hg commit -m "add copy of original"
136 $ rhg cat -r 1 copy_of_original
136 $ rhg cat -r 1 copy_of_original
137 original content
137 original content
138
138
139 Requirements
139 Requirements
140 $ rhg debugrequirements
140 $ rhg debugrequirements
141 dotencode
141 dotencode
142 fncache
142 fncache
143 generaldelta
143 generaldelta
144 revlogv1
144 revlogv1
145 sparserevlog
145 sparserevlog
146 store
146 store
147
147
148 $ echo indoor-pool >> .hg/requires
148 $ echo indoor-pool >> .hg/requires
149 $ rhg files
149 $ rhg files
150 [252]
150 [252]
151
151
152 $ rhg cat -r 1 copy_of_original
152 $ rhg cat -r 1 copy_of_original
153 [252]
153 [252]
154
154
155 $ rhg debugrequirements
155 $ rhg debugrequirements
156 dotencode
156 [252]
157 fncache
158 generaldelta
159 revlogv1
160 sparserevlog
161 store
162 indoor-pool
163
157
164 $ echo -e '\xFF' >> .hg/requires
158 $ echo -e '\xFF' >> .hg/requires
165 $ rhg debugrequirements
159 $ rhg debugrequirements
166 abort: corrupted repository: parse error in 'requires' file
160 abort: corrupted repository: parse error in 'requires' file
167 [255]
161 [255]
168
162
169 Persistent nodemap
163 Persistent nodemap
170 $ cd $TESTTMP
164 $ cd $TESTTMP
171 $ rm -rf repository
165 $ rm -rf repository
172 $ hg init repository
166 $ hg init repository
173 $ cd repository
167 $ cd repository
174 $ rhg debugrequirements | grep nodemap
168 $ rhg debugrequirements | grep nodemap
175 [1]
169 [1]
176 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
170 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
177 $ hg id -r tip
171 $ hg id -r tip
178 c3ae8dec9fad tip
172 c3ae8dec9fad tip
179 $ ls .hg/store/00changelog*
173 $ ls .hg/store/00changelog*
180 .hg/store/00changelog.d
174 .hg/store/00changelog.d
181 .hg/store/00changelog.i
175 .hg/store/00changelog.i
182 $ rhg files -r c3ae8dec9fad
176 $ rhg files -r c3ae8dec9fad
183 of
177 of
184
178
185 $ cd $TESTTMP
179 $ cd $TESTTMP
186 $ rm -rf repository
180 $ rm -rf repository
187 $ hg --config format.use-persistent-nodemap=True init repository
181 $ hg --config format.use-persistent-nodemap=True init repository
188 $ cd repository
182 $ cd repository
189 $ rhg debugrequirements | grep nodemap
183 $ rhg debugrequirements | grep nodemap
190 persistent-nodemap
184 persistent-nodemap
191 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
185 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
192 $ hg id -r tip
186 $ hg id -r tip
193 c3ae8dec9fad tip
187 c3ae8dec9fad tip
194 $ ls .hg/store/00changelog*
188 $ ls .hg/store/00changelog*
195 .hg/store/00changelog-*.nd (glob)
189 .hg/store/00changelog-*.nd (glob)
196 .hg/store/00changelog.d
190 .hg/store/00changelog.d
197 .hg/store/00changelog.i
191 .hg/store/00changelog.i
198 .hg/store/00changelog.n
192 .hg/store/00changelog.n
199
193
200 Specifying revisions by changeset ID
194 Specifying revisions by changeset ID
201 $ rhg files -r c3ae8dec9fad
195 $ rhg files -r c3ae8dec9fad
202 of
196 of
203 $ rhg cat -r c3ae8dec9fad of
197 $ rhg cat -r c3ae8dec9fad of
204 r5000
198 r5000
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now