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(r |
|
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(§ion, &item) { |
|
93 | match self.get_inner(§ion, &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(§ion, &item) { |
|
132 | if let Some(v) = layer.get(§ion, &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(§ion, &item) { |
|
145 | if let Some(v) = layer.get(§ion, &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 |
/// |
|
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 |
|
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 |
|
|
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. |
|
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:: |
|
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<Find |
|
53 | impl From<RepoFindError> for CommandError { | |
52 |
fn from(err: Find |
|
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