##// END OF EJS Templates
rhg: add limited support for the `config` sub-command...
Simon Sapin -
r47233:fb0ad038 default draft
parent child Browse files
Show More
@@ -1,298 +1,291
1 // layer.rs
1 // layer.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 crate::errors::{HgError, IoResultExt};
10 use crate::errors::{HgError, IoResultExt};
11 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
11 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
12 use format_bytes::{write_bytes, DisplayBytes};
12 use format_bytes::{write_bytes, DisplayBytes};
13 use lazy_static::lazy_static;
13 use lazy_static::lazy_static;
14 use regex::bytes::Regex;
14 use regex::bytes::Regex;
15 use std::collections::HashMap;
15 use std::collections::HashMap;
16 use std::path::{Path, PathBuf};
16 use std::path::{Path, PathBuf};
17
17
18 lazy_static! {
18 lazy_static! {
19 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
19 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
20 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
20 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
21 /// Continuation whitespace
21 /// Continuation whitespace
22 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
22 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
23 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
23 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
24 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
24 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
25 /// A directive that allows for removing previous entries
25 /// A directive that allows for removing previous entries
26 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
26 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
27 /// A directive that allows for including other config files
27 /// A directive that allows for including other config files
28 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
28 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
29 }
29 }
30
30
31 /// All config values separated by layers of precedence.
31 /// All config values separated by layers of precedence.
32 /// Each config source may be split in multiple layers if `%include` directives
32 /// Each config source may be split in multiple layers if `%include` directives
33 /// are used.
33 /// are used.
34 /// TODO detail the general precedence
34 /// TODO detail the general precedence
35 #[derive(Clone)]
35 #[derive(Clone)]
36 pub struct ConfigLayer {
36 pub struct ConfigLayer {
37 /// Mapping of the sections to their items
37 /// Mapping of the sections to their items
38 sections: HashMap<Vec<u8>, ConfigItem>,
38 sections: HashMap<Vec<u8>, ConfigItem>,
39 /// All sections (and their items/values) in a layer share the same origin
39 /// All sections (and their items/values) in a layer share the same origin
40 pub origin: ConfigOrigin,
40 pub origin: ConfigOrigin,
41 /// Whether this layer comes from a trusted user or group
41 /// Whether this layer comes from a trusted user or group
42 pub trusted: bool,
42 pub trusted: bool,
43 }
43 }
44
44
45 impl ConfigLayer {
45 impl ConfigLayer {
46 pub fn new(origin: ConfigOrigin) -> Self {
46 pub fn new(origin: ConfigOrigin) -> Self {
47 ConfigLayer {
47 ConfigLayer {
48 sections: HashMap::new(),
48 sections: HashMap::new(),
49 trusted: true, // TODO check
49 trusted: true, // TODO check
50 origin,
50 origin,
51 }
51 }
52 }
52 }
53
53
54 /// Parse `--config` CLI arguments and return a layer if there’s any
54 /// Parse `--config` CLI arguments and return a layer if there’s any
55 pub(crate) fn parse_cli_args(
55 pub(crate) fn parse_cli_args(
56 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
56 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
57 ) -> Result<Option<Self>, ConfigError> {
57 ) -> Result<Option<Self>, ConfigError> {
58 fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
58 fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
59 use crate::utils::SliceExt;
59 use crate::utils::SliceExt;
60
60
61 let (section_and_item, value) = split_2(arg, b'=')?;
61 let (section_and_item, value) = arg.split_2(b'=')?;
62 let (section, item) = split_2(section_and_item.trim(), b'.')?;
62 let (section, item) = section_and_item.trim().split_2(b'.')?;
63 Some((
63 Some((
64 section.to_owned(),
64 section.to_owned(),
65 item.to_owned(),
65 item.to_owned(),
66 value.trim().to_owned(),
66 value.trim().to_owned(),
67 ))
67 ))
68 }
68 }
69
69
70 fn split_2(bytes: &[u8], separator: u8) -> Option<(&[u8], &[u8])> {
71 let mut iter = bytes.splitn(2, |&byte| byte == separator);
72 let a = iter.next()?;
73 let b = iter.next()?;
74 Some((a, b))
75 }
76
77 let mut layer = Self::new(ConfigOrigin::CommandLine);
70 let mut layer = Self::new(ConfigOrigin::CommandLine);
78 for arg in cli_config_args {
71 for arg in cli_config_args {
79 let arg = arg.as_ref();
72 let arg = arg.as_ref();
80 if let Some((section, item, value)) = parse_one(arg) {
73 if let Some((section, item, value)) = parse_one(arg) {
81 layer.add(section, item, value, None);
74 layer.add(section, item, value, None);
82 } else {
75 } else {
83 Err(HgError::abort(format!(
76 Err(HgError::abort(format!(
84 "malformed --config option: \"{}\" \
77 "malformed --config option: \"{}\" \
85 (use --config section.name=value)",
78 (use --config section.name=value)",
86 String::from_utf8_lossy(arg),
79 String::from_utf8_lossy(arg),
87 )))?
80 )))?
88 }
81 }
89 }
82 }
90 if layer.sections.is_empty() {
83 if layer.sections.is_empty() {
91 Ok(None)
84 Ok(None)
92 } else {
85 } else {
93 Ok(Some(layer))
86 Ok(Some(layer))
94 }
87 }
95 }
88 }
96
89
97 /// Returns whether this layer comes from `--config` CLI arguments
90 /// Returns whether this layer comes from `--config` CLI arguments
98 pub(crate) fn is_from_command_line(&self) -> bool {
91 pub(crate) fn is_from_command_line(&self) -> bool {
99 if let ConfigOrigin::CommandLine = self.origin {
92 if let ConfigOrigin::CommandLine = self.origin {
100 true
93 true
101 } else {
94 } else {
102 false
95 false
103 }
96 }
104 }
97 }
105
98
106 /// Add an entry to the config, overwriting the old one if already present.
99 /// Add an entry to the config, overwriting the old one if already present.
107 pub fn add(
100 pub fn add(
108 &mut self,
101 &mut self,
109 section: Vec<u8>,
102 section: Vec<u8>,
110 item: Vec<u8>,
103 item: Vec<u8>,
111 value: Vec<u8>,
104 value: Vec<u8>,
112 line: Option<usize>,
105 line: Option<usize>,
113 ) {
106 ) {
114 self.sections
107 self.sections
115 .entry(section)
108 .entry(section)
116 .or_insert_with(|| HashMap::new())
109 .or_insert_with(|| HashMap::new())
117 .insert(item, ConfigValue { bytes: value, line });
110 .insert(item, ConfigValue { bytes: value, line });
118 }
111 }
119
112
120 /// Returns the config value in `<section>.<item>` if it exists
113 /// Returns the config value in `<section>.<item>` if it exists
121 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
114 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
122 Some(self.sections.get(section)?.get(item)?)
115 Some(self.sections.get(section)?.get(item)?)
123 }
116 }
124
117
125 pub fn is_empty(&self) -> bool {
118 pub fn is_empty(&self) -> bool {
126 self.sections.is_empty()
119 self.sections.is_empty()
127 }
120 }
128
121
129 /// Returns a `Vec` of layers in order of precedence (so, in read order),
122 /// Returns a `Vec` of layers in order of precedence (so, in read order),
130 /// recursively parsing the `%include` directives if any.
123 /// recursively parsing the `%include` directives if any.
131 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
124 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
132 let mut layers = vec![];
125 let mut layers = vec![];
133
126
134 // Discard byte order mark if any
127 // Discard byte order mark if any
135 let data = if data.starts_with(b"\xef\xbb\xbf") {
128 let data = if data.starts_with(b"\xef\xbb\xbf") {
136 &data[3..]
129 &data[3..]
137 } else {
130 } else {
138 data
131 data
139 };
132 };
140
133
141 // TODO check if it's trusted
134 // TODO check if it's trusted
142 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
135 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
143
136
144 let mut lines_iter =
137 let mut lines_iter =
145 data.split(|b| *b == b'\n').enumerate().peekable();
138 data.split(|b| *b == b'\n').enumerate().peekable();
146 let mut section = b"".to_vec();
139 let mut section = b"".to_vec();
147
140
148 while let Some((index, bytes)) = lines_iter.next() {
141 while let Some((index, bytes)) = lines_iter.next() {
149 if let Some(m) = INCLUDE_RE.captures(&bytes) {
142 if let Some(m) = INCLUDE_RE.captures(&bytes) {
150 let filename_bytes = &m[1];
143 let filename_bytes = &m[1];
151 // `Path::parent` only fails for the root directory,
144 // `Path::parent` only fails for the root directory,
152 // which `src` can’t be since we’ve managed to open it as a
145 // which `src` can’t be since we’ve managed to open it as a
153 // file.
146 // file.
154 let dir = src
147 let dir = src
155 .parent()
148 .parent()
156 .expect("Path::parent fail on a file we’ve read");
149 .expect("Path::parent fail on a file we’ve read");
157 // `Path::join` with an absolute argument correctly ignores the
150 // `Path::join` with an absolute argument correctly ignores the
158 // base path
151 // base path
159 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
152 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
160 let data = std::fs::read(&filename).for_file(&filename)?;
153 let data = std::fs::read(&filename).for_file(&filename)?;
161 layers.push(current_layer);
154 layers.push(current_layer);
162 layers.extend(Self::parse(&filename, &data)?);
155 layers.extend(Self::parse(&filename, &data)?);
163 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
156 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
164 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
157 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
165 } else if let Some(m) = SECTION_RE.captures(&bytes) {
158 } else if let Some(m) = SECTION_RE.captures(&bytes) {
166 section = m[1].to_vec();
159 section = m[1].to_vec();
167 } else if let Some(m) = ITEM_RE.captures(&bytes) {
160 } else if let Some(m) = ITEM_RE.captures(&bytes) {
168 let item = m[1].to_vec();
161 let item = m[1].to_vec();
169 let mut value = m[2].to_vec();
162 let mut value = m[2].to_vec();
170 loop {
163 loop {
171 match lines_iter.peek() {
164 match lines_iter.peek() {
172 None => break,
165 None => break,
173 Some((_, v)) => {
166 Some((_, v)) => {
174 if let Some(_) = COMMENT_RE.captures(&v) {
167 if let Some(_) = COMMENT_RE.captures(&v) {
175 } else if let Some(_) = CONT_RE.captures(&v) {
168 } else if let Some(_) = CONT_RE.captures(&v) {
176 value.extend(b"\n");
169 value.extend(b"\n");
177 value.extend(&m[1]);
170 value.extend(&m[1]);
178 } else {
171 } else {
179 break;
172 break;
180 }
173 }
181 }
174 }
182 };
175 };
183 lines_iter.next();
176 lines_iter.next();
184 }
177 }
185 current_layer.add(
178 current_layer.add(
186 section.clone(),
179 section.clone(),
187 item,
180 item,
188 value,
181 value,
189 Some(index + 1),
182 Some(index + 1),
190 );
183 );
191 } else if let Some(m) = UNSET_RE.captures(&bytes) {
184 } else if let Some(m) = UNSET_RE.captures(&bytes) {
192 if let Some(map) = current_layer.sections.get_mut(&section) {
185 if let Some(map) = current_layer.sections.get_mut(&section) {
193 map.remove(&m[1]);
186 map.remove(&m[1]);
194 }
187 }
195 } else {
188 } else {
196 return Err(ConfigParseError {
189 return Err(ConfigParseError {
197 origin: ConfigOrigin::File(src.to_owned()),
190 origin: ConfigOrigin::File(src.to_owned()),
198 line: Some(index + 1),
191 line: Some(index + 1),
199 bytes: bytes.to_owned(),
192 bytes: bytes.to_owned(),
200 }
193 }
201 .into());
194 .into());
202 }
195 }
203 }
196 }
204 if !current_layer.is_empty() {
197 if !current_layer.is_empty() {
205 layers.push(current_layer);
198 layers.push(current_layer);
206 }
199 }
207 Ok(layers)
200 Ok(layers)
208 }
201 }
209 }
202 }
210
203
211 impl DisplayBytes for ConfigLayer {
204 impl DisplayBytes for ConfigLayer {
212 fn display_bytes(
205 fn display_bytes(
213 &self,
206 &self,
214 out: &mut dyn std::io::Write,
207 out: &mut dyn std::io::Write,
215 ) -> std::io::Result<()> {
208 ) -> std::io::Result<()> {
216 let mut sections: Vec<_> = self.sections.iter().collect();
209 let mut sections: Vec<_> = self.sections.iter().collect();
217 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
210 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
218
211
219 for (section, items) in sections.into_iter() {
212 for (section, items) in sections.into_iter() {
220 let mut items: Vec<_> = items.into_iter().collect();
213 let mut items: Vec<_> = items.into_iter().collect();
221 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
214 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
222
215
223 for (item, config_entry) in items {
216 for (item, config_entry) in items {
224 write_bytes!(
217 write_bytes!(
225 out,
218 out,
226 b"{}.{}={} # {}\n",
219 b"{}.{}={} # {}\n",
227 section,
220 section,
228 item,
221 item,
229 &config_entry.bytes,
222 &config_entry.bytes,
230 &self.origin,
223 &self.origin,
231 )?
224 )?
232 }
225 }
233 }
226 }
234 Ok(())
227 Ok(())
235 }
228 }
236 }
229 }
237
230
238 /// Mapping of section item to value.
231 /// Mapping of section item to value.
239 /// In the following:
232 /// In the following:
240 /// ```text
233 /// ```text
241 /// [ui]
234 /// [ui]
242 /// paginate=no
235 /// paginate=no
243 /// ```
236 /// ```
244 /// "paginate" is the section item and "no" the value.
237 /// "paginate" is the section item and "no" the value.
245 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
238 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
246
239
247 #[derive(Clone, Debug, PartialEq)]
240 #[derive(Clone, Debug, PartialEq)]
248 pub struct ConfigValue {
241 pub struct ConfigValue {
249 /// The raw bytes of the value (be it from the CLI, env or from a file)
242 /// The raw bytes of the value (be it from the CLI, env or from a file)
250 pub bytes: Vec<u8>,
243 pub bytes: Vec<u8>,
251 /// Only present if the value comes from a file, 1-indexed.
244 /// Only present if the value comes from a file, 1-indexed.
252 pub line: Option<usize>,
245 pub line: Option<usize>,
253 }
246 }
254
247
255 #[derive(Clone, Debug)]
248 #[derive(Clone, Debug)]
256 pub enum ConfigOrigin {
249 pub enum ConfigOrigin {
257 /// From a configuration file
250 /// From a configuration file
258 File(PathBuf),
251 File(PathBuf),
259 /// From a `--config` CLI argument
252 /// From a `--config` CLI argument
260 CommandLine,
253 CommandLine,
261 /// From environment variables like `$PAGER` or `$EDITOR`
254 /// From environment variables like `$PAGER` or `$EDITOR`
262 Environment(Vec<u8>),
255 Environment(Vec<u8>),
263 /* TODO cli
256 /* TODO cli
264 * TODO defaults (configitems.py)
257 * TODO defaults (configitems.py)
265 * TODO extensions
258 * TODO extensions
266 * TODO Python resources?
259 * TODO Python resources?
267 * Others? */
260 * Others? */
268 }
261 }
269
262
270 impl DisplayBytes for ConfigOrigin {
263 impl DisplayBytes for ConfigOrigin {
271 fn display_bytes(
264 fn display_bytes(
272 &self,
265 &self,
273 out: &mut dyn std::io::Write,
266 out: &mut dyn std::io::Write,
274 ) -> std::io::Result<()> {
267 ) -> std::io::Result<()> {
275 match self {
268 match self {
276 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
269 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
277 ConfigOrigin::CommandLine => out.write_all(b"--config"),
270 ConfigOrigin::CommandLine => out.write_all(b"--config"),
278 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
271 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
279 }
272 }
280 }
273 }
281 }
274 }
282
275
283 #[derive(Debug)]
276 #[derive(Debug)]
284 pub struct ConfigParseError {
277 pub struct ConfigParseError {
285 pub origin: ConfigOrigin,
278 pub origin: ConfigOrigin,
286 pub line: Option<usize>,
279 pub line: Option<usize>,
287 pub bytes: Vec<u8>,
280 pub bytes: Vec<u8>,
288 }
281 }
289
282
290 #[derive(Debug, derive_more::From)]
283 #[derive(Debug, derive_more::From)]
291 pub enum ConfigError {
284 pub enum ConfigError {
292 Parse(ConfigParseError),
285 Parse(ConfigParseError),
293 Other(HgError),
286 Other(HgError),
294 }
287 }
295
288
296 fn make_regex(pattern: &'static str) -> Regex {
289 fn make_regex(pattern: &'static str) -> Regex {
297 Regex::new(pattern).expect("expected a valid regex")
290 Regex::new(pattern).expect("expected a valid regex")
298 }
291 }
@@ -1,238 +1,264
1 use crate::config::{Config, ConfigError, ConfigParseError};
1 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::errors::{HgError, IoResultExt};
2 use crate::errors::{HgError, IoResultExt};
3 use crate::requirements;
3 use crate::requirements;
4 use crate::utils::current_dir;
4 use crate::utils::current_dir;
5 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::files::get_path_from_bytes;
6 use memmap::{Mmap, MmapOptions};
6 use memmap::{Mmap, MmapOptions};
7 use std::collections::HashSet;
7 use std::collections::HashSet;
8 use std::path::{Path, PathBuf};
8 use std::path::{Path, PathBuf};
9
9
10 /// A repository on disk
10 /// A repository on disk
11 pub struct Repo {
11 pub struct Repo {
12 working_directory: PathBuf,
12 working_directory: PathBuf,
13 dot_hg: PathBuf,
13 dot_hg: PathBuf,
14 store: PathBuf,
14 store: PathBuf,
15 requirements: HashSet<String>,
15 requirements: HashSet<String>,
16 config: Config,
16 config: Config,
17 }
17 }
18
18
19 #[derive(Debug, derive_more::From)]
19 #[derive(Debug, derive_more::From)]
20 pub enum RepoError {
20 pub enum RepoError {
21 NotFound {
21 NotFound {
22 at: PathBuf,
22 at: PathBuf,
23 },
23 },
24 #[from]
24 #[from]
25 ConfigParseError(ConfigParseError),
25 ConfigParseError(ConfigParseError),
26 #[from]
26 #[from]
27 Other(HgError),
27 Other(HgError),
28 }
28 }
29
29
30 impl From<ConfigError> for RepoError {
30 impl From<ConfigError> for RepoError {
31 fn from(error: ConfigError) -> Self {
31 fn from(error: ConfigError) -> Self {
32 match error {
32 match error {
33 ConfigError::Parse(error) => error.into(),
33 ConfigError::Parse(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 #[derive(Clone, Copy)]
40 #[derive(Clone, Copy)]
41 pub(crate) struct Vfs<'a> {
41 pub(crate) struct Vfs<'a> {
42 base: &'a Path,
42 base: &'a Path,
43 }
43 }
44
44
45 impl Repo {
45 impl Repo {
46 /// Search the current directory and its ancestores for a repository:
46 /// Find a repository, either at the given path (which must contain a `.hg`
47 /// a working directory that contains a `.hg` sub-directory.
47 /// sub-directory) or by searching the current directory and its
48 /// ancestors.
48 ///
49 ///
49 /// `explicit_path` is for `--repository` command-line arguments.
50 /// A method with two very different "modes" like this usually a code smell
51 /// to make two methods instead, but in this case an `Option` is what rhg
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 /// Having two methods would just move that `if` to almost all callers.
50 pub fn find(
54 pub fn find(
51 config: &Config,
55 config: &Config,
52 explicit_path: Option<&Path>,
56 explicit_path: Option<&Path>,
53 ) -> Result<Self, RepoError> {
57 ) -> Result<Self, RepoError> {
54 if let Some(root) = explicit_path {
58 if let Some(root) = explicit_path {
55 // Having an absolute path isn’t necessary here but can help code
59 // Having an absolute path isn’t necessary here but can help code
56 // elsewhere
60 // elsewhere
57 let root = current_dir()?.join(root);
61 let root = current_dir()?.join(root);
58 if root.join(".hg").is_dir() {
62 if root.join(".hg").is_dir() {
59 Self::new_at_path(root, config)
63 Self::new_at_path(root, config)
60 } else {
64 } else {
61 Err(RepoError::NotFound {
65 Err(RepoError::NotFound {
62 at: root.to_owned(),
66 at: root.to_owned(),
63 })
67 })
64 }
68 }
65 } else {
69 } else {
66 let current_directory = crate::utils::current_dir()?;
70 let current_directory = crate::utils::current_dir()?;
67 // ancestors() is inclusive: it first yields `current_directory`
71 // ancestors() is inclusive: it first yields `current_directory`
68 // as-is.
72 // as-is.
69 for ancestor in current_directory.ancestors() {
73 for ancestor in current_directory.ancestors() {
70 if ancestor.join(".hg").is_dir() {
74 if ancestor.join(".hg").is_dir() {
71 return Self::new_at_path(ancestor.to_owned(), config);
75 return Self::new_at_path(ancestor.to_owned(), config);
72 }
76 }
73 }
77 }
74 Err(RepoError::NotFound {
78 Err(RepoError::NotFound {
75 at: current_directory,
79 at: current_directory,
76 })
80 })
77 }
81 }
78 }
82 }
79
83
84 /// Like `Repo::find`, but not finding a repository is not an error if no
85 /// explicit path is given. `Ok(None)` is returned in that case.
86 ///
87 /// If an explicit path *is* given, not finding a repository there is still
88 /// an error.
89 ///
90 /// For sub-commands that don’t need a repository, configuration should
91 /// still be affected by a repository’s `.hg/hgrc` file. This is the
92 /// constructor to use.
93 pub fn find_optional(
94 config: &Config,
95 explicit_path: Option<&Path>,
96 ) -> Result<Option<Self>, RepoError> {
97 match Self::find(config, explicit_path) {
98 Ok(repo) => Ok(Some(repo)),
99 Err(RepoError::NotFound { .. }) if explicit_path.is_none() => {
100 Ok(None)
101 }
102 Err(error) => Err(error),
103 }
104 }
105
80 /// To be called after checking that `.hg` is a sub-directory
106 /// To be called after checking that `.hg` is a sub-directory
81 fn new_at_path(
107 fn new_at_path(
82 working_directory: PathBuf,
108 working_directory: PathBuf,
83 config: &Config,
109 config: &Config,
84 ) -> Result<Self, RepoError> {
110 ) -> Result<Self, RepoError> {
85 let dot_hg = working_directory.join(".hg");
111 let dot_hg = working_directory.join(".hg");
86
112
87 let mut repo_config_files = Vec::new();
113 let mut repo_config_files = Vec::new();
88 repo_config_files.push(dot_hg.join("hgrc"));
114 repo_config_files.push(dot_hg.join("hgrc"));
89 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
115 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
90
116
91 let hg_vfs = Vfs { base: &dot_hg };
117 let hg_vfs = Vfs { base: &dot_hg };
92 let mut reqs = requirements::load_if_exists(hg_vfs)?;
118 let mut reqs = requirements::load_if_exists(hg_vfs)?;
93 let relative =
119 let relative =
94 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
120 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
95 let shared =
121 let shared =
96 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
122 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
97
123
98 // From `mercurial/localrepo.py`:
124 // From `mercurial/localrepo.py`:
99 //
125 //
100 // if .hg/requires contains the sharesafe requirement, it means
126 // if .hg/requires contains the sharesafe requirement, it means
101 // there exists a `.hg/store/requires` too and we should read it
127 // there exists a `.hg/store/requires` too and we should read it
102 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
128 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
103 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
129 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
104 // is not present, refer checkrequirementscompat() for that
130 // is not present, refer checkrequirementscompat() for that
105 //
131 //
106 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
132 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
107 // repository was shared the old way. We check the share source
133 // repository was shared the old way. We check the share source
108 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
134 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
109 // current repository needs to be reshared
135 // current repository needs to be reshared
110 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
136 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
111
137
112 let store_path;
138 let store_path;
113 if !shared {
139 if !shared {
114 store_path = dot_hg.join("store");
140 store_path = dot_hg.join("store");
115 if share_safe {
141 if share_safe {
116 reqs.extend(requirements::load(Vfs { base: &store_path })?);
142 reqs.extend(requirements::load(Vfs { base: &store_path })?);
117 }
143 }
118 } else {
144 } else {
119 let bytes = hg_vfs.read("sharedpath")?;
145 let bytes = hg_vfs.read("sharedpath")?;
120 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
146 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
121 if relative {
147 if relative {
122 shared_path = dot_hg.join(shared_path)
148 shared_path = dot_hg.join(shared_path)
123 }
149 }
124 if !shared_path.is_dir() {
150 if !shared_path.is_dir() {
125 return Err(HgError::corrupted(format!(
151 return Err(HgError::corrupted(format!(
126 ".hg/sharedpath points to nonexistent directory {}",
152 ".hg/sharedpath points to nonexistent directory {}",
127 shared_path.display()
153 shared_path.display()
128 ))
154 ))
129 .into());
155 .into());
130 }
156 }
131
157
132 store_path = shared_path.join("store");
158 store_path = shared_path.join("store");
133
159
134 let source_is_share_safe =
160 let source_is_share_safe =
135 requirements::load(Vfs { base: &shared_path })?
161 requirements::load(Vfs { base: &shared_path })?
136 .contains(requirements::SHARESAFE_REQUIREMENT);
162 .contains(requirements::SHARESAFE_REQUIREMENT);
137
163
138 if share_safe && !source_is_share_safe {
164 if share_safe && !source_is_share_safe {
139 return Err(match config
165 return Err(match config
140 .get(b"safe-mismatch", b"source-not-safe")
166 .get(b"safe-mismatch", b"source-not-safe")
141 {
167 {
142 Some(b"abort") | None => HgError::abort(
168 Some(b"abort") | None => HgError::abort(
143 "share source does not support share-safe requirement",
169 "share source does not support share-safe requirement",
144 ),
170 ),
145 _ => HgError::unsupported("share-safe downgrade"),
171 _ => HgError::unsupported("share-safe downgrade"),
146 }
172 }
147 .into());
173 .into());
148 } else if source_is_share_safe && !share_safe {
174 } else if source_is_share_safe && !share_safe {
149 return Err(
175 return Err(
150 match config.get(b"safe-mismatch", b"source-safe") {
176 match config.get(b"safe-mismatch", b"source-safe") {
151 Some(b"abort") | None => HgError::abort(
177 Some(b"abort") | None => HgError::abort(
152 "version mismatch: source uses share-safe \
178 "version mismatch: source uses share-safe \
153 functionality while the current share does not",
179 functionality while the current share does not",
154 ),
180 ),
155 _ => HgError::unsupported("share-safe upgrade"),
181 _ => HgError::unsupported("share-safe upgrade"),
156 }
182 }
157 .into(),
183 .into(),
158 );
184 );
159 }
185 }
160
186
161 if share_safe {
187 if share_safe {
162 repo_config_files.insert(0, shared_path.join("hgrc"))
188 repo_config_files.insert(0, shared_path.join("hgrc"))
163 }
189 }
164 }
190 }
165
191
166 let repo_config = config.combine_with_repo(&repo_config_files)?;
192 let repo_config = config.combine_with_repo(&repo_config_files)?;
167
193
168 let repo = Self {
194 let repo = Self {
169 requirements: reqs,
195 requirements: reqs,
170 working_directory,
196 working_directory,
171 store: store_path,
197 store: store_path,
172 dot_hg,
198 dot_hg,
173 config: repo_config,
199 config: repo_config,
174 };
200 };
175
201
176 requirements::check(&repo)?;
202 requirements::check(&repo)?;
177
203
178 Ok(repo)
204 Ok(repo)
179 }
205 }
180
206
181 pub fn working_directory_path(&self) -> &Path {
207 pub fn working_directory_path(&self) -> &Path {
182 &self.working_directory
208 &self.working_directory
183 }
209 }
184
210
185 pub fn requirements(&self) -> &HashSet<String> {
211 pub fn requirements(&self) -> &HashSet<String> {
186 &self.requirements
212 &self.requirements
187 }
213 }
188
214
189 pub fn config(&self) -> &Config {
215 pub fn config(&self) -> &Config {
190 &self.config
216 &self.config
191 }
217 }
192
218
193 /// For accessing repository files (in `.hg`), except for the store
219 /// For accessing repository files (in `.hg`), except for the store
194 /// (`.hg/store`).
220 /// (`.hg/store`).
195 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
221 pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
196 Vfs { base: &self.dot_hg }
222 Vfs { base: &self.dot_hg }
197 }
223 }
198
224
199 /// For accessing repository store files (in `.hg/store`)
225 /// For accessing repository store files (in `.hg/store`)
200 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
226 pub(crate) fn store_vfs(&self) -> Vfs<'_> {
201 Vfs { base: &self.store }
227 Vfs { base: &self.store }
202 }
228 }
203
229
204 /// For accessing the working copy
230 /// For accessing the working copy
205
231
206 // The undescore prefix silences the "never used" warning. Remove before
232 // The undescore prefix silences the "never used" warning. Remove before
207 // using.
233 // using.
208 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
234 pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> {
209 Vfs {
235 Vfs {
210 base: &self.working_directory,
236 base: &self.working_directory,
211 }
237 }
212 }
238 }
213 }
239 }
214
240
215 impl Vfs<'_> {
241 impl Vfs<'_> {
216 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
242 pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
217 self.base.join(relative_path)
243 self.base.join(relative_path)
218 }
244 }
219
245
220 pub(crate) fn read(
246 pub(crate) fn read(
221 &self,
247 &self,
222 relative_path: impl AsRef<Path>,
248 relative_path: impl AsRef<Path>,
223 ) -> Result<Vec<u8>, HgError> {
249 ) -> Result<Vec<u8>, HgError> {
224 let path = self.join(relative_path);
250 let path = self.join(relative_path);
225 std::fs::read(&path).for_file(&path)
251 std::fs::read(&path).for_file(&path)
226 }
252 }
227
253
228 pub(crate) fn mmap_open(
254 pub(crate) fn mmap_open(
229 &self,
255 &self,
230 relative_path: impl AsRef<Path>,
256 relative_path: impl AsRef<Path>,
231 ) -> Result<Mmap, HgError> {
257 ) -> Result<Mmap, HgError> {
232 let path = self.base.join(relative_path);
258 let path = self.base.join(relative_path);
233 let file = std::fs::File::open(&path).for_file(&path)?;
259 let file = std::fs::File::open(&path).for_file(&path)?;
234 // TODO: what are the safety requirements here?
260 // TODO: what are the safety requirements here?
235 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
261 let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?;
236 Ok(mmap)
262 Ok(mmap)
237 }
263 }
238 }
264 }
@@ -1,193 +1,201
1 // utils module
1 // utils module
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! Contains useful functions, traits, structs, etc. for use in core.
8 //! Contains useful functions, traits, structs, etc. for use in core.
9
9
10 use crate::errors::{HgError, IoErrorContext};
10 use crate::errors::{HgError, IoErrorContext};
11 use crate::utils::hg_path::HgPath;
11 use crate::utils::hg_path::HgPath;
12 use std::{io::Write, ops::Deref};
12 use std::{io::Write, ops::Deref};
13
13
14 pub mod files;
14 pub mod files;
15 pub mod hg_path;
15 pub mod hg_path;
16 pub mod path_auditor;
16 pub mod path_auditor;
17
17
18 /// Useful until rust/issues/56345 is stable
18 /// Useful until rust/issues/56345 is stable
19 ///
19 ///
20 /// # Examples
20 /// # Examples
21 ///
21 ///
22 /// ```
22 /// ```
23 /// use crate::hg::utils::find_slice_in_slice;
23 /// use crate::hg::utils::find_slice_in_slice;
24 ///
24 ///
25 /// let haystack = b"This is the haystack".to_vec();
25 /// let haystack = b"This is the haystack".to_vec();
26 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
26 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
27 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
27 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
28 /// ```
28 /// ```
29 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
29 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
30 where
30 where
31 for<'a> &'a [T]: PartialEq,
31 for<'a> &'a [T]: PartialEq,
32 {
32 {
33 slice
33 slice
34 .windows(needle.len())
34 .windows(needle.len())
35 .position(|window| window == needle)
35 .position(|window| window == needle)
36 }
36 }
37
37
38 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
38 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
39 ///
39 ///
40 /// # Examples
40 /// # Examples
41 ///
41 ///
42 /// ```
42 /// ```
43 /// use crate::hg::utils::replace_slice;
43 /// use crate::hg::utils::replace_slice;
44 /// let mut line = b"I hate writing tests!".to_vec();
44 /// let mut line = b"I hate writing tests!".to_vec();
45 /// replace_slice(&mut line, b"hate", b"love");
45 /// replace_slice(&mut line, b"hate", b"love");
46 /// assert_eq!(
46 /// assert_eq!(
47 /// line,
47 /// line,
48 /// b"I love writing tests!".to_vec()
48 /// b"I love writing tests!".to_vec()
49 /// );
49 /// );
50 /// ```
50 /// ```
51 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
51 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
52 where
52 where
53 T: Clone + PartialEq,
53 T: Clone + PartialEq,
54 {
54 {
55 if buf.len() < from.len() || from.len() != to.len() {
55 if buf.len() < from.len() || from.len() != to.len() {
56 return;
56 return;
57 }
57 }
58 for i in 0..=buf.len() - from.len() {
58 for i in 0..=buf.len() - from.len() {
59 if buf[i..].starts_with(from) {
59 if buf[i..].starts_with(from) {
60 buf[i..(i + from.len())].clone_from_slice(to);
60 buf[i..(i + from.len())].clone_from_slice(to);
61 }
61 }
62 }
62 }
63 }
63 }
64
64
65 pub trait SliceExt {
65 pub trait SliceExt {
66 fn trim_end(&self) -> &Self;
66 fn trim_end(&self) -> &Self;
67 fn trim_start(&self) -> &Self;
67 fn trim_start(&self) -> &Self;
68 fn trim(&self) -> &Self;
68 fn trim(&self) -> &Self;
69 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
69 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
70 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
70 }
71 }
71
72
72 #[allow(clippy::trivially_copy_pass_by_ref)]
73 #[allow(clippy::trivially_copy_pass_by_ref)]
73 fn is_not_whitespace(c: &u8) -> bool {
74 fn is_not_whitespace(c: &u8) -> bool {
74 !(*c as char).is_whitespace()
75 !(*c as char).is_whitespace()
75 }
76 }
76
77
77 impl SliceExt for [u8] {
78 impl SliceExt for [u8] {
78 fn trim_end(&self) -> &[u8] {
79 fn trim_end(&self) -> &[u8] {
79 if let Some(last) = self.iter().rposition(is_not_whitespace) {
80 if let Some(last) = self.iter().rposition(is_not_whitespace) {
80 &self[..=last]
81 &self[..=last]
81 } else {
82 } else {
82 &[]
83 &[]
83 }
84 }
84 }
85 }
85 fn trim_start(&self) -> &[u8] {
86 fn trim_start(&self) -> &[u8] {
86 if let Some(first) = self.iter().position(is_not_whitespace) {
87 if let Some(first) = self.iter().position(is_not_whitespace) {
87 &self[first..]
88 &self[first..]
88 } else {
89 } else {
89 &[]
90 &[]
90 }
91 }
91 }
92 }
92
93
93 /// ```
94 /// ```
94 /// use hg::utils::SliceExt;
95 /// use hg::utils::SliceExt;
95 /// assert_eq!(
96 /// assert_eq!(
96 /// b" to trim ".trim(),
97 /// b" to trim ".trim(),
97 /// b"to trim"
98 /// b"to trim"
98 /// );
99 /// );
99 /// assert_eq!(
100 /// assert_eq!(
100 /// b"to trim ".trim(),
101 /// b"to trim ".trim(),
101 /// b"to trim"
102 /// b"to trim"
102 /// );
103 /// );
103 /// assert_eq!(
104 /// assert_eq!(
104 /// b" to trim".trim(),
105 /// b" to trim".trim(),
105 /// b"to trim"
106 /// b"to trim"
106 /// );
107 /// );
107 /// ```
108 /// ```
108 fn trim(&self) -> &[u8] {
109 fn trim(&self) -> &[u8] {
109 self.trim_start().trim_end()
110 self.trim_start().trim_end()
110 }
111 }
111
112
112 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
113 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
113 if self.starts_with(needle) {
114 if self.starts_with(needle) {
114 Some(&self[needle.len()..])
115 Some(&self[needle.len()..])
115 } else {
116 } else {
116 None
117 None
117 }
118 }
118 }
119 }
120
121 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
122 let mut iter = self.splitn(2, |&byte| byte == separator);
123 let a = iter.next()?;
124 let b = iter.next()?;
125 Some((a, b))
126 }
119 }
127 }
120
128
121 pub trait Escaped {
129 pub trait Escaped {
122 /// Return bytes escaped for display to the user
130 /// Return bytes escaped for display to the user
123 fn escaped_bytes(&self) -> Vec<u8>;
131 fn escaped_bytes(&self) -> Vec<u8>;
124 }
132 }
125
133
126 impl Escaped for u8 {
134 impl Escaped for u8 {
127 fn escaped_bytes(&self) -> Vec<u8> {
135 fn escaped_bytes(&self) -> Vec<u8> {
128 let mut acc = vec![];
136 let mut acc = vec![];
129 match self {
137 match self {
130 c @ b'\'' | c @ b'\\' => {
138 c @ b'\'' | c @ b'\\' => {
131 acc.push(b'\\');
139 acc.push(b'\\');
132 acc.push(*c);
140 acc.push(*c);
133 }
141 }
134 b'\t' => {
142 b'\t' => {
135 acc.extend(br"\\t");
143 acc.extend(br"\\t");
136 }
144 }
137 b'\n' => {
145 b'\n' => {
138 acc.extend(br"\\n");
146 acc.extend(br"\\n");
139 }
147 }
140 b'\r' => {
148 b'\r' => {
141 acc.extend(br"\\r");
149 acc.extend(br"\\r");
142 }
150 }
143 c if (*c < b' ' || *c >= 127) => {
151 c if (*c < b' ' || *c >= 127) => {
144 write!(acc, "\\x{:x}", self).unwrap();
152 write!(acc, "\\x{:x}", self).unwrap();
145 }
153 }
146 c => {
154 c => {
147 acc.push(*c);
155 acc.push(*c);
148 }
156 }
149 }
157 }
150 acc
158 acc
151 }
159 }
152 }
160 }
153
161
154 impl<'a, T: Escaped> Escaped for &'a [T] {
162 impl<'a, T: Escaped> Escaped for &'a [T] {
155 fn escaped_bytes(&self) -> Vec<u8> {
163 fn escaped_bytes(&self) -> Vec<u8> {
156 self.iter().flat_map(Escaped::escaped_bytes).collect()
164 self.iter().flat_map(Escaped::escaped_bytes).collect()
157 }
165 }
158 }
166 }
159
167
160 impl<T: Escaped> Escaped for Vec<T> {
168 impl<T: Escaped> Escaped for Vec<T> {
161 fn escaped_bytes(&self) -> Vec<u8> {
169 fn escaped_bytes(&self) -> Vec<u8> {
162 self.deref().escaped_bytes()
170 self.deref().escaped_bytes()
163 }
171 }
164 }
172 }
165
173
166 impl<'a> Escaped for &'a HgPath {
174 impl<'a> Escaped for &'a HgPath {
167 fn escaped_bytes(&self) -> Vec<u8> {
175 fn escaped_bytes(&self) -> Vec<u8> {
168 self.as_bytes().escaped_bytes()
176 self.as_bytes().escaped_bytes()
169 }
177 }
170 }
178 }
171
179
172 // TODO: use the str method when we require Rust 1.45
180 // TODO: use the str method when we require Rust 1.45
173 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
181 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
174 if s.ends_with(suffix) {
182 if s.ends_with(suffix) {
175 Some(&s[..s.len() - suffix.len()])
183 Some(&s[..s.len() - suffix.len()])
176 } else {
184 } else {
177 None
185 None
178 }
186 }
179 }
187 }
180
188
181 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
189 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
182 std::env::current_dir().map_err(|error| HgError::IoError {
190 std::env::current_dir().map_err(|error| HgError::IoError {
183 error,
191 error,
184 context: IoErrorContext::CurrentDir,
192 context: IoErrorContext::CurrentDir,
185 })
193 })
186 }
194 }
187
195
188 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
196 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
189 std::env::current_exe().map_err(|error| HgError::IoError {
197 std::env::current_exe().map_err(|error| HgError::IoError {
190 error,
198 error,
191 context: IoErrorContext::CurrentExe,
199 context: IoErrorContext::CurrentExe,
192 })
200 })
193 }
201 }
@@ -1,30 +1,52
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::Arg;
3 use clap::ArgMatches;
4 use clap::ArgMatches;
4 use format_bytes::format_bytes;
5 use format_bytes::format_bytes;
5 use hg::config::Config;
6 use hg::config::Config;
7 use hg::errors::HgError;
6 use hg::repo::Repo;
8 use hg::repo::Repo;
7 use hg::utils::files::get_bytes_from_path;
9 use hg::utils::SliceExt;
8 use std::path::Path;
10 use std::path::Path;
9
11
10 pub const HELP_TEXT: &str = "
12 pub const HELP_TEXT: &str = "
11 Print the root directory of the current repository.
13 With one argument of the form section.name, print just the value of that config item.
12
13 Returns 0 on success.
14 ";
14 ";
15
15
16 pub fn args() -> clap::App<'static, 'static> {
16 pub fn args() -> clap::App<'static, 'static> {
17 clap::SubCommand::with_name("root").about(HELP_TEXT)
17 clap::SubCommand::with_name("config")
18 .arg(
19 Arg::with_name("name")
20 .help("the section.name to print")
21 .value_name("NAME")
22 .required(true)
23 .takes_value(true),
24 )
25 .about(HELP_TEXT)
18 }
26 }
19
27
20 pub fn run(
28 pub fn run(
21 ui: &Ui,
29 ui: &Ui,
22 config: &Config,
30 config: &Config,
23 repo_path: Option<&Path>,
31 repo_path: Option<&Path>,
24 _args: &ArgMatches,
32 args: &ArgMatches,
25 ) -> Result<(), CommandError> {
33 ) -> Result<(), CommandError> {
26 let repo = Repo::find(config, repo_path)?;
34 let opt_repo = Repo::find_optional(config, repo_path)?;
27 let bytes = get_bytes_from_path(repo.working_directory_path());
35 let config = if let Some(repo) = &opt_repo {
28 ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
36 repo.config()
37 } else {
38 config
39 };
40
41 let (section, name) = args
42 .value_of("name")
43 .expect("missing required CLI argument")
44 .as_bytes()
45 .split_2(b'.')
46 .ok_or_else(|| HgError::abort(""))?;
47
48 let value = config.get(section, name).unwrap_or(b"");
49
50 ui.write_stdout(&format_bytes!(b"{}\n", value))?;
29 Ok(())
51 Ok(())
30 }
52 }
@@ -1,137 +1,138
1 extern crate log;
1 extern crate log;
2 use clap::App;
2 use clap::App;
3 use clap::AppSettings;
3 use clap::AppSettings;
4 use clap::Arg;
4 use clap::Arg;
5 use clap::ArgMatches;
5 use clap::ArgMatches;
6 use format_bytes::format_bytes;
6 use format_bytes::format_bytes;
7 use std::path::Path;
7 use std::path::Path;
8
8
9 mod error;
9 mod error;
10 mod exitcode;
10 mod exitcode;
11 mod ui;
11 mod ui;
12 use error::CommandError;
12 use error::CommandError;
13
13
14 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
14 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
15 app.arg(
15 app.arg(
16 Arg::with_name("repository")
16 Arg::with_name("repository")
17 .help("repository root directory")
17 .help("repository root directory")
18 .short("-R")
18 .short("-R")
19 .long("--repository")
19 .long("--repository")
20 .value_name("REPO")
20 .value_name("REPO")
21 .takes_value(true),
21 .takes_value(true),
22 )
22 )
23 .arg(
23 .arg(
24 Arg::with_name("config")
24 Arg::with_name("config")
25 .help("set/override config option (use 'section.name=value')")
25 .help("set/override config option (use 'section.name=value')")
26 .long("--config")
26 .long("--config")
27 .value_name("CONFIG")
27 .value_name("CONFIG")
28 .takes_value(true)
28 .takes_value(true)
29 // Ok: `--config section.key1=val --config section.key2=val2`
29 // Ok: `--config section.key1=val --config section.key2=val2`
30 .multiple(true)
30 .multiple(true)
31 // Not ok: `--config section.key1=val section.key2=val2`
31 // Not ok: `--config section.key1=val section.key2=val2`
32 .number_of_values(1),
32 .number_of_values(1),
33 )
33 )
34 }
34 }
35
35
36 fn main() {
36 fn main() {
37 env_logger::init();
37 env_logger::init();
38 let app = App::new("rhg")
38 let app = App::new("rhg")
39 .setting(AppSettings::AllowInvalidUtf8)
39 .setting(AppSettings::AllowInvalidUtf8)
40 .setting(AppSettings::SubcommandRequired)
40 .setting(AppSettings::SubcommandRequired)
41 .setting(AppSettings::VersionlessSubcommands)
41 .setting(AppSettings::VersionlessSubcommands)
42 .version("0.0.1");
42 .version("0.0.1");
43 let app = add_global_args(app);
43 let app = add_global_args(app);
44 let app = add_subcommand_args(app);
44 let app = add_subcommand_args(app);
45
45
46 let ui = ui::Ui::new();
46 let ui = ui::Ui::new();
47
47
48 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
48 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
49 let _ = ui.writeln_stderr_str(&err.message);
49 let _ = ui.writeln_stderr_str(&err.message);
50 std::process::exit(exitcode::UNIMPLEMENTED)
50 std::process::exit(exitcode::UNIMPLEMENTED)
51 });
51 });
52
52
53 let (subcommand_name, subcommand_matches) = matches.subcommand();
53 let (subcommand_name, subcommand_matches) = matches.subcommand();
54 let run = subcommand_run_fn(subcommand_name)
54 let run = subcommand_run_fn(subcommand_name)
55 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
55 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
56 let args = subcommand_matches
56 let args = subcommand_matches
57 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
57 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
58
58
59 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
59 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
60 // `hg log -R ./foo`
60 // `hg log -R ./foo`
61 let value_of_global_arg =
61 let value_of_global_arg =
62 |name| args.value_of_os(name).or_else(|| matches.value_of_os(name));
62 |name| args.value_of_os(name).or_else(|| matches.value_of_os(name));
63 // For arguments where multiple occurences are allowed, return a
63 // For arguments where multiple occurences are allowed, return a
64 // possibly-iterator of all values.
64 // possibly-iterator of all values.
65 let values_of_global_arg = |name: &str| {
65 let values_of_global_arg = |name: &str| {
66 let a = matches.values_of_os(name).into_iter().flatten();
66 let a = matches.values_of_os(name).into_iter().flatten();
67 let b = args.values_of_os(name).into_iter().flatten();
67 let b = args.values_of_os(name).into_iter().flatten();
68 a.chain(b)
68 a.chain(b)
69 };
69 };
70
70
71 let repo_path = value_of_global_arg("repository").map(Path::new);
71 let repo_path = value_of_global_arg("repository").map(Path::new);
72 let result = (|| -> Result<(), CommandError> {
72 let result = (|| -> Result<(), CommandError> {
73 let config_args = values_of_global_arg("config")
73 let config_args = values_of_global_arg("config")
74 // `get_bytes_from_path` works for OsStr the same as for Path
74 // `get_bytes_from_path` works for OsStr the same as for Path
75 .map(hg::utils::files::get_bytes_from_path);
75 .map(hg::utils::files::get_bytes_from_path);
76 let config = hg::config::Config::load(config_args)?;
76 let config = hg::config::Config::load(config_args)?;
77 run(&ui, &config, repo_path, args)
77 run(&ui, &config, repo_path, args)
78 })();
78 })();
79
79
80 let exit_code = match result {
80 let exit_code = match result {
81 Ok(_) => exitcode::OK,
81 Ok(_) => exitcode::OK,
82
82
83 // Exit with a specific code and no error message to let a potential
83 // Exit with a specific code and no error message to let a potential
84 // wrapper script fallback to Python-based Mercurial.
84 // wrapper script fallback to Python-based Mercurial.
85 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
85 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
86
86
87 Err(CommandError::Abort { message }) => {
87 Err(CommandError::Abort { message }) => {
88 if !message.is_empty() {
88 if !message.is_empty() {
89 // Ignore errors when writing to stderr, we’re already exiting
89 // Ignore errors when writing to stderr, we’re already exiting
90 // with failure code so there’s not much more we can do.
90 // with failure code so there’s not much more we can do.
91 let _ =
91 let _ =
92 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
92 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
93 }
93 }
94 exitcode::ABORT
94 exitcode::ABORT
95 }
95 }
96 };
96 };
97 std::process::exit(exit_code)
97 std::process::exit(exit_code)
98 }
98 }
99
99
100 macro_rules! subcommands {
100 macro_rules! subcommands {
101 ($( $command: ident )+) => {
101 ($( $command: ident )+) => {
102 mod commands {
102 mod commands {
103 $(
103 $(
104 pub mod $command;
104 pub mod $command;
105 )+
105 )+
106 }
106 }
107
107
108 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
108 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
109 app
109 app
110 $(
110 $(
111 .subcommand(add_global_args(commands::$command::args()))
111 .subcommand(add_global_args(commands::$command::args()))
112 )+
112 )+
113 }
113 }
114
114
115 fn subcommand_run_fn(name: &str) -> Option<fn(
115 fn subcommand_run_fn(name: &str) -> Option<fn(
116 &ui::Ui,
116 &ui::Ui,
117 &hg::config::Config,
117 &hg::config::Config,
118 Option<&Path>,
118 Option<&Path>,
119 &ArgMatches,
119 &ArgMatches,
120 ) -> Result<(), CommandError>> {
120 ) -> Result<(), CommandError>> {
121 match name {
121 match name {
122 $(
122 $(
123 stringify!($command) => Some(commands::$command::run),
123 stringify!($command) => Some(commands::$command::run),
124 )+
124 )+
125 _ => None,
125 _ => None,
126 }
126 }
127 }
127 }
128 };
128 };
129 }
129 }
130
130
131 subcommands! {
131 subcommands! {
132 cat
132 cat
133 debugdata
133 debugdata
134 debugrequirements
134 debugrequirements
135 files
135 files
136 root
136 root
137 config
137 }
138 }
@@ -1,257 +1,269
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 [OPTIONS] <SUBCOMMAND>
18 rhg [OPTIONS] <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 Reading and setting configuration
34 $ echo "[ui]" >> $HGRCPATH
35 $ echo "username = user1" >> $HGRCPATH
36 $ rhg config ui.username
37 user1
38 $ echo "[ui]" >> .hg/hgrc
39 $ echo "username = user2" >> .hg/hgrc
40 $ rhg config ui.username
41 user2
42 $ rhg --config ui.username=user3 config ui.username
43 user3
44
33 Unwritable file descriptor
45 Unwritable file descriptor
34 $ rhg root > /dev/full
46 $ rhg root > /dev/full
35 abort: No space left on device (os error 28)
47 abort: No space left on device (os error 28)
36 [255]
48 [255]
37
49
38 Deleted repository
50 Deleted repository
39 $ rm -rf `pwd`
51 $ rm -rf `pwd`
40 $ rhg root
52 $ rhg root
41 abort: $ENOENT$: current directory
53 abort: $ENOENT$: current directory
42 [255]
54 [255]
43
55
44 Listing tracked files
56 Listing tracked files
45 $ cd $TESTTMP
57 $ cd $TESTTMP
46 $ hg init repository
58 $ hg init repository
47 $ cd repository
59 $ cd repository
48 $ for i in 1 2 3; do
60 $ for i in 1 2 3; do
49 > echo $i >> file$i
61 > echo $i >> file$i
50 > hg add file$i
62 > hg add file$i
51 > done
63 > done
52 > hg commit -m "commit $i" -q
64 > hg commit -m "commit $i" -q
53
65
54 Listing tracked files from root
66 Listing tracked files from root
55 $ rhg files
67 $ rhg files
56 file1
68 file1
57 file2
69 file2
58 file3
70 file3
59
71
60 Listing tracked files from subdirectory
72 Listing tracked files from subdirectory
61 $ mkdir -p path/to/directory
73 $ mkdir -p path/to/directory
62 $ cd path/to/directory
74 $ cd path/to/directory
63 $ rhg files
75 $ rhg files
64 ../../../file1
76 ../../../file1
65 ../../../file2
77 ../../../file2
66 ../../../file3
78 ../../../file3
67
79
68 Listing tracked files through broken pipe
80 Listing tracked files through broken pipe
69 $ rhg files | head -n 1
81 $ rhg files | head -n 1
70 ../../../file1
82 ../../../file1
71
83
72 Debuging data in inline index
84 Debuging data in inline index
73 $ cd $TESTTMP
85 $ cd $TESTTMP
74 $ rm -rf repository
86 $ rm -rf repository
75 $ hg init repository
87 $ hg init repository
76 $ cd repository
88 $ cd repository
77 $ for i in 1 2 3 4 5 6; do
89 $ for i in 1 2 3 4 5 6; do
78 > echo $i >> file-$i
90 > echo $i >> file-$i
79 > hg add file-$i
91 > hg add file-$i
80 > hg commit -m "Commit $i" -q
92 > hg commit -m "Commit $i" -q
81 > done
93 > done
82 $ rhg debugdata -c 2
94 $ rhg debugdata -c 2
83 8d0267cb034247ebfa5ee58ce59e22e57a492297
95 8d0267cb034247ebfa5ee58ce59e22e57a492297
84 test
96 test
85 0 0
97 0 0
86 file-3
98 file-3
87
99
88 Commit 3 (no-eol)
100 Commit 3 (no-eol)
89 $ rhg debugdata -m 2
101 $ rhg debugdata -m 2
90 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
102 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
91 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
103 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
92 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
104 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
93
105
94 Debuging with full node id
106 Debuging with full node id
95 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
107 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
96 d1d1c679d3053e8926061b6f45ca52009f011e3f
108 d1d1c679d3053e8926061b6f45ca52009f011e3f
97 test
109 test
98 0 0
110 0 0
99 file-1
111 file-1
100
112
101 Commit 1 (no-eol)
113 Commit 1 (no-eol)
102
114
103 Specifying revisions by changeset ID
115 Specifying revisions by changeset ID
104 $ hg log -T '{node}\n'
116 $ hg log -T '{node}\n'
105 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
117 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
106 d654274993d0149eecc3cc03214f598320211900
118 d654274993d0149eecc3cc03214f598320211900
107 f646af7e96481d3a5470b695cf30ad8e3ab6c575
119 f646af7e96481d3a5470b695cf30ad8e3ab6c575
108 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
120 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
109 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
121 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
110 6ae9681c6d30389694d8701faf24b583cf3ccafe
122 6ae9681c6d30389694d8701faf24b583cf3ccafe
111 $ rhg files -r cf8b83
123 $ rhg files -r cf8b83
112 file-1
124 file-1
113 file-2
125 file-2
114 file-3
126 file-3
115 $ rhg cat -r cf8b83 file-2
127 $ rhg cat -r cf8b83 file-2
116 2
128 2
117 $ rhg cat -r c file-2
129 $ rhg cat -r c file-2
118 abort: ambiguous revision identifier c
130 abort: ambiguous revision identifier c
119 [255]
131 [255]
120 $ rhg cat -r d file-2
132 $ rhg cat -r d file-2
121 2
133 2
122
134
123 Cat files
135 Cat files
124 $ cd $TESTTMP
136 $ cd $TESTTMP
125 $ rm -rf repository
137 $ rm -rf repository
126 $ hg init repository
138 $ hg init repository
127 $ cd repository
139 $ cd repository
128 $ echo "original content" > original
140 $ echo "original content" > original
129 $ hg add original
141 $ hg add original
130 $ hg commit -m "add original" original
142 $ hg commit -m "add original" original
131 $ rhg cat -r 0 original
143 $ rhg cat -r 0 original
132 original content
144 original content
133 Cat copied file should not display copy metadata
145 Cat copied file should not display copy metadata
134 $ hg copy original copy_of_original
146 $ hg copy original copy_of_original
135 $ hg commit -m "add copy of original"
147 $ hg commit -m "add copy of original"
136 $ rhg cat -r 1 copy_of_original
148 $ rhg cat -r 1 copy_of_original
137 original content
149 original content
138
150
139 Requirements
151 Requirements
140 $ rhg debugrequirements
152 $ rhg debugrequirements
141 dotencode
153 dotencode
142 fncache
154 fncache
143 generaldelta
155 generaldelta
144 revlogv1
156 revlogv1
145 sparserevlog
157 sparserevlog
146 store
158 store
147
159
148 $ echo indoor-pool >> .hg/requires
160 $ echo indoor-pool >> .hg/requires
149 $ rhg files
161 $ rhg files
150 [252]
162 [252]
151
163
152 $ rhg cat -r 1 copy_of_original
164 $ rhg cat -r 1 copy_of_original
153 [252]
165 [252]
154
166
155 $ rhg debugrequirements
167 $ rhg debugrequirements
156 [252]
168 [252]
157
169
158 $ echo -e '\xFF' >> .hg/requires
170 $ echo -e '\xFF' >> .hg/requires
159 $ rhg debugrequirements
171 $ rhg debugrequirements
160 abort: corrupted repository: parse error in 'requires' file
172 abort: corrupted repository: parse error in 'requires' file
161 [255]
173 [255]
162
174
163 Persistent nodemap
175 Persistent nodemap
164 $ cd $TESTTMP
176 $ cd $TESTTMP
165 $ rm -rf repository
177 $ rm -rf repository
166 $ hg init repository
178 $ hg init repository
167 $ cd repository
179 $ cd repository
168 $ rhg debugrequirements | grep nodemap
180 $ rhg debugrequirements | grep nodemap
169 [1]
181 [1]
170 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
182 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
171 $ hg id -r tip
183 $ hg id -r tip
172 c3ae8dec9fad tip
184 c3ae8dec9fad tip
173 $ ls .hg/store/00changelog*
185 $ ls .hg/store/00changelog*
174 .hg/store/00changelog.d
186 .hg/store/00changelog.d
175 .hg/store/00changelog.i
187 .hg/store/00changelog.i
176 $ rhg files -r c3ae8dec9fad
188 $ rhg files -r c3ae8dec9fad
177 of
189 of
178
190
179 $ cd $TESTTMP
191 $ cd $TESTTMP
180 $ rm -rf repository
192 $ rm -rf repository
181 $ hg --config format.use-persistent-nodemap=True init repository
193 $ hg --config format.use-persistent-nodemap=True init repository
182 $ cd repository
194 $ cd repository
183 $ rhg debugrequirements | grep nodemap
195 $ rhg debugrequirements | grep nodemap
184 persistent-nodemap
196 persistent-nodemap
185 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
197 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
186 $ hg id -r tip
198 $ hg id -r tip
187 c3ae8dec9fad tip
199 c3ae8dec9fad tip
188 $ ls .hg/store/00changelog*
200 $ ls .hg/store/00changelog*
189 .hg/store/00changelog-*.nd (glob)
201 .hg/store/00changelog-*.nd (glob)
190 .hg/store/00changelog.d
202 .hg/store/00changelog.d
191 .hg/store/00changelog.i
203 .hg/store/00changelog.i
192 .hg/store/00changelog.n
204 .hg/store/00changelog.n
193
205
194 Specifying revisions by changeset ID
206 Specifying revisions by changeset ID
195 $ rhg files -r c3ae8dec9fad
207 $ rhg files -r c3ae8dec9fad
196 of
208 of
197 $ rhg cat -r c3ae8dec9fad of
209 $ rhg cat -r c3ae8dec9fad of
198 r5000
210 r5000
199
211
200 Crate a shared repository
212 Crate a shared repository
201
213
202 $ echo "[extensions]" >> $HGRCPATH
214 $ echo "[extensions]" >> $HGRCPATH
203 $ echo "share = " >> $HGRCPATH
215 $ echo "share = " >> $HGRCPATH
204
216
205 $ cd $TESTTMP
217 $ cd $TESTTMP
206 $ hg init repo1
218 $ hg init repo1
207 $ echo a > repo1/a
219 $ echo a > repo1/a
208 $ hg -R repo1 commit -A -m'init'
220 $ hg -R repo1 commit -A -m'init'
209 adding a
221 adding a
210
222
211 $ hg share repo1 repo2
223 $ hg share repo1 repo2
212 updating working directory
224 updating working directory
213 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214
226
215 And check that basic rhg commands work with sharing
227 And check that basic rhg commands work with sharing
216
228
217 $ rhg files -R repo2
229 $ rhg files -R repo2
218 repo2/a
230 repo2/a
219 $ rhg -R repo2 cat -r 0 repo2/a
231 $ rhg -R repo2 cat -r 0 repo2/a
220 a
232 a
221
233
222 Same with relative sharing
234 Same with relative sharing
223
235
224 $ hg share repo2 repo3 --relative
236 $ hg share repo2 repo3 --relative
225 updating working directory
237 updating working directory
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
238 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227
239
228 $ rhg files -R repo3
240 $ rhg files -R repo3
229 repo3/a
241 repo3/a
230 $ rhg -R repo3 cat -r 0 repo3/a
242 $ rhg -R repo3 cat -r 0 repo3/a
231 a
243 a
232
244
233 Same with share-safe
245 Same with share-safe
234
246
235 $ echo "[format]" >> $HGRCPATH
247 $ echo "[format]" >> $HGRCPATH
236 $ echo "use-share-safe = True" >> $HGRCPATH
248 $ echo "use-share-safe = True" >> $HGRCPATH
237
249
238 $ cd $TESTTMP
250 $ cd $TESTTMP
239 $ hg init repo4
251 $ hg init repo4
240 $ cd repo4
252 $ cd repo4
241 $ echo a > a
253 $ echo a > a
242 $ hg commit -A -m'init'
254 $ hg commit -A -m'init'
243 adding a
255 adding a
244
256
245 $ cd ..
257 $ cd ..
246 $ hg share repo4 repo5
258 $ hg share repo4 repo5
247 updating working directory
259 updating working directory
248 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
249
261
250 And check that basic rhg commands work with sharing
262 And check that basic rhg commands work with sharing
251
263
252 $ cd repo5
264 $ cd repo5
253 $ rhg files
265 $ rhg files
254 a
266 a
255 $ rhg cat -r 0 a
267 $ rhg cat -r 0 a
256 a
268 a
257
269
General Comments 0
You need to be logged in to leave comments. Login now