##// END OF EJS Templates
rust-config: add support for default config items...
Raphaël Gomès -
r51656:f8412da8 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (669 lines changed) Show them Hide them
@@ -0,0 +1,669
1 //! Code for parsing default Mercurial config items.
2 use itertools::Itertools;
3 use serde::Deserialize;
4
5 use crate::{errors::HgError, exit_codes, FastHashMap};
6
7 /// Corresponds to the structure of `mercurial/configitems.toml`.
8 #[derive(Debug, Deserialize)]
9 pub struct ConfigItems {
10 items: Vec<DefaultConfigItem>,
11 templates: FastHashMap<String, Vec<TemplateItem>>,
12 #[serde(rename = "template-applications")]
13 template_applications: Vec<TemplateApplication>,
14 }
15
16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
17 #[derive(Clone, Debug, PartialEq, Deserialize)]
18 #[serde(try_from = "RawDefaultConfigItem")]
19 pub struct DefaultConfigItem {
20 /// Section of the config the item is in (e.g. `[merge-tools]`)
21 section: String,
22 /// Name of the item (e.g. `meld.gui`)
23 name: String,
24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
25 default: Option<DefaultConfigItemType>,
26 /// If the config option is generic (e.g. `merge-tools.*`), defines
27 /// the priority of this item relative to other generic items.
28 /// If we're looking for <pattern>, then all generic items within the same
29 /// section will be sorted by order of priority, and the first regex match
30 /// against `name` is returned.
31 #[serde(default)]
32 priority: Option<isize>,
33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
34 /// option that is aliased to this one.
35 #[serde(default)]
36 alias: Vec<(String, String)>,
37 /// Whether the config item is marked as experimental
38 #[serde(default)]
39 experimental: bool,
40 /// The (possibly empty) docstring for the item
41 #[serde(default)]
42 documentation: String,
43 }
44
45 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
46 /// an intermediate step in deserialization.
47 #[derive(Clone, Debug, Deserialize)]
48 struct RawDefaultConfigItem {
49 section: String,
50 name: String,
51 default: Option<toml::Value>,
52 #[serde(rename = "default-type")]
53 default_type: Option<String>,
54 #[serde(default)]
55 priority: isize,
56 #[serde(default)]
57 generic: bool,
58 #[serde(default)]
59 alias: Vec<(String, String)>,
60 #[serde(default)]
61 experimental: bool,
62 #[serde(default)]
63 documentation: String,
64 }
65
66 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
67 type Error = HgError;
68
69 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
70 Ok(Self {
71 section: value.section,
72 name: value.name,
73 default: raw_default_to_concrete(
74 value.default_type,
75 value.default,
76 )?,
77 priority: if value.generic {
78 Some(value.priority)
79 } else {
80 None
81 },
82 alias: value.alias,
83 experimental: value.experimental,
84 documentation: value.documentation,
85 })
86 }
87 }
88
89 impl DefaultConfigItem {
90 fn is_generic(&self) -> bool {
91 self.priority.is_some()
92 }
93 }
94
95 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
96 type Error = HgError;
97
98 fn try_from(
99 value: &'a DefaultConfigItem,
100 ) -> Result<Option<&'a str>, Self::Error> {
101 match &value.default {
102 Some(default) => {
103 let err = HgError::abort(
104 format!(
105 "programming error: wrong query on config item '{}.{}'",
106 value.section,
107 value.name
108 ),
109 exit_codes::ABORT,
110 Some(format!(
111 "asked for '&str', type of default is '{}'",
112 default.type_str()
113 )),
114 );
115 match default {
116 DefaultConfigItemType::Primitive(toml::Value::String(
117 s,
118 )) => Ok(Some(s)),
119 _ => Err(err),
120 }
121 }
122 None => Ok(None),
123 }
124 }
125 }
126
127 impl TryFrom<&DefaultConfigItem> for Option<bool> {
128 type Error = HgError;
129
130 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
131 match &value.default {
132 Some(default) => {
133 let err = HgError::abort(
134 format!(
135 "programming error: wrong query on config item '{}.{}'",
136 value.section,
137 value.name
138 ),
139 exit_codes::ABORT,
140 Some(format!(
141 "asked for 'bool', type of default is '{}'",
142 default.type_str()
143 )),
144 );
145 match default {
146 DefaultConfigItemType::Primitive(
147 toml::Value::Boolean(b),
148 ) => Ok(Some(*b)),
149 _ => Err(err),
150 }
151 }
152 None => Ok(Some(false)),
153 }
154 }
155 }
156
157 impl TryFrom<&DefaultConfigItem> for Option<u32> {
158 type Error = HgError;
159
160 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
161 match &value.default {
162 Some(default) => {
163 let err = HgError::abort(
164 format!(
165 "programming error: wrong query on config item '{}.{}'",
166 value.section,
167 value.name
168 ),
169 exit_codes::ABORT,
170 Some(format!(
171 "asked for 'u32', type of default is '{}'",
172 default.type_str()
173 )),
174 );
175 match default {
176 DefaultConfigItemType::Primitive(
177 toml::Value::Integer(b),
178 ) => {
179 Ok(Some((*b).try_into().expect("TOML integer to u32")))
180 }
181 _ => Err(err),
182 }
183 }
184 None => Ok(None),
185 }
186 }
187 }
188
189 impl TryFrom<&DefaultConfigItem> for Option<u64> {
190 type Error = HgError;
191
192 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
193 match &value.default {
194 Some(default) => {
195 let err = HgError::abort(
196 format!(
197 "programming error: wrong query on config item '{}.{}'",
198 value.section,
199 value.name
200 ),
201 exit_codes::ABORT,
202 Some(format!(
203 "asked for 'u64', type of default is '{}'",
204 default.type_str()
205 )),
206 );
207 match default {
208 DefaultConfigItemType::Primitive(
209 toml::Value::Integer(b),
210 ) => {
211 Ok(Some((*b).try_into().expect("TOML integer to u64")))
212 }
213 _ => Err(err),
214 }
215 }
216 None => Ok(None),
217 }
218 }
219 }
220
221 /// Allows abstracting over more complex default values than just primitives.
222 /// The former `configitems.py` contained some dynamic code that is encoded
223 /// in this enum.
224 #[derive(Debug, PartialEq, Clone, Deserialize)]
225 pub enum DefaultConfigItemType {
226 /// Some primitive type (string, integer, boolean)
227 Primitive(toml::Value),
228 /// A dynamic value that will be given by the code at runtime
229 Dynamic,
230 /// An lazily-returned array (possibly only relevant in the Python impl)
231 /// Example: `lambda: [b"zstd", b"zlib"]`
232 Lambda(Vec<String>),
233 /// For now, a special case for `web.encoding` that points to the
234 /// `encoding.encoding` module in the Python impl so that local encoding
235 /// is correctly resolved at runtime
236 LazyModule(String),
237 ListType,
238 }
239
240 impl DefaultConfigItemType {
241 pub fn type_str(&self) -> &str {
242 match self {
243 DefaultConfigItemType::Primitive(primitive) => {
244 primitive.type_str()
245 }
246 DefaultConfigItemType::Dynamic => "dynamic",
247 DefaultConfigItemType::Lambda(_) => "lambda",
248 DefaultConfigItemType::LazyModule(_) => "lazy_module",
249 DefaultConfigItemType::ListType => "list_type",
250 }
251 }
252 }
253
254 /// Most of the fields are shared with [`DefaultConfigItem`].
255 #[derive(Debug, Clone, Deserialize)]
256 #[serde(try_from = "RawTemplateItem")]
257 struct TemplateItem {
258 suffix: String,
259 default: Option<DefaultConfigItemType>,
260 priority: Option<isize>,
261 #[serde(default)]
262 alias: Vec<(String, String)>,
263 #[serde(default)]
264 experimental: bool,
265 #[serde(default)]
266 documentation: String,
267 }
268
269 /// Corresponds to the raw (i.e. on disk) representation of a template item.
270 /// Used as an intermediate step in deserialization.
271 #[derive(Clone, Debug, Deserialize)]
272 struct RawTemplateItem {
273 suffix: String,
274 default: Option<toml::Value>,
275 #[serde(rename = "default-type")]
276 default_type: Option<String>,
277 #[serde(default)]
278 priority: isize,
279 #[serde(default)]
280 generic: bool,
281 #[serde(default)]
282 alias: Vec<(String, String)>,
283 #[serde(default)]
284 experimental: bool,
285 #[serde(default)]
286 documentation: String,
287 }
288
289 impl TemplateItem {
290 fn into_default_item(
291 self,
292 application: TemplateApplication,
293 ) -> DefaultConfigItem {
294 DefaultConfigItem {
295 section: application.section,
296 name: application
297 .prefix
298 .map(|prefix| format!("{}.{}", prefix, self.suffix))
299 .unwrap_or(self.suffix),
300 default: self.default,
301 priority: self.priority,
302 alias: self.alias,
303 experimental: self.experimental,
304 documentation: self.documentation,
305 }
306 }
307 }
308
309 impl TryFrom<RawTemplateItem> for TemplateItem {
310 type Error = HgError;
311
312 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
313 Ok(Self {
314 suffix: value.suffix,
315 default: raw_default_to_concrete(
316 value.default_type,
317 value.default,
318 )?,
319 priority: if value.generic {
320 Some(value.priority)
321 } else {
322 None
323 },
324 alias: value.alias,
325 experimental: value.experimental,
326 documentation: value.documentation,
327 })
328 }
329 }
330
331 /// Transforms the on-disk string-based representation of complex default types
332 /// to the concrete [`DefaultconfigItemType`].
333 fn raw_default_to_concrete(
334 default_type: Option<String>,
335 default: Option<toml::Value>,
336 ) -> Result<Option<DefaultConfigItemType>, HgError> {
337 Ok(match default_type.as_deref() {
338 None => default.as_ref().map(|default| {
339 DefaultConfigItemType::Primitive(default.to_owned())
340 }),
341 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
342 Some("list_type") => Some(DefaultConfigItemType::ListType),
343 Some("lambda") => match &default {
344 Some(default) => Some(DefaultConfigItemType::Lambda(
345 default.to_owned().try_into().map_err(|e| {
346 HgError::abort(
347 e.to_string(),
348 exit_codes::ABORT,
349 Some("Check 'mercurial/configitems.toml'".into()),
350 )
351 })?,
352 )),
353 None => {
354 return Err(HgError::abort(
355 "lambda defined with no return value".to_string(),
356 exit_codes::ABORT,
357 Some("Check 'mercurial/configitems.toml'".into()),
358 ))
359 }
360 },
361 Some("lazy_module") => match &default {
362 Some(default) => {
363 Some(DefaultConfigItemType::LazyModule(match default {
364 toml::Value::String(module) => module.to_owned(),
365 _ => {
366 return Err(HgError::abort(
367 "lazy_module module name should be a string"
368 .to_string(),
369 exit_codes::ABORT,
370 Some("Check 'mercurial/configitems.toml'".into()),
371 ))
372 }
373 }))
374 }
375 None => {
376 return Err(HgError::abort(
377 "lazy_module should have a default value".to_string(),
378 exit_codes::ABORT,
379 Some("Check 'mercurial/configitems.toml'".into()),
380 ))
381 }
382 },
383 Some(invalid) => {
384 return Err(HgError::abort(
385 format!("invalid default_type '{}'", invalid),
386 exit_codes::ABORT,
387 Some("Check 'mercurial/configitems.toml'".into()),
388 ))
389 }
390 })
391 }
392
393 #[derive(Debug, Clone, Deserialize)]
394 struct TemplateApplication {
395 template: String,
396 section: String,
397 #[serde(default)]
398 prefix: Option<String>,
399 }
400
401 /// Represents the (dynamic) set of default core Mercurial config items from
402 /// `mercurial/configitems.toml`.
403 #[derive(Clone, Debug, Default)]
404 pub struct DefaultConfig {
405 /// Mapping of section -> (mapping of name -> item)
406 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
407 }
408
409 impl DefaultConfig {
410 pub fn empty() -> DefaultConfig {
411 Self {
412 items: Default::default(),
413 }
414 }
415
416 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
417 #[logging_timer::time("trace")]
418 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
419 let mut from_file: ConfigItems =
420 toml::from_str(contents).map_err(|e| {
421 HgError::abort(
422 e.to_string(),
423 exit_codes::ABORT,
424 Some("Check 'mercurial/configitems.toml'".into()),
425 )
426 })?;
427
428 let mut flat_items = from_file.items;
429
430 for application in from_file.template_applications.drain(..) {
431 match from_file.templates.get(&application.template) {
432 None => return Err(
433 HgError::abort(
434 format!(
435 "template application refers to undefined template '{}'",
436 application.template
437 ),
438 exit_codes::ABORT,
439 Some("Check 'mercurial/configitems.toml'".into())
440 )
441 ),
442 Some(template_items) => {
443 for template_item in template_items {
444 flat_items.push(
445 template_item
446 .clone()
447 .into_default_item(application.clone()),
448 )
449 }
450 }
451 };
452 }
453
454 let items = flat_items.into_iter().fold(
455 FastHashMap::default(),
456 |mut acc, item| {
457 acc.entry(item.section.to_owned())
458 .or_insert_with(|| {
459 let mut section = FastHashMap::default();
460 section.insert(item.name.to_owned(), item.to_owned());
461 section
462 })
463 .insert(item.name.to_owned(), item);
464 acc
465 },
466 );
467
468 Ok(Self { items })
469 }
470
471 /// Return the default config item that matches `section` and `item`.
472 pub fn get(
473 &self,
474 section: &[u8],
475 item: &[u8],
476 ) -> Option<&DefaultConfigItem> {
477 // Core items must be valid UTF-8
478 let section = String::from_utf8_lossy(section);
479 let section_map = self.items.get(section.as_ref())?;
480 let item_name_lossy = String::from_utf8_lossy(item);
481 match section_map.get(item_name_lossy.as_ref()) {
482 Some(item) => Some(item),
483 None => {
484 for generic_item in section_map
485 .values()
486 .filter(|item| item.is_generic())
487 .sorted_by_key(|item| match item.priority {
488 Some(priority) => (priority, &item.name),
489 _ => unreachable!(),
490 })
491 {
492 if regex::bytes::Regex::new(&generic_item.name)
493 .expect("invalid regex in configitems")
494 .is_match(item)
495 {
496 return Some(generic_item);
497 }
498 }
499 None
500 }
501 }
502 }
503 }
504
505 #[cfg(test)]
506 mod tests {
507 use crate::config::config_items::{
508 DefaultConfigItem, DefaultConfigItemType,
509 };
510
511 use super::DefaultConfig;
512
513 #[test]
514 fn test_config_read() {
515 let contents = r#"
516 [[items]]
517 section = "alias"
518 name = "abcd.*"
519 default = 3
520 generic = true
521 priority = -1
522
523 [[items]]
524 section = "alias"
525 name = ".*"
526 default-type = "dynamic"
527 generic = true
528
529 [[items]]
530 section = "cmdserver"
531 name = "track-log"
532 default-type = "lambda"
533 default = [ "chgserver", "cmdserver", "repocache",]
534
535 [[items]]
536 section = "chgserver"
537 name = "idletimeout"
538 default = 3600
539
540 [[items]]
541 section = "cmdserver"
542 name = "message-encodings"
543 default-type = "list_type"
544
545 [[items]]
546 section = "web"
547 name = "encoding"
548 default-type = "lazy_module"
549 default = "encoding.encoding"
550
551 [[items]]
552 section = "command-templates"
553 name = "graphnode"
554 alias = [["ui", "graphnodetemplate"]]
555 documentation = """This is a docstring.
556 This is another line \
557 but this is not."""
558
559 [[items]]
560 section = "censor"
561 name = "policy"
562 default = "abort"
563 experimental = true
564
565 [[template-applications]]
566 template = "diff-options"
567 section = "commands"
568 prefix = "revert.interactive"
569
570 [[template-applications]]
571 template = "diff-options"
572 section = "diff"
573
574 [templates]
575 [[templates.diff-options]]
576 suffix = "nodates"
577 default = false
578
579 [[templates.diff-options]]
580 suffix = "showfunc"
581 default = false
582
583 [[templates.diff-options]]
584 suffix = "unified"
585 "#;
586 let res = DefaultConfig::from_contents(contents);
587 let config = match res {
588 Ok(config) => config,
589 Err(e) => panic!("{}", e),
590 };
591 let expected = DefaultConfigItem {
592 section: "censor".into(),
593 name: "policy".into(),
594 default: Some(DefaultConfigItemType::Primitive("abort".into())),
595 priority: None,
596 alias: vec![],
597 experimental: true,
598 documentation: "".into(),
599 };
600 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
601
602 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
603 // `abcd.*` has priority, so it should match first.
604 let expected = DefaultConfigItem {
605 section: "alias".into(),
606 name: "abcd.*".into(),
607 default: Some(DefaultConfigItemType::Primitive(3.into())),
608 priority: Some(-1),
609 alias: vec![],
610 experimental: false,
611 documentation: "".into(),
612 };
613 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
614
615 //... but if it doesn't, we should fallback to `.*`
616 let expected = DefaultConfigItem {
617 section: "alias".into(),
618 name: ".*".into(),
619 default: Some(DefaultConfigItemType::Dynamic),
620 priority: Some(0),
621 alias: vec![],
622 experimental: false,
623 documentation: "".into(),
624 };
625 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
626
627 let expected = DefaultConfigItem {
628 section: "chgserver".into(),
629 name: "idletimeout".into(),
630 default: Some(DefaultConfigItemType::Primitive(3600.into())),
631 priority: None,
632 alias: vec![],
633 experimental: false,
634 documentation: "".into(),
635 };
636 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
637
638 let expected = DefaultConfigItem {
639 section: "cmdserver".into(),
640 name: "track-log".into(),
641 default: Some(DefaultConfigItemType::Lambda(vec![
642 "chgserver".into(),
643 "cmdserver".into(),
644 "repocache".into(),
645 ])),
646 priority: None,
647 alias: vec![],
648 experimental: false,
649 documentation: "".into(),
650 };
651 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
652
653 let expected = DefaultConfigItem {
654 section: "command-templates".into(),
655 name: "graphnode".into(),
656 default: None,
657 priority: None,
658 alias: vec![("ui".into(), "graphnodetemplate".into())],
659 experimental: false,
660 documentation:
661 "This is a docstring.\nThis is another line but this is not."
662 .into(),
663 };
664 assert_eq!(
665 config.get(b"command-templates", b"graphnode"),
666 Some(&expected)
667 );
668 }
669 }
@@ -1,1435 +1,1525
1 1 # This file is automatically @generated by Cargo.
2 2 # It is not intended for manual editing.
3 3 version = 3
4 4
5 5 [[package]]
6 6 name = "adler"
7 7 version = "1.0.2"
8 8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 9 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
10 10
11 11 [[package]]
12 12 name = "ahash"
13 13 version = "0.8.2"
14 14 source = "registry+https://github.com/rust-lang/crates.io-index"
15 15 checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
16 16 dependencies = [
17 17 "cfg-if",
18 18 "once_cell",
19 19 "version_check",
20 20 ]
21 21
22 22 [[package]]
23 23 name = "aho-corasick"
24 24 version = "0.7.19"
25 25 source = "registry+https://github.com/rust-lang/crates.io-index"
26 26 checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
27 27 dependencies = [
28 28 "memchr",
29 29 ]
30 30
31 31 [[package]]
32 32 name = "android_system_properties"
33 33 version = "0.1.5"
34 34 source = "registry+https://github.com/rust-lang/crates.io-index"
35 35 checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
36 36 dependencies = [
37 37 "libc",
38 38 ]
39 39
40 40 [[package]]
41 41 name = "atty"
42 42 version = "0.2.14"
43 43 source = "registry+https://github.com/rust-lang/crates.io-index"
44 44 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
45 45 dependencies = [
46 46 "hermit-abi",
47 47 "libc",
48 48 "winapi",
49 49 ]
50 50
51 51 [[package]]
52 52 name = "autocfg"
53 53 version = "1.1.0"
54 54 source = "registry+https://github.com/rust-lang/crates.io-index"
55 55 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
56 56
57 57 [[package]]
58 58 name = "bitflags"
59 59 version = "1.3.2"
60 60 source = "registry+https://github.com/rust-lang/crates.io-index"
61 61 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
62 62
63 63 [[package]]
64 64 name = "bitmaps"
65 65 version = "2.1.0"
66 66 source = "registry+https://github.com/rust-lang/crates.io-index"
67 67 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
68 68 dependencies = [
69 69 "typenum",
70 70 ]
71 71
72 72 [[package]]
73 73 name = "block-buffer"
74 74 version = "0.9.0"
75 75 source = "registry+https://github.com/rust-lang/crates.io-index"
76 76 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
77 77 dependencies = [
78 78 "generic-array",
79 79 ]
80 80
81 81 [[package]]
82 82 name = "block-buffer"
83 83 version = "0.10.3"
84 84 source = "registry+https://github.com/rust-lang/crates.io-index"
85 85 checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
86 86 dependencies = [
87 87 "generic-array",
88 88 ]
89 89
90 90 [[package]]
91 91 name = "bumpalo"
92 92 version = "3.11.1"
93 93 source = "registry+https://github.com/rust-lang/crates.io-index"
94 94 checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
95 95
96 96 [[package]]
97 97 name = "byteorder"
98 98 version = "1.4.3"
99 99 source = "registry+https://github.com/rust-lang/crates.io-index"
100 100 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
101 101
102 102 [[package]]
103 103 name = "bytes-cast"
104 104 version = "0.3.0"
105 105 source = "registry+https://github.com/rust-lang/crates.io-index"
106 106 checksum = "a20de93b91d7703ca0e39e12930e310acec5ff4d715f4166e0ab026babb352e8"
107 107 dependencies = [
108 108 "bytes-cast-derive",
109 109 ]
110 110
111 111 [[package]]
112 112 name = "bytes-cast-derive"
113 113 version = "0.2.0"
114 114 source = "registry+https://github.com/rust-lang/crates.io-index"
115 115 checksum = "7470a6fcce58cde3d62cce758bf71007978b75247e6becd9255c9b884bcb4f71"
116 116 dependencies = [
117 117 "proc-macro2",
118 118 "quote",
119 119 "syn",
120 120 ]
121 121
122 122 [[package]]
123 123 name = "cc"
124 124 version = "1.0.76"
125 125 source = "registry+https://github.com/rust-lang/crates.io-index"
126 126 checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
127 127 dependencies = [
128 128 "jobserver",
129 129 ]
130 130
131 131 [[package]]
132 132 name = "cfg-if"
133 133 version = "1.0.0"
134 134 source = "registry+https://github.com/rust-lang/crates.io-index"
135 135 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
136 136
137 137 [[package]]
138 138 name = "chrono"
139 139 version = "0.4.23"
140 140 source = "registry+https://github.com/rust-lang/crates.io-index"
141 141 checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
142 142 dependencies = [
143 143 "iana-time-zone",
144 144 "js-sys",
145 145 "num-integer",
146 146 "num-traits",
147 147 "time",
148 148 "wasm-bindgen",
149 149 "winapi",
150 150 ]
151 151
152 152 [[package]]
153 153 name = "clap"
154 154 version = "4.0.24"
155 155 source = "registry+https://github.com/rust-lang/crates.io-index"
156 156 checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
157 157 dependencies = [
158 158 "atty",
159 159 "bitflags",
160 160 "clap_derive",
161 161 "clap_lex",
162 162 "once_cell",
163 163 "strsim",
164 164 "termcolor",
165 165 ]
166 166
167 167 [[package]]
168 168 name = "clap_derive"
169 169 version = "4.0.21"
170 170 source = "registry+https://github.com/rust-lang/crates.io-index"
171 171 checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
172 172 dependencies = [
173 173 "heck",
174 174 "proc-macro-error",
175 175 "proc-macro2",
176 176 "quote",
177 177 "syn",
178 178 ]
179 179
180 180 [[package]]
181 181 name = "clap_lex"
182 182 version = "0.3.0"
183 183 source = "registry+https://github.com/rust-lang/crates.io-index"
184 184 checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
185 185 dependencies = [
186 186 "os_str_bytes",
187 187 ]
188 188
189 189 [[package]]
190 190 name = "codespan-reporting"
191 191 version = "0.11.1"
192 192 source = "registry+https://github.com/rust-lang/crates.io-index"
193 193 checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
194 194 dependencies = [
195 195 "termcolor",
196 196 "unicode-width",
197 197 ]
198 198
199 199 [[package]]
200 200 name = "convert_case"
201 201 version = "0.4.0"
202 202 source = "registry+https://github.com/rust-lang/crates.io-index"
203 203 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
204 204
205 205 [[package]]
206 206 name = "core-foundation-sys"
207 207 version = "0.8.3"
208 208 source = "registry+https://github.com/rust-lang/crates.io-index"
209 209 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
210 210
211 211 [[package]]
212 212 name = "cpufeatures"
213 213 version = "0.2.5"
214 214 source = "registry+https://github.com/rust-lang/crates.io-index"
215 215 checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
216 216 dependencies = [
217 217 "libc",
218 218 ]
219 219
220 220 [[package]]
221 221 name = "cpython"
222 222 version = "0.7.1"
223 223 source = "registry+https://github.com/rust-lang/crates.io-index"
224 224 checksum = "3052106c29da7390237bc2310c1928335733b286287754ea85e6093d2495280e"
225 225 dependencies = [
226 226 "libc",
227 227 "num-traits",
228 228 "paste",
229 229 "python3-sys",
230 230 ]
231 231
232 232 [[package]]
233 233 name = "crc32fast"
234 234 version = "1.3.2"
235 235 source = "registry+https://github.com/rust-lang/crates.io-index"
236 236 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
237 237 dependencies = [
238 238 "cfg-if",
239 239 ]
240 240
241 241 [[package]]
242 242 name = "crossbeam-channel"
243 243 version = "0.5.6"
244 244 source = "registry+https://github.com/rust-lang/crates.io-index"
245 245 checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
246 246 dependencies = [
247 247 "cfg-if",
248 248 "crossbeam-utils",
249 249 ]
250 250
251 251 [[package]]
252 252 name = "crossbeam-deque"
253 253 version = "0.8.2"
254 254 source = "registry+https://github.com/rust-lang/crates.io-index"
255 255 checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
256 256 dependencies = [
257 257 "cfg-if",
258 258 "crossbeam-epoch",
259 259 "crossbeam-utils",
260 260 ]
261 261
262 262 [[package]]
263 263 name = "crossbeam-epoch"
264 264 version = "0.9.11"
265 265 source = "registry+https://github.com/rust-lang/crates.io-index"
266 266 checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
267 267 dependencies = [
268 268 "autocfg",
269 269 "cfg-if",
270 270 "crossbeam-utils",
271 271 "memoffset",
272 272 "scopeguard",
273 273 ]
274 274
275 275 [[package]]
276 276 name = "crossbeam-utils"
277 277 version = "0.8.12"
278 278 source = "registry+https://github.com/rust-lang/crates.io-index"
279 279 checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
280 280 dependencies = [
281 281 "cfg-if",
282 282 ]
283 283
284 284 [[package]]
285 285 name = "crypto-common"
286 286 version = "0.1.6"
287 287 source = "registry+https://github.com/rust-lang/crates.io-index"
288 288 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
289 289 dependencies = [
290 290 "generic-array",
291 291 "typenum",
292 292 ]
293 293
294 294 [[package]]
295 295 name = "ctor"
296 296 version = "0.1.26"
297 297 source = "registry+https://github.com/rust-lang/crates.io-index"
298 298 checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
299 299 dependencies = [
300 300 "quote",
301 301 "syn",
302 302 ]
303 303
304 304 [[package]]
305 305 name = "cxx"
306 306 version = "1.0.81"
307 307 source = "registry+https://github.com/rust-lang/crates.io-index"
308 308 checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
309 309 dependencies = [
310 310 "cc",
311 311 "cxxbridge-flags",
312 312 "cxxbridge-macro",
313 313 "link-cplusplus",
314 314 ]
315 315
316 316 [[package]]
317 317 name = "cxx-build"
318 318 version = "1.0.81"
319 319 source = "registry+https://github.com/rust-lang/crates.io-index"
320 320 checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
321 321 dependencies = [
322 322 "cc",
323 323 "codespan-reporting",
324 324 "once_cell",
325 325 "proc-macro2",
326 326 "quote",
327 327 "scratch",
328 328 "syn",
329 329 ]
330 330
331 331 [[package]]
332 332 name = "cxxbridge-flags"
333 333 version = "1.0.81"
334 334 source = "registry+https://github.com/rust-lang/crates.io-index"
335 335 checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
336 336
337 337 [[package]]
338 338 name = "cxxbridge-macro"
339 339 version = "1.0.81"
340 340 source = "registry+https://github.com/rust-lang/crates.io-index"
341 341 checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
342 342 dependencies = [
343 343 "proc-macro2",
344 344 "quote",
345 345 "syn",
346 346 ]
347 347
348 348 [[package]]
349 349 name = "derive_more"
350 350 version = "0.99.17"
351 351 source = "registry+https://github.com/rust-lang/crates.io-index"
352 352 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
353 353 dependencies = [
354 354 "convert_case",
355 355 "proc-macro2",
356 356 "quote",
357 357 "rustc_version",
358 358 "syn",
359 359 ]
360 360
361 361 [[package]]
362 362 name = "diff"
363 363 version = "0.1.13"
364 364 source = "registry+https://github.com/rust-lang/crates.io-index"
365 365 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
366 366
367 367 [[package]]
368 368 name = "digest"
369 369 version = "0.9.0"
370 370 source = "registry+https://github.com/rust-lang/crates.io-index"
371 371 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
372 372 dependencies = [
373 373 "generic-array",
374 374 ]
375 375
376 376 [[package]]
377 377 name = "digest"
378 378 version = "0.10.5"
379 379 source = "registry+https://github.com/rust-lang/crates.io-index"
380 380 checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
381 381 dependencies = [
382 382 "block-buffer 0.10.3",
383 383 "crypto-common",
384 384 ]
385 385
386 386 [[package]]
387 387 name = "either"
388 388 version = "1.8.0"
389 389 source = "registry+https://github.com/rust-lang/crates.io-index"
390 390 checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
391 391
392 392 [[package]]
393 393 name = "env_logger"
394 394 version = "0.9.3"
395 395 source = "registry+https://github.com/rust-lang/crates.io-index"
396 396 checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
397 397 dependencies = [
398 398 "atty",
399 399 "humantime",
400 400 "log",
401 401 "regex",
402 402 "termcolor",
403 403 ]
404 404
405 405 [[package]]
406 406 name = "fastrand"
407 407 version = "1.8.0"
408 408 source = "registry+https://github.com/rust-lang/crates.io-index"
409 409 checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
410 410 dependencies = [
411 411 "instant",
412 412 ]
413 413
414 414 [[package]]
415 415 name = "flate2"
416 416 version = "1.0.24"
417 417 source = "registry+https://github.com/rust-lang/crates.io-index"
418 418 checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
419 419 dependencies = [
420 420 "crc32fast",
421 421 "libz-sys",
422 422 "miniz_oxide",
423 423 ]
424 424
425 425 [[package]]
426 426 name = "format-bytes"
427 427 version = "0.3.0"
428 428 source = "registry+https://github.com/rust-lang/crates.io-index"
429 429 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
430 430 dependencies = [
431 431 "format-bytes-macros",
432 432 ]
433 433
434 434 [[package]]
435 435 name = "format-bytes-macros"
436 436 version = "0.4.0"
437 437 source = "registry+https://github.com/rust-lang/crates.io-index"
438 438 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
439 439 dependencies = [
440 440 "proc-macro2",
441 441 "quote",
442 442 "syn",
443 443 ]
444 444
445 445 [[package]]
446 446 name = "generic-array"
447 447 version = "0.14.6"
448 448 source = "registry+https://github.com/rust-lang/crates.io-index"
449 449 checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
450 450 dependencies = [
451 451 "typenum",
452 452 "version_check",
453 453 ]
454 454
455 455 [[package]]
456 456 name = "getrandom"
457 457 version = "0.1.16"
458 458 source = "registry+https://github.com/rust-lang/crates.io-index"
459 459 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
460 460 dependencies = [
461 461 "cfg-if",
462 462 "libc",
463 463 "wasi 0.9.0+wasi-snapshot-preview1",
464 464 ]
465 465
466 466 [[package]]
467 467 name = "getrandom"
468 468 version = "0.2.8"
469 469 source = "registry+https://github.com/rust-lang/crates.io-index"
470 470 checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
471 471 dependencies = [
472 472 "cfg-if",
473 473 "libc",
474 474 "wasi 0.11.0+wasi-snapshot-preview1",
475 475 ]
476 476
477 477 [[package]]
478 478 name = "hashbrown"
479 version = "0.12.3"
480 source = "registry+https://github.com/rust-lang/crates.io-index"
481 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
482
483 [[package]]
484 name = "hashbrown"
479 485 version = "0.13.1"
480 486 source = "registry+https://github.com/rust-lang/crates.io-index"
481 487 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
482 488 dependencies = [
483 489 "ahash",
484 490 "rayon",
485 491 ]
486 492
487 493 [[package]]
488 494 name = "heck"
489 495 version = "0.4.0"
490 496 source = "registry+https://github.com/rust-lang/crates.io-index"
491 497 checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
492 498
493 499 [[package]]
494 500 name = "hermit-abi"
495 501 version = "0.1.19"
496 502 source = "registry+https://github.com/rust-lang/crates.io-index"
497 503 checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
498 504 dependencies = [
499 505 "libc",
500 506 ]
501 507
502 508 [[package]]
503 509 name = "hex"
504 510 version = "0.4.3"
505 511 source = "registry+https://github.com/rust-lang/crates.io-index"
506 512 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
507 513
508 514 [[package]]
509 515 name = "hg-core"
510 516 version = "0.1.0"
511 517 dependencies = [
512 518 "bitflags",
513 519 "byteorder",
514 520 "bytes-cast",
515 521 "clap",
516 522 "crossbeam-channel",
517 523 "derive_more",
518 524 "flate2",
519 525 "format-bytes",
520 "hashbrown",
526 "hashbrown 0.13.1",
521 527 "home",
522 528 "im-rc",
523 529 "itertools",
524 530 "lazy_static",
525 531 "libc",
526 532 "log",
527 533 "logging_timer",
528 534 "memmap2",
529 535 "once_cell",
530 536 "pretty_assertions",
531 537 "rand 0.8.5",
532 538 "rand_distr",
533 539 "rand_pcg",
534 540 "rayon",
535 541 "regex",
536 542 "same-file",
537 543 "self_cell",
544 "serde",
538 545 "sha-1 0.10.0",
539 546 "tempfile",
540 547 "thread_local",
548 "toml",
541 549 "twox-hash",
542 550 "zstd",
543 551 ]
544 552
545 553 [[package]]
546 554 name = "hg-cpython"
547 555 version = "0.1.0"
548 556 dependencies = [
549 557 "cpython",
550 558 "crossbeam-channel",
551 559 "env_logger",
552 560 "hg-core",
553 561 "libc",
554 562 "log",
555 563 "stable_deref_trait",
556 564 "vcsgraph",
557 565 ]
558 566
559 567 [[package]]
560 568 name = "home"
561 569 version = "0.5.4"
562 570 source = "registry+https://github.com/rust-lang/crates.io-index"
563 571 checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
564 572 dependencies = [
565 573 "winapi",
566 574 ]
567 575
568 576 [[package]]
569 577 name = "humantime"
570 578 version = "2.1.0"
571 579 source = "registry+https://github.com/rust-lang/crates.io-index"
572 580 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
573 581
574 582 [[package]]
575 583 name = "iana-time-zone"
576 584 version = "0.1.53"
577 585 source = "registry+https://github.com/rust-lang/crates.io-index"
578 586 checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
579 587 dependencies = [
580 588 "android_system_properties",
581 589 "core-foundation-sys",
582 590 "iana-time-zone-haiku",
583 591 "js-sys",
584 592 "wasm-bindgen",
585 593 "winapi",
586 594 ]
587 595
588 596 [[package]]
589 597 name = "iana-time-zone-haiku"
590 598 version = "0.1.1"
591 599 source = "registry+https://github.com/rust-lang/crates.io-index"
592 600 checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
593 601 dependencies = [
594 602 "cxx",
595 603 "cxx-build",
596 604 ]
597 605
598 606 [[package]]
599 607 name = "im-rc"
600 608 version = "15.1.0"
601 609 source = "registry+https://github.com/rust-lang/crates.io-index"
602 610 checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
603 611 dependencies = [
604 612 "bitmaps",
605 613 "rand_core 0.6.4",
606 614 "rand_xoshiro",
607 615 "sized-chunks",
608 616 "typenum",
609 617 "version_check",
610 618 ]
611 619
612 620 [[package]]
621 name = "indexmap"
622 version = "1.9.2"
623 source = "registry+https://github.com/rust-lang/crates.io-index"
624 checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
625 dependencies = [
626 "autocfg",
627 "hashbrown 0.12.3",
628 ]
629
630 [[package]]
613 631 name = "instant"
614 632 version = "0.1.12"
615 633 source = "registry+https://github.com/rust-lang/crates.io-index"
616 634 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
617 635 dependencies = [
618 636 "cfg-if",
619 637 ]
620 638
621 639 [[package]]
622 640 name = "itertools"
623 641 version = "0.10.5"
624 642 source = "registry+https://github.com/rust-lang/crates.io-index"
625 643 checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
626 644 dependencies = [
627 645 "either",
628 646 ]
629 647
630 648 [[package]]
631 649 name = "jobserver"
632 650 version = "0.1.25"
633 651 source = "registry+https://github.com/rust-lang/crates.io-index"
634 652 checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
635 653 dependencies = [
636 654 "libc",
637 655 ]
638 656
639 657 [[package]]
640 658 name = "js-sys"
641 659 version = "0.3.60"
642 660 source = "registry+https://github.com/rust-lang/crates.io-index"
643 661 checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
644 662 dependencies = [
645 663 "wasm-bindgen",
646 664 ]
647 665
648 666 [[package]]
649 667 name = "lazy_static"
650 668 version = "1.4.0"
651 669 source = "registry+https://github.com/rust-lang/crates.io-index"
652 670 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
653 671
654 672 [[package]]
655 673 name = "libc"
656 674 version = "0.2.137"
657 675 source = "registry+https://github.com/rust-lang/crates.io-index"
658 676 checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
659 677
660 678 [[package]]
661 679 name = "libm"
662 680 version = "0.2.6"
663 681 source = "registry+https://github.com/rust-lang/crates.io-index"
664 682 checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
665 683
666 684 [[package]]
667 685 name = "libz-sys"
668 686 version = "1.1.8"
669 687 source = "registry+https://github.com/rust-lang/crates.io-index"
670 688 checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
671 689 dependencies = [
672 690 "cc",
673 691 "pkg-config",
674 692 "vcpkg",
675 693 ]
676 694
677 695 [[package]]
678 696 name = "link-cplusplus"
679 697 version = "1.0.7"
680 698 source = "registry+https://github.com/rust-lang/crates.io-index"
681 699 checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
682 700 dependencies = [
683 701 "cc",
684 702 ]
685 703
686 704 [[package]]
687 705 name = "log"
688 706 version = "0.4.17"
689 707 source = "registry+https://github.com/rust-lang/crates.io-index"
690 708 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
691 709 dependencies = [
692 710 "cfg-if",
693 711 ]
694 712
695 713 [[package]]
696 714 name = "logging_timer"
697 715 version = "1.1.0"
698 716 source = "registry+https://github.com/rust-lang/crates.io-index"
699 717 checksum = "64e96f261d684b7089aa576bb74e823241dccd994b27d30fabf1dcb3af284fe9"
700 718 dependencies = [
701 719 "log",
702 720 "logging_timer_proc_macros",
703 721 ]
704 722
705 723 [[package]]
706 724 name = "logging_timer_proc_macros"
707 725 version = "1.1.0"
708 726 source = "registry+https://github.com/rust-lang/crates.io-index"
709 727 checksum = "10a9062912d7952c5588cc474795e0b9ee008e7e6781127945b85413d4b99d81"
710 728 dependencies = [
711 729 "log",
712 730 "proc-macro2",
713 731 "quote",
714 732 "syn",
715 733 ]
716 734
717 735 [[package]]
718 736 name = "memchr"
719 737 version = "2.5.0"
720 738 source = "registry+https://github.com/rust-lang/crates.io-index"
721 739 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
722 740
723 741 [[package]]
724 742 name = "memmap2"
725 743 version = "0.5.8"
726 744 source = "registry+https://github.com/rust-lang/crates.io-index"
727 745 checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
728 746 dependencies = [
729 747 "libc",
730 748 "stable_deref_trait",
731 749 ]
732 750
733 751 [[package]]
734 752 name = "memoffset"
735 753 version = "0.6.5"
736 754 source = "registry+https://github.com/rust-lang/crates.io-index"
737 755 checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
738 756 dependencies = [
739 757 "autocfg",
740 758 ]
741 759
742 760 [[package]]
743 761 name = "miniz_oxide"
744 762 version = "0.5.4"
745 763 source = "registry+https://github.com/rust-lang/crates.io-index"
746 764 checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
747 765 dependencies = [
748 766 "adler",
749 767 ]
750 768
751 769 [[package]]
770 name = "nom8"
771 version = "0.2.0"
772 source = "registry+https://github.com/rust-lang/crates.io-index"
773 checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
774 dependencies = [
775 "memchr",
776 ]
777
778 [[package]]
752 779 name = "num-integer"
753 780 version = "0.1.45"
754 781 source = "registry+https://github.com/rust-lang/crates.io-index"
755 782 checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
756 783 dependencies = [
757 784 "autocfg",
758 785 "num-traits",
759 786 ]
760 787
761 788 [[package]]
762 789 name = "num-traits"
763 790 version = "0.2.15"
764 791 source = "registry+https://github.com/rust-lang/crates.io-index"
765 792 checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
766 793 dependencies = [
767 794 "autocfg",
768 795 "libm",
769 796 ]
770 797
771 798 [[package]]
772 799 name = "num_cpus"
773 800 version = "1.14.0"
774 801 source = "registry+https://github.com/rust-lang/crates.io-index"
775 802 checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
776 803 dependencies = [
777 804 "hermit-abi",
778 805 "libc",
779 806 ]
780 807
781 808 [[package]]
782 809 name = "once_cell"
783 810 version = "1.16.0"
784 811 source = "registry+https://github.com/rust-lang/crates.io-index"
785 812 checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
786 813
787 814 [[package]]
788 815 name = "opaque-debug"
789 816 version = "0.3.0"
790 817 source = "registry+https://github.com/rust-lang/crates.io-index"
791 818 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
792 819
793 820 [[package]]
794 821 name = "os_str_bytes"
795 822 version = "6.4.0"
796 823 source = "registry+https://github.com/rust-lang/crates.io-index"
797 824 checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
798 825
799 826 [[package]]
800 827 name = "output_vt100"
801 828 version = "0.1.3"
802 829 source = "registry+https://github.com/rust-lang/crates.io-index"
803 830 checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
804 831 dependencies = [
805 832 "winapi",
806 833 ]
807 834
808 835 [[package]]
809 836 name = "paste"
810 837 version = "1.0.9"
811 838 source = "registry+https://github.com/rust-lang/crates.io-index"
812 839 checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
813 840
814 841 [[package]]
815 842 name = "pkg-config"
816 843 version = "0.3.26"
817 844 source = "registry+https://github.com/rust-lang/crates.io-index"
818 845 checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
819 846
820 847 [[package]]
821 848 name = "ppv-lite86"
822 849 version = "0.2.17"
823 850 source = "registry+https://github.com/rust-lang/crates.io-index"
824 851 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
825 852
826 853 [[package]]
827 854 name = "pretty_assertions"
828 855 version = "1.3.0"
829 856 source = "registry+https://github.com/rust-lang/crates.io-index"
830 857 checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
831 858 dependencies = [
832 859 "ctor",
833 860 "diff",
834 861 "output_vt100",
835 862 "yansi",
836 863 ]
837 864
838 865 [[package]]
839 866 name = "proc-macro-error"
840 867 version = "1.0.4"
841 868 source = "registry+https://github.com/rust-lang/crates.io-index"
842 869 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
843 870 dependencies = [
844 871 "proc-macro-error-attr",
845 872 "proc-macro2",
846 873 "quote",
847 874 "syn",
848 875 "version_check",
849 876 ]
850 877
851 878 [[package]]
852 879 name = "proc-macro-error-attr"
853 880 version = "1.0.4"
854 881 source = "registry+https://github.com/rust-lang/crates.io-index"
855 882 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
856 883 dependencies = [
857 884 "proc-macro2",
858 885 "quote",
859 886 "version_check",
860 887 ]
861 888
862 889 [[package]]
863 890 name = "proc-macro2"
864 891 version = "1.0.47"
865 892 source = "registry+https://github.com/rust-lang/crates.io-index"
866 893 checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
867 894 dependencies = [
868 895 "unicode-ident",
869 896 ]
870 897
871 898 [[package]]
872 899 name = "python3-sys"
873 900 version = "0.7.1"
874 901 source = "registry+https://github.com/rust-lang/crates.io-index"
875 902 checksum = "49f8b50d72fb3015735aa403eebf19bbd72c093bfeeae24ee798be5f2f1aab52"
876 903 dependencies = [
877 904 "libc",
878 905 "regex",
879 906 ]
880 907
881 908 [[package]]
882 909 name = "quote"
883 910 version = "1.0.21"
884 911 source = "registry+https://github.com/rust-lang/crates.io-index"
885 912 checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
886 913 dependencies = [
887 914 "proc-macro2",
888 915 ]
889 916
890 917 [[package]]
891 918 name = "rand"
892 919 version = "0.7.3"
893 920 source = "registry+https://github.com/rust-lang/crates.io-index"
894 921 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
895 922 dependencies = [
896 923 "getrandom 0.1.16",
897 924 "libc",
898 925 "rand_chacha 0.2.2",
899 926 "rand_core 0.5.1",
900 927 "rand_hc",
901 928 ]
902 929
903 930 [[package]]
904 931 name = "rand"
905 932 version = "0.8.5"
906 933 source = "registry+https://github.com/rust-lang/crates.io-index"
907 934 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
908 935 dependencies = [
909 936 "libc",
910 937 "rand_chacha 0.3.1",
911 938 "rand_core 0.6.4",
912 939 ]
913 940
914 941 [[package]]
915 942 name = "rand_chacha"
916 943 version = "0.2.2"
917 944 source = "registry+https://github.com/rust-lang/crates.io-index"
918 945 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
919 946 dependencies = [
920 947 "ppv-lite86",
921 948 "rand_core 0.5.1",
922 949 ]
923 950
924 951 [[package]]
925 952 name = "rand_chacha"
926 953 version = "0.3.1"
927 954 source = "registry+https://github.com/rust-lang/crates.io-index"
928 955 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
929 956 dependencies = [
930 957 "ppv-lite86",
931 958 "rand_core 0.6.4",
932 959 ]
933 960
934 961 [[package]]
935 962 name = "rand_core"
936 963 version = "0.5.1"
937 964 source = "registry+https://github.com/rust-lang/crates.io-index"
938 965 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
939 966 dependencies = [
940 967 "getrandom 0.1.16",
941 968 ]
942 969
943 970 [[package]]
944 971 name = "rand_core"
945 972 version = "0.6.4"
946 973 source = "registry+https://github.com/rust-lang/crates.io-index"
947 974 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
948 975 dependencies = [
949 976 "getrandom 0.2.8",
950 977 ]
951 978
952 979 [[package]]
953 980 name = "rand_distr"
954 981 version = "0.4.3"
955 982 source = "registry+https://github.com/rust-lang/crates.io-index"
956 983 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
957 984 dependencies = [
958 985 "num-traits",
959 986 "rand 0.8.5",
960 987 ]
961 988
962 989 [[package]]
963 990 name = "rand_hc"
964 991 version = "0.2.0"
965 992 source = "registry+https://github.com/rust-lang/crates.io-index"
966 993 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
967 994 dependencies = [
968 995 "rand_core 0.5.1",
969 996 ]
970 997
971 998 [[package]]
972 999 name = "rand_pcg"
973 1000 version = "0.3.1"
974 1001 source = "registry+https://github.com/rust-lang/crates.io-index"
975 1002 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
976 1003 dependencies = [
977 1004 "rand_core 0.6.4",
978 1005 ]
979 1006
980 1007 [[package]]
981 1008 name = "rand_xoshiro"
982 1009 version = "0.6.0"
983 1010 source = "registry+https://github.com/rust-lang/crates.io-index"
984 1011 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
985 1012 dependencies = [
986 1013 "rand_core 0.6.4",
987 1014 ]
988 1015
989 1016 [[package]]
990 1017 name = "rayon"
991 1018 version = "1.7.0"
992 1019 source = "registry+https://github.com/rust-lang/crates.io-index"
993 1020 checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
994 1021 dependencies = [
995 1022 "either",
996 1023 "rayon-core",
997 1024 ]
998 1025
999 1026 [[package]]
1000 1027 name = "rayon-core"
1001 1028 version = "1.11.0"
1002 1029 source = "registry+https://github.com/rust-lang/crates.io-index"
1003 1030 checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1004 1031 dependencies = [
1005 1032 "crossbeam-channel",
1006 1033 "crossbeam-deque",
1007 1034 "crossbeam-utils",
1008 1035 "num_cpus",
1009 1036 ]
1010 1037
1011 1038 [[package]]
1012 1039 name = "redox_syscall"
1013 1040 version = "0.2.16"
1014 1041 source = "registry+https://github.com/rust-lang/crates.io-index"
1015 1042 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1016 1043 dependencies = [
1017 1044 "bitflags",
1018 1045 ]
1019 1046
1020 1047 [[package]]
1021 1048 name = "regex"
1022 1049 version = "1.7.0"
1023 1050 source = "registry+https://github.com/rust-lang/crates.io-index"
1024 1051 checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
1025 1052 dependencies = [
1026 1053 "aho-corasick",
1027 1054 "memchr",
1028 1055 "regex-syntax",
1029 1056 ]
1030 1057
1031 1058 [[package]]
1032 1059 name = "regex-syntax"
1033 1060 version = "0.6.28"
1034 1061 source = "registry+https://github.com/rust-lang/crates.io-index"
1035 1062 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
1036 1063
1037 1064 [[package]]
1038 1065 name = "remove_dir_all"
1039 1066 version = "0.5.3"
1040 1067 source = "registry+https://github.com/rust-lang/crates.io-index"
1041 1068 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1042 1069 dependencies = [
1043 1070 "winapi",
1044 1071 ]
1045 1072
1046 1073 [[package]]
1047 1074 name = "rhg"
1048 1075 version = "0.1.0"
1049 1076 dependencies = [
1050 1077 "atty",
1051 1078 "chrono",
1052 1079 "clap",
1053 1080 "derive_more",
1054 1081 "env_logger",
1055 1082 "format-bytes",
1056 1083 "hg-core",
1057 1084 "home",
1058 1085 "lazy_static",
1059 1086 "log",
1060 1087 "logging_timer",
1061 1088 "rayon",
1062 1089 "regex",
1063 1090 "which",
1064 1091 "whoami",
1065 1092 ]
1066 1093
1067 1094 [[package]]
1068 1095 name = "rustc_version"
1069 1096 version = "0.4.0"
1070 1097 source = "registry+https://github.com/rust-lang/crates.io-index"
1071 1098 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1072 1099 dependencies = [
1073 1100 "semver",
1074 1101 ]
1075 1102
1076 1103 [[package]]
1077 1104 name = "same-file"
1078 1105 version = "1.0.6"
1079 1106 source = "registry+https://github.com/rust-lang/crates.io-index"
1080 1107 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1081 1108 dependencies = [
1082 1109 "winapi-util",
1083 1110 ]
1084 1111
1085 1112 [[package]]
1086 1113 name = "scopeguard"
1087 1114 version = "1.1.0"
1088 1115 source = "registry+https://github.com/rust-lang/crates.io-index"
1089 1116 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1090 1117
1091 1118 [[package]]
1092 1119 name = "scratch"
1093 1120 version = "1.0.2"
1094 1121 source = "registry+https://github.com/rust-lang/crates.io-index"
1095 1122 checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
1096 1123
1097 1124 [[package]]
1098 1125 name = "self_cell"
1099 1126 version = "1.0.0"
1100 1127 source = "registry+https://github.com/rust-lang/crates.io-index"
1101 1128 checksum = "4a3926e239738d36060909ffe6f511502f92149a45a1fade7fe031cb2d33e88b"
1102 1129
1103 1130 [[package]]
1104 1131 name = "semver"
1105 1132 version = "1.0.14"
1106 1133 source = "registry+https://github.com/rust-lang/crates.io-index"
1107 1134 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1108 1135
1109 1136 [[package]]
1137 name = "serde"
1138 version = "1.0.152"
1139 source = "registry+https://github.com/rust-lang/crates.io-index"
1140 checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
1141 dependencies = [
1142 "serde_derive",
1143 ]
1144
1145 [[package]]
1146 name = "serde_derive"
1147 version = "1.0.152"
1148 source = "registry+https://github.com/rust-lang/crates.io-index"
1149 checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
1150 dependencies = [
1151 "proc-macro2",
1152 "quote",
1153 "syn",
1154 ]
1155
1156 [[package]]
1157 name = "serde_spanned"
1158 version = "0.6.1"
1159 source = "registry+https://github.com/rust-lang/crates.io-index"
1160 checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
1161 dependencies = [
1162 "serde",
1163 ]
1164
1165 [[package]]
1110 1166 name = "sha-1"
1111 1167 version = "0.9.8"
1112 1168 source = "registry+https://github.com/rust-lang/crates.io-index"
1113 1169 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
1114 1170 dependencies = [
1115 1171 "block-buffer 0.9.0",
1116 1172 "cfg-if",
1117 1173 "cpufeatures",
1118 1174 "digest 0.9.0",
1119 1175 "opaque-debug",
1120 1176 ]
1121 1177
1122 1178 [[package]]
1123 1179 name = "sha-1"
1124 1180 version = "0.10.0"
1125 1181 source = "registry+https://github.com/rust-lang/crates.io-index"
1126 1182 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1127 1183 dependencies = [
1128 1184 "cfg-if",
1129 1185 "cpufeatures",
1130 1186 "digest 0.10.5",
1131 1187 ]
1132 1188
1133 1189 [[package]]
1134 1190 name = "sized-chunks"
1135 1191 version = "0.6.5"
1136 1192 source = "registry+https://github.com/rust-lang/crates.io-index"
1137 1193 checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
1138 1194 dependencies = [
1139 1195 "bitmaps",
1140 1196 "typenum",
1141 1197 ]
1142 1198
1143 1199 [[package]]
1144 1200 name = "stable_deref_trait"
1145 1201 version = "1.2.0"
1146 1202 source = "registry+https://github.com/rust-lang/crates.io-index"
1147 1203 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1148 1204
1149 1205 [[package]]
1150 1206 name = "static_assertions"
1151 1207 version = "1.1.0"
1152 1208 source = "registry+https://github.com/rust-lang/crates.io-index"
1153 1209 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1154 1210
1155 1211 [[package]]
1156 1212 name = "strsim"
1157 1213 version = "0.10.0"
1158 1214 source = "registry+https://github.com/rust-lang/crates.io-index"
1159 1215 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1160 1216
1161 1217 [[package]]
1162 1218 name = "syn"
1163 version = "1.0.103"
1219 version = "1.0.109"
1164 1220 source = "registry+https://github.com/rust-lang/crates.io-index"
1165 checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
1221 checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1166 1222 dependencies = [
1167 1223 "proc-macro2",
1168 1224 "quote",
1169 1225 "unicode-ident",
1170 1226 ]
1171 1227
1172 1228 [[package]]
1173 1229 name = "tempfile"
1174 1230 version = "3.3.0"
1175 1231 source = "registry+https://github.com/rust-lang/crates.io-index"
1176 1232 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1177 1233 dependencies = [
1178 1234 "cfg-if",
1179 1235 "fastrand",
1180 1236 "libc",
1181 1237 "redox_syscall",
1182 1238 "remove_dir_all",
1183 1239 "winapi",
1184 1240 ]
1185 1241
1186 1242 [[package]]
1187 1243 name = "termcolor"
1188 1244 version = "1.1.3"
1189 1245 source = "registry+https://github.com/rust-lang/crates.io-index"
1190 1246 checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1191 1247 dependencies = [
1192 1248 "winapi-util",
1193 1249 ]
1194 1250
1195 1251 [[package]]
1196 1252 name = "thread_local"
1197 1253 version = "1.1.4"
1198 1254 source = "registry+https://github.com/rust-lang/crates.io-index"
1199 1255 checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
1200 1256 dependencies = [
1201 1257 "once_cell",
1202 1258 ]
1203 1259
1204 1260 [[package]]
1205 1261 name = "time"
1206 1262 version = "0.1.44"
1207 1263 source = "registry+https://github.com/rust-lang/crates.io-index"
1208 1264 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1209 1265 dependencies = [
1210 1266 "libc",
1211 1267 "wasi 0.10.0+wasi-snapshot-preview1",
1212 1268 "winapi",
1213 1269 ]
1214 1270
1215 1271 [[package]]
1272 name = "toml"
1273 version = "0.6.0"
1274 source = "registry+https://github.com/rust-lang/crates.io-index"
1275 checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
1276 dependencies = [
1277 "serde",
1278 "serde_spanned",
1279 "toml_datetime",
1280 "toml_edit",
1281 ]
1282
1283 [[package]]
1284 name = "toml_datetime"
1285 version = "0.5.1"
1286 source = "registry+https://github.com/rust-lang/crates.io-index"
1287 checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
1288 dependencies = [
1289 "serde",
1290 ]
1291
1292 [[package]]
1293 name = "toml_edit"
1294 version = "0.18.1"
1295 source = "registry+https://github.com/rust-lang/crates.io-index"
1296 checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
1297 dependencies = [
1298 "indexmap",
1299 "nom8",
1300 "serde",
1301 "serde_spanned",
1302 "toml_datetime",
1303 ]
1304
1305 [[package]]
1216 1306 name = "twox-hash"
1217 1307 version = "1.6.3"
1218 1308 source = "registry+https://github.com/rust-lang/crates.io-index"
1219 1309 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
1220 1310 dependencies = [
1221 1311 "cfg-if",
1222 1312 "rand 0.8.5",
1223 1313 "static_assertions",
1224 1314 ]
1225 1315
1226 1316 [[package]]
1227 1317 name = "typenum"
1228 1318 version = "1.15.0"
1229 1319 source = "registry+https://github.com/rust-lang/crates.io-index"
1230 1320 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
1231 1321
1232 1322 [[package]]
1233 1323 name = "unicode-ident"
1234 1324 version = "1.0.5"
1235 1325 source = "registry+https://github.com/rust-lang/crates.io-index"
1236 1326 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
1237 1327
1238 1328 [[package]]
1239 1329 name = "unicode-width"
1240 1330 version = "0.1.10"
1241 1331 source = "registry+https://github.com/rust-lang/crates.io-index"
1242 1332 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
1243 1333
1244 1334 [[package]]
1245 1335 name = "vcpkg"
1246 1336 version = "0.2.15"
1247 1337 source = "registry+https://github.com/rust-lang/crates.io-index"
1248 1338 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1249 1339
1250 1340 [[package]]
1251 1341 name = "vcsgraph"
1252 1342 version = "0.2.0"
1253 1343 source = "registry+https://github.com/rust-lang/crates.io-index"
1254 1344 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1255 1345 dependencies = [
1256 1346 "hex",
1257 1347 "rand 0.7.3",
1258 1348 "sha-1 0.9.8",
1259 1349 ]
1260 1350
1261 1351 [[package]]
1262 1352 name = "version_check"
1263 1353 version = "0.9.4"
1264 1354 source = "registry+https://github.com/rust-lang/crates.io-index"
1265 1355 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
1266 1356
1267 1357 [[package]]
1268 1358 name = "wasi"
1269 1359 version = "0.9.0+wasi-snapshot-preview1"
1270 1360 source = "registry+https://github.com/rust-lang/crates.io-index"
1271 1361 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1272 1362
1273 1363 [[package]]
1274 1364 name = "wasi"
1275 1365 version = "0.10.0+wasi-snapshot-preview1"
1276 1366 source = "registry+https://github.com/rust-lang/crates.io-index"
1277 1367 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1278 1368
1279 1369 [[package]]
1280 1370 name = "wasi"
1281 1371 version = "0.11.0+wasi-snapshot-preview1"
1282 1372 source = "registry+https://github.com/rust-lang/crates.io-index"
1283 1373 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1284 1374
1285 1375 [[package]]
1286 1376 name = "wasm-bindgen"
1287 1377 version = "0.2.83"
1288 1378 source = "registry+https://github.com/rust-lang/crates.io-index"
1289 1379 checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
1290 1380 dependencies = [
1291 1381 "cfg-if",
1292 1382 "wasm-bindgen-macro",
1293 1383 ]
1294 1384
1295 1385 [[package]]
1296 1386 name = "wasm-bindgen-backend"
1297 1387 version = "0.2.83"
1298 1388 source = "registry+https://github.com/rust-lang/crates.io-index"
1299 1389 checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
1300 1390 dependencies = [
1301 1391 "bumpalo",
1302 1392 "log",
1303 1393 "once_cell",
1304 1394 "proc-macro2",
1305 1395 "quote",
1306 1396 "syn",
1307 1397 "wasm-bindgen-shared",
1308 1398 ]
1309 1399
1310 1400 [[package]]
1311 1401 name = "wasm-bindgen-macro"
1312 1402 version = "0.2.83"
1313 1403 source = "registry+https://github.com/rust-lang/crates.io-index"
1314 1404 checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
1315 1405 dependencies = [
1316 1406 "quote",
1317 1407 "wasm-bindgen-macro-support",
1318 1408 ]
1319 1409
1320 1410 [[package]]
1321 1411 name = "wasm-bindgen-macro-support"
1322 1412 version = "0.2.83"
1323 1413 source = "registry+https://github.com/rust-lang/crates.io-index"
1324 1414 checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
1325 1415 dependencies = [
1326 1416 "proc-macro2",
1327 1417 "quote",
1328 1418 "syn",
1329 1419 "wasm-bindgen-backend",
1330 1420 "wasm-bindgen-shared",
1331 1421 ]
1332 1422
1333 1423 [[package]]
1334 1424 name = "wasm-bindgen-shared"
1335 1425 version = "0.2.83"
1336 1426 source = "registry+https://github.com/rust-lang/crates.io-index"
1337 1427 checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
1338 1428
1339 1429 [[package]]
1340 1430 name = "web-sys"
1341 1431 version = "0.3.60"
1342 1432 source = "registry+https://github.com/rust-lang/crates.io-index"
1343 1433 checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
1344 1434 dependencies = [
1345 1435 "js-sys",
1346 1436 "wasm-bindgen",
1347 1437 ]
1348 1438
1349 1439 [[package]]
1350 1440 name = "which"
1351 1441 version = "4.3.0"
1352 1442 source = "registry+https://github.com/rust-lang/crates.io-index"
1353 1443 checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
1354 1444 dependencies = [
1355 1445 "either",
1356 1446 "libc",
1357 1447 "once_cell",
1358 1448 ]
1359 1449
1360 1450 [[package]]
1361 1451 name = "whoami"
1362 1452 version = "1.4.0"
1363 1453 source = "registry+https://github.com/rust-lang/crates.io-index"
1364 1454 checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68"
1365 1455 dependencies = [
1366 1456 "wasm-bindgen",
1367 1457 "web-sys",
1368 1458 ]
1369 1459
1370 1460 [[package]]
1371 1461 name = "winapi"
1372 1462 version = "0.3.9"
1373 1463 source = "registry+https://github.com/rust-lang/crates.io-index"
1374 1464 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1375 1465 dependencies = [
1376 1466 "winapi-i686-pc-windows-gnu",
1377 1467 "winapi-x86_64-pc-windows-gnu",
1378 1468 ]
1379 1469
1380 1470 [[package]]
1381 1471 name = "winapi-i686-pc-windows-gnu"
1382 1472 version = "0.4.0"
1383 1473 source = "registry+https://github.com/rust-lang/crates.io-index"
1384 1474 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1385 1475
1386 1476 [[package]]
1387 1477 name = "winapi-util"
1388 1478 version = "0.1.5"
1389 1479 source = "registry+https://github.com/rust-lang/crates.io-index"
1390 1480 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1391 1481 dependencies = [
1392 1482 "winapi",
1393 1483 ]
1394 1484
1395 1485 [[package]]
1396 1486 name = "winapi-x86_64-pc-windows-gnu"
1397 1487 version = "0.4.0"
1398 1488 source = "registry+https://github.com/rust-lang/crates.io-index"
1399 1489 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1400 1490
1401 1491 [[package]]
1402 1492 name = "yansi"
1403 1493 version = "0.5.1"
1404 1494 source = "registry+https://github.com/rust-lang/crates.io-index"
1405 1495 checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
1406 1496
1407 1497 [[package]]
1408 1498 name = "zstd"
1409 1499 version = "0.12.3+zstd.1.5.2"
1410 1500 source = "registry+https://github.com/rust-lang/crates.io-index"
1411 1501 checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806"
1412 1502 dependencies = [
1413 1503 "zstd-safe",
1414 1504 ]
1415 1505
1416 1506 [[package]]
1417 1507 name = "zstd-safe"
1418 1508 version = "6.0.4+zstd.1.5.4"
1419 1509 source = "registry+https://github.com/rust-lang/crates.io-index"
1420 1510 checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543"
1421 1511 dependencies = [
1422 1512 "libc",
1423 1513 "zstd-sys",
1424 1514 ]
1425 1515
1426 1516 [[package]]
1427 1517 name = "zstd-sys"
1428 1518 version = "2.0.7+zstd.1.5.4"
1429 1519 source = "registry+https://github.com/rust-lang/crates.io-index"
1430 1520 checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5"
1431 1521 dependencies = [
1432 1522 "cc",
1433 1523 "libc",
1434 1524 "pkg-config",
1435 1525 ]
@@ -1,50 +1,52
1 1 [package]
2 2 name = "hg-core"
3 3 version = "0.1.0"
4 4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
6 6 edition = "2021"
7 7
8 8 [lib]
9 9 name = "hg"
10 10
11 11 [dependencies]
12 12 bitflags = "1.3.2"
13 13 bytes-cast = "0.3.0"
14 14 byteorder = "1.4.3"
15 15 derive_more = "0.99.17"
16 16 hashbrown = { version = "0.13.1", features = ["rayon"] }
17 17 home = "0.5.4"
18 18 im-rc = "15.1.0"
19 19 itertools = "0.10.5"
20 20 lazy_static = "1.4.0"
21 21 libc = "0.2.137"
22 22 logging_timer = "1.1.0"
23 23 rand = "0.8.5"
24 24 rand_pcg = "0.3.1"
25 25 rand_distr = "0.4.3"
26 26 rayon = "1.7.0"
27 27 regex = "1.7.0"
28 28 self_cell = "1.0"
29 serde = { version = "1.0", features = ["derive"] }
29 30 sha-1 = "0.10.0"
30 31 twox-hash = "1.6.3"
31 32 same-file = "1.0.6"
32 33 tempfile = "3.3.0"
34 toml = "0.6"
33 35 thread_local = "1.1.4"
34 36 crossbeam-channel = "0.5.6"
35 37 log = "0.4.17"
36 38 memmap2 = { version = "0.5.8", features = ["stable_deref_trait"] }
37 39 zstd = "0.12"
38 40 format-bytes = "0.3.0"
39 41 once_cell = "1.16.0"
40 42
41 43 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
42 44 # we have a clearer view of which backend is the fastest.
43 45 [dependencies.flate2]
44 46 version = "1.0.24"
45 47 features = ["zlib"]
46 48 default-features = false
47 49
48 50 [dev-dependencies]
49 clap = { version = "4.0.24", features = ["derive"] }
51 clap = { version = "~4.0", features = ["derive"] }
50 52 pretty_assertions = "1.1.0"
@@ -1,345 +1,349
1 1 // layer.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 use crate::errors::HgError;
11 11 use crate::exit_codes::CONFIG_PARSE_ERROR_ABORT;
12 12 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
13 13 use format_bytes::{format_bytes, write_bytes, DisplayBytes};
14 14 use lazy_static::lazy_static;
15 15 use regex::bytes::Regex;
16 16 use std::collections::HashMap;
17 17 use std::path::{Path, PathBuf};
18 18
19 19 lazy_static! {
20 20 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
21 21 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
22 22 /// Continuation whitespace
23 23 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
24 24 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
25 25 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
26 26 /// A directive that allows for removing previous entries
27 27 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
28 28 /// A directive that allows for including other config files
29 29 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
30 30 }
31 31
32 32 /// All config values separated by layers of precedence.
33 33 /// Each config source may be split in multiple layers if `%include` directives
34 34 /// are used.
35 35 /// TODO detail the general precedence
36 36 #[derive(Clone)]
37 37 pub struct ConfigLayer {
38 38 /// Mapping of the sections to their items
39 39 sections: HashMap<Vec<u8>, ConfigItem>,
40 40 /// All sections (and their items/values) in a layer share the same origin
41 41 pub origin: ConfigOrigin,
42 42 /// Whether this layer comes from a trusted user or group
43 43 pub trusted: bool,
44 44 }
45 45
46 46 impl ConfigLayer {
47 47 pub fn new(origin: ConfigOrigin) -> Self {
48 48 ConfigLayer {
49 49 sections: HashMap::new(),
50 50 trusted: true, // TODO check
51 51 origin,
52 52 }
53 53 }
54 54
55 55 /// Parse `--config` CLI arguments and return a layer if there’s any
56 56 pub(crate) fn parse_cli_args(
57 57 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
58 58 ) -> Result<Option<Self>, ConfigError> {
59 59 fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
60 60 use crate::utils::SliceExt;
61 61
62 62 let (section_and_item, value) = arg.split_2(b'=')?;
63 63 let (section, item) = section_and_item.trim().split_2(b'.')?;
64 64 Some((
65 65 section.to_owned(),
66 66 item.to_owned(),
67 67 value.trim().to_owned(),
68 68 ))
69 69 }
70 70
71 71 let mut layer = Self::new(ConfigOrigin::CommandLine);
72 72 for arg in cli_config_args {
73 73 let arg = arg.as_ref();
74 74 if let Some((section, item, value)) = parse_one(arg) {
75 75 layer.add(section, item, value, None);
76 76 } else {
77 77 Err(HgError::abort(
78 78 format!(
79 79 "abort: malformed --config option: '{}' \
80 80 (use --config section.name=value)",
81 81 String::from_utf8_lossy(arg),
82 82 ),
83 83 CONFIG_PARSE_ERROR_ABORT,
84 84 None,
85 85 ))?
86 86 }
87 87 }
88 88 if layer.sections.is_empty() {
89 89 Ok(None)
90 90 } else {
91 91 Ok(Some(layer))
92 92 }
93 93 }
94 94
95 95 /// Returns whether this layer comes from `--config` CLI arguments
96 96 pub(crate) fn is_from_command_line(&self) -> bool {
97 97 matches!(self.origin, ConfigOrigin::CommandLine)
98 98 }
99 99
100 100 /// Add an entry to the config, overwriting the old one if already present.
101 101 pub fn add(
102 102 &mut self,
103 103 section: Vec<u8>,
104 104 item: Vec<u8>,
105 105 value: Vec<u8>,
106 106 line: Option<usize>,
107 107 ) {
108 108 self.sections
109 109 .entry(section)
110 110 .or_insert_with(HashMap::new)
111 111 .insert(item, ConfigValue { bytes: value, line });
112 112 }
113 113
114 114 /// Returns the config value in `<section>.<item>` if it exists
115 115 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
116 116 self.sections.get(section)?.get(item)
117 117 }
118 118
119 119 /// Returns the keys defined in the given section
120 120 pub fn iter_keys(&self, section: &[u8]) -> impl Iterator<Item = &[u8]> {
121 121 self.sections
122 122 .get(section)
123 123 .into_iter()
124 124 .flat_map(|section| section.keys().map(|vec| &**vec))
125 125 }
126 126
127 127 /// Returns the (key, value) pairs defined in the given section
128 128 pub fn iter_section<'layer>(
129 129 &'layer self,
130 130 section: &[u8],
131 131 ) -> impl Iterator<Item = (&'layer [u8], &'layer [u8])> {
132 132 self.sections
133 133 .get(section)
134 134 .into_iter()
135 135 .flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes)))
136 136 }
137 137
138 138 /// Returns whether any key is defined in the given section
139 139 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
140 140 self.sections
141 141 .get(section)
142 142 .map_or(false, |section| !section.is_empty())
143 143 }
144 144
145 145 pub fn is_empty(&self) -> bool {
146 146 self.sections.is_empty()
147 147 }
148 148
149 149 /// Returns a `Vec` of layers in order of precedence (so, in read order),
150 150 /// recursively parsing the `%include` directives if any.
151 151 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
152 152 let mut layers = vec![];
153 153
154 154 // Discard byte order mark if any
155 155 let data = if data.starts_with(b"\xef\xbb\xbf") {
156 156 &data[3..]
157 157 } else {
158 158 data
159 159 };
160 160
161 161 // TODO check if it's trusted
162 162 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
163 163
164 164 let mut lines_iter =
165 165 data.split(|b| *b == b'\n').enumerate().peekable();
166 166 let mut section = b"".to_vec();
167 167
168 168 while let Some((index, bytes)) = lines_iter.next() {
169 169 let line = Some(index + 1);
170 170 if let Some(m) = INCLUDE_RE.captures(bytes) {
171 171 let filename_bytes = &m[1];
172 172 let filename_bytes = crate::utils::expand_vars(filename_bytes);
173 173 // `Path::parent` only fails for the root directory,
174 174 // which `src` can’t be since we’ve managed to open it as a
175 175 // file.
176 176 let dir = src
177 177 .parent()
178 178 .expect("Path::parent fail on a file we’ve read");
179 179 // `Path::join` with an absolute argument correctly ignores the
180 180 // base path
181 181 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
182 182 match std::fs::read(&filename) {
183 183 Ok(data) => {
184 184 layers.push(current_layer);
185 185 layers.extend(Self::parse(&filename, &data)?);
186 186 current_layer =
187 187 Self::new(ConfigOrigin::File(src.to_owned()));
188 188 }
189 189 Err(error) => {
190 190 if error.kind() != std::io::ErrorKind::NotFound {
191 191 return Err(ConfigParseError {
192 192 origin: ConfigOrigin::File(src.to_owned()),
193 193 line,
194 194 message: format_bytes!(
195 195 b"cannot include {} ({})",
196 196 filename_bytes,
197 197 format_bytes::Utf8(error)
198 198 ),
199 199 }
200 200 .into());
201 201 }
202 202 }
203 203 }
204 204 } else if EMPTY_RE.captures(bytes).is_some() {
205 205 } else if let Some(m) = SECTION_RE.captures(bytes) {
206 206 section = m[1].to_vec();
207 207 } else if let Some(m) = ITEM_RE.captures(bytes) {
208 208 let item = m[1].to_vec();
209 209 let mut value = m[2].to_vec();
210 210 loop {
211 211 match lines_iter.peek() {
212 212 None => break,
213 213 Some((_, v)) => {
214 214 if COMMENT_RE.captures(v).is_some() {
215 215 } else if CONT_RE.captures(v).is_some() {
216 216 value.extend(b"\n");
217 217 value.extend(&m[1]);
218 218 } else {
219 219 break;
220 220 }
221 221 }
222 222 };
223 223 lines_iter.next();
224 224 }
225 225 current_layer.add(section.clone(), item, value, line);
226 226 } else if let Some(m) = UNSET_RE.captures(bytes) {
227 227 if let Some(map) = current_layer.sections.get_mut(&section) {
228 228 map.remove(&m[1]);
229 229 }
230 230 } else {
231 231 let message = if bytes.starts_with(b" ") {
232 232 format_bytes!(b"unexpected leading whitespace: {}", bytes)
233 233 } else {
234 234 bytes.to_owned()
235 235 };
236 236 return Err(ConfigParseError {
237 237 origin: ConfigOrigin::File(src.to_owned()),
238 238 line,
239 239 message,
240 240 }
241 241 .into());
242 242 }
243 243 }
244 244 if !current_layer.is_empty() {
245 245 layers.push(current_layer);
246 246 }
247 247 Ok(layers)
248 248 }
249 249 }
250 250
251 251 impl DisplayBytes for ConfigLayer {
252 252 fn display_bytes(
253 253 &self,
254 254 out: &mut dyn std::io::Write,
255 255 ) -> std::io::Result<()> {
256 256 let mut sections: Vec<_> = self.sections.iter().collect();
257 257 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
258 258
259 259 for (section, items) in sections.into_iter() {
260 260 let mut items: Vec<_> = items.iter().collect();
261 261 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
262 262
263 263 for (item, config_entry) in items {
264 264 write_bytes!(
265 265 out,
266 266 b"{}.{}={} # {}\n",
267 267 section,
268 268 item,
269 269 &config_entry.bytes,
270 270 &self.origin,
271 271 )?
272 272 }
273 273 }
274 274 Ok(())
275 275 }
276 276 }
277 277
278 278 /// Mapping of section item to value.
279 279 /// In the following:
280 280 /// ```text
281 281 /// [ui]
282 282 /// paginate=no
283 283 /// ```
284 284 /// "paginate" is the section item and "no" the value.
285 285 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
286 286
287 287 #[derive(Clone, Debug, PartialEq)]
288 288 pub struct ConfigValue {
289 289 /// The raw bytes of the value (be it from the CLI, env or from a file)
290 290 pub bytes: Vec<u8>,
291 291 /// Only present if the value comes from a file, 1-indexed.
292 292 pub line: Option<usize>,
293 293 }
294 294
295 295 #[derive(Clone, Debug, PartialEq, Eq)]
296 296 pub enum ConfigOrigin {
297 297 /// From a configuration file
298 298 File(PathBuf),
299 299 /// From [ui.tweakdefaults]
300 300 Tweakdefaults,
301 301 /// From a `--config` CLI argument
302 302 CommandLine,
303 303 /// From a `--color` CLI argument
304 304 CommandLineColor,
305 305 /// From environment variables like `$PAGER` or `$EDITOR`
306 306 Environment(Vec<u8>),
307 /* TODO defaults (configitems.py)
308 * TODO extensions
307 /// From configitems.toml
308 Defaults,
309 /* TODO extensions
309 310 * TODO Python resources?
310 311 * Others? */
311 312 }
312 313
313 314 impl DisplayBytes for ConfigOrigin {
314 315 fn display_bytes(
315 316 &self,
316 317 out: &mut dyn std::io::Write,
317 318 ) -> std::io::Result<()> {
318 319 match self {
319 320 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
320 321 ConfigOrigin::CommandLine => out.write_all(b"--config"),
321 322 ConfigOrigin::CommandLineColor => out.write_all(b"--color"),
322 323 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
323 324 ConfigOrigin::Tweakdefaults => {
324 325 write_bytes!(out, b"ui.tweakdefaults")
325 326 }
327 ConfigOrigin::Defaults => {
328 write_bytes!(out, b"configitems.toml")
329 }
326 330 }
327 331 }
328 332 }
329 333
330 334 #[derive(Debug)]
331 335 pub struct ConfigParseError {
332 336 pub origin: ConfigOrigin,
333 337 pub line: Option<usize>,
334 338 pub message: Vec<u8>,
335 339 }
336 340
337 341 #[derive(Debug, derive_more::From)]
338 342 pub enum ConfigError {
339 343 Parse(ConfigParseError),
340 344 Other(HgError),
341 345 }
342 346
343 347 fn make_regex(pattern: &'static str) -> Regex {
344 348 Regex::new(pattern).expect("expected a valid regex")
345 349 }
@@ -1,636 +1,674
1 1 // config.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 //! Mercurial config parsing and interfaces.
11 11
12 pub mod config_items;
12 13 mod layer;
13 14 mod plain_info;
14 15 mod values;
15 16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
17 use lazy_static::lazy_static;
16 18 pub use plain_info::PlainInfo;
17 19
20 use self::config_items::DefaultConfig;
21 use self::config_items::DefaultConfigItem;
18 22 use self::layer::ConfigLayer;
19 23 use self::layer::ConfigValue;
24 use crate::errors::HgError;
20 25 use crate::errors::{HgResultExt, IoResultExt};
21 26 use crate::utils::files::get_bytes_from_os_str;
22 27 use format_bytes::{write_bytes, DisplayBytes};
23 28 use std::collections::HashSet;
24 29 use std::env;
25 30 use std::fmt;
26 31 use std::path::{Path, PathBuf};
27 32 use std::str;
28 33
34 lazy_static! {
35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
36 DefaultConfig::from_contents(include_str!(
37 "../../../../mercurial/configitems.toml"
38 ))
39 };
40 }
41
29 42 /// Holds the config values for the current repository
30 43 /// TODO update this docstring once we support more sources
31 44 #[derive(Clone)]
32 45 pub struct Config {
33 46 layers: Vec<layer::ConfigLayer>,
34 47 plain: PlainInfo,
35 48 }
36 49
37 50 impl DisplayBytes for Config {
38 51 fn display_bytes(
39 52 &self,
40 53 out: &mut dyn std::io::Write,
41 54 ) -> std::io::Result<()> {
42 55 for (index, layer) in self.layers.iter().rev().enumerate() {
43 56 write_bytes!(
44 57 out,
45 58 b"==== Layer {} (trusted: {}) ====\n{}",
46 59 index,
47 60 if layer.trusted {
48 61 &b"yes"[..]
49 62 } else {
50 63 &b"no"[..]
51 64 },
52 65 layer
53 66 )?;
54 67 }
55 68 Ok(())
56 69 }
57 70 }
58 71
59 72 pub enum ConfigSource {
60 73 /// Absolute path to a config file
61 74 AbsPath(PathBuf),
62 75 /// Already parsed (from the CLI, env, Python resources, etc.)
63 76 Parsed(layer::ConfigLayer),
64 77 }
65 78
66 79 #[derive(Debug)]
67 80 pub struct ConfigValueParseErrorDetails {
68 81 pub origin: ConfigOrigin,
69 82 pub line: Option<usize>,
70 83 pub section: Vec<u8>,
71 84 pub item: Vec<u8>,
72 85 pub value: Vec<u8>,
73 86 pub expected_type: &'static str,
74 87 }
75 88
76 89 // boxed to avoid very large Result types
77 90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
78 91
79 92 impl fmt::Display for ConfigValueParseError {
80 93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 94 // TODO: add origin and line number information, here and in
82 95 // corresponding python code
83 96 write!(
84 97 f,
85 98 "config error: {}.{} is not a {} ('{}')",
86 99 String::from_utf8_lossy(&self.section),
87 100 String::from_utf8_lossy(&self.item),
88 101 self.expected_type,
89 102 String::from_utf8_lossy(&self.value)
90 103 )
91 104 }
92 105 }
93 106
94 107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
95 108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
96 109 // duplication with [_applyconfig] in [ui.py],
97 110 if !plain.is_plain() {
98 111 return false;
99 112 }
100 113 if section == b"alias" {
101 114 return plain.plainalias();
102 115 }
103 116 if section == b"revsetalias" {
104 117 return plain.plainrevsetalias();
105 118 }
106 119 if section == b"templatealias" {
107 120 return plain.plaintemplatealias();
108 121 }
109 122 if section == b"ui" {
110 123 let to_delete: &[&[u8]] = &[
111 124 b"debug",
112 125 b"fallbackencoding",
113 126 b"quiet",
114 127 b"slash",
115 128 b"logtemplate",
116 129 b"message-output",
117 130 b"statuscopies",
118 131 b"style",
119 132 b"traceback",
120 133 b"verbose",
121 134 ];
122 135 return to_delete.contains(&item);
123 136 }
124 137 let sections_to_delete: &[&[u8]] =
125 138 &[b"defaults", b"commands", b"command-templates"];
126 139 sections_to_delete.contains(&section)
127 140 }
128 141
129 142 impl Config {
130 143 /// The configuration to use when printing configuration-loading errors
131 144 pub fn empty() -> Self {
132 145 Self {
133 146 layers: Vec::new(),
134 147 plain: PlainInfo::empty(),
135 148 }
136 149 }
137 150
138 151 /// Load system and user configuration from various files.
139 152 ///
140 153 /// This is also affected by some environment variables.
141 154 pub fn load_non_repo() -> Result<Self, ConfigError> {
142 155 let mut config = Self::empty();
143 156 let opt_rc_path = env::var_os("HGRCPATH");
144 157 // HGRCPATH replaces system config
145 158 if opt_rc_path.is_none() {
146 159 config.add_system_config()?
147 160 }
148 161
149 162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
150 163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
151 164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
152 165
153 166 // These are set by `run-tests.py --rhg` to enable fallback for the
154 167 // entire test suite. Alternatives would be setting configuration
155 168 // through `$HGRCPATH` but some tests override that, or changing the
156 169 // `hg` shell alias to include `--config` but that disrupts tests that
157 170 // print command lines and check expected output.
158 171 config.add_for_environment_variable(
159 172 "RHG_ON_UNSUPPORTED",
160 173 b"rhg",
161 174 b"on-unsupported",
162 175 );
163 176 config.add_for_environment_variable(
164 177 "RHG_FALLBACK_EXECUTABLE",
165 178 b"rhg",
166 179 b"fallback-executable",
167 180 );
168 181
169 182 // HGRCPATH replaces user config
170 183 if opt_rc_path.is_none() {
171 184 config.add_user_config()?
172 185 }
173 186 if let Some(rc_path) = &opt_rc_path {
174 187 for path in env::split_paths(rc_path) {
175 188 if !path.as_os_str().is_empty() {
176 189 if path.is_dir() {
177 190 config.add_trusted_dir(&path)?
178 191 } else {
179 192 config.add_trusted_file(&path)?
180 193 }
181 194 }
182 195 }
183 196 }
184 197 Ok(config)
185 198 }
186 199
187 200 pub fn load_cli_args(
188 201 &mut self,
189 202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
190 203 color_arg: Option<Vec<u8>>,
191 204 ) -> Result<(), ConfigError> {
192 205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
193 206 self.layers.push(layer)
194 207 }
195 208 if let Some(arg) = color_arg {
196 209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
197 210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
198 211 self.layers.push(layer)
199 212 }
200 213 Ok(())
201 214 }
202 215
203 216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
204 217 if let Some(entries) = std::fs::read_dir(path)
205 218 .when_reading_file(path)
206 219 .io_not_found_as_none()?
207 220 {
208 221 let mut file_paths = entries
209 222 .map(|result| {
210 223 result.when_reading_file(path).map(|entry| entry.path())
211 224 })
212 225 .collect::<Result<Vec<_>, _>>()?;
213 226 file_paths.sort();
214 227 for file_path in &file_paths {
215 228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
216 229 self.add_trusted_file(file_path)?
217 230 }
218 231 }
219 232 }
220 233 Ok(())
221 234 }
222 235
223 236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
224 237 if let Some(data) = std::fs::read(path)
225 238 .when_reading_file(path)
226 239 .io_not_found_as_none()?
227 240 {
228 241 self.layers.extend(ConfigLayer::parse(path, &data)?)
229 242 }
230 243 Ok(())
231 244 }
232 245
233 246 fn add_for_environment_variable(
234 247 &mut self,
235 248 var: &str,
236 249 section: &[u8],
237 250 key: &[u8],
238 251 ) {
239 252 if let Some(value) = env::var_os(var) {
240 253 let origin = layer::ConfigOrigin::Environment(var.into());
241 254 let mut layer = ConfigLayer::new(origin);
242 255 layer.add(
243 256 section.to_owned(),
244 257 key.to_owned(),
245 258 get_bytes_from_os_str(value),
246 259 None,
247 260 );
248 261 self.layers.push(layer)
249 262 }
250 263 }
251 264
252 265 #[cfg(unix)] // TODO: other platforms
253 266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
254 267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
255 268 let etc = prefix.join("etc").join("mercurial");
256 269 self.add_trusted_file(&etc.join("hgrc"))?;
257 270 self.add_trusted_dir(&etc.join("hgrc.d"))
258 271 };
259 272 let root = Path::new("/");
260 273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
261 274 // instead? TODO: can this be a relative path?
262 275 let hg = crate::utils::current_exe()?;
263 276 // TODO: this order (per-installation then per-system) matches
264 277 // `systemrcpath()` in `mercurial/scmposix.py`, but
265 278 // `mercurial/helptext/config.txt` suggests it should be reversed
266 279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
267 280 if installation_prefix != root {
268 281 add_for_prefix(installation_prefix)?
269 282 }
270 283 }
271 284 add_for_prefix(root)?;
272 285 Ok(())
273 286 }
274 287
275 288 #[cfg(unix)] // TODO: other plateforms
276 289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
277 290 let opt_home = home::home_dir();
278 291 if let Some(home) = &opt_home {
279 292 self.add_trusted_file(&home.join(".hgrc"))?
280 293 }
281 294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
282 295 if !darwin {
283 296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
284 297 .map(PathBuf::from)
285 298 .or_else(|| opt_home.map(|home| home.join(".config")))
286 299 {
287 300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
288 301 }
289 302 }
290 303 Ok(())
291 304 }
292 305
293 306 /// Loads in order, which means that the precedence is the same
294 307 /// as the order of `sources`.
295 308 pub fn load_from_explicit_sources(
296 309 sources: Vec<ConfigSource>,
297 310 ) -> Result<Self, ConfigError> {
298 311 let mut layers = vec![];
299 312
300 313 for source in sources.into_iter() {
301 314 match source {
302 315 ConfigSource::Parsed(c) => layers.push(c),
303 316 ConfigSource::AbsPath(c) => {
304 317 // TODO check if it should be trusted
305 318 // mercurial/ui.py:427
306 319 let data = match std::fs::read(&c) {
307 320 Err(_) => continue, // same as the python code
308 321 Ok(data) => data,
309 322 };
310 323 layers.extend(ConfigLayer::parse(&c, &data)?)
311 324 }
312 325 }
313 326 }
314 327
315 328 Ok(Config {
316 329 layers,
317 330 plain: PlainInfo::empty(),
318 331 })
319 332 }
320 333
321 334 /// Loads the per-repository config into a new `Config` which is combined
322 335 /// with `self`.
323 336 pub(crate) fn combine_with_repo(
324 337 &self,
325 338 repo_config_files: &[PathBuf],
326 339 ) -> Result<Self, ConfigError> {
327 340 let (cli_layers, other_layers) = self
328 341 .layers
329 342 .iter()
330 343 .cloned()
331 344 .partition(ConfigLayer::is_from_command_line);
332 345
333 346 let mut repo_config = Self {
334 347 layers: other_layers,
335 348 plain: PlainInfo::empty(),
336 349 };
337 350 for path in repo_config_files {
338 351 // TODO: check if this file should be trusted:
339 352 // `mercurial/ui.py:427`
340 353 repo_config.add_trusted_file(path)?;
341 354 }
342 355 repo_config.layers.extend(cli_layers);
343 356 Ok(repo_config)
344 357 }
345 358
346 359 pub fn apply_plain(&mut self, plain: PlainInfo) {
347 360 self.plain = plain;
348 361 }
349 362
363 /// Returns the default value for the given config item, if any.
364 pub fn get_default(
365 &self,
366 section: &[u8],
367 item: &[u8],
368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
370 HgError::abort(
371 e.to_string(),
372 crate::exit_codes::ABORT,
373 Some("`mercurial/configitems.toml` is not valid".into()),
374 )
375 })?;
376 Ok(default_config.get(section, item))
377 }
378
350 379 fn get_parse<'config, T: 'config>(
351 380 &'config self,
352 381 section: &[u8],
353 382 item: &[u8],
354 383 expected_type: &'static str,
355 384 parse: impl Fn(&'config [u8]) -> Option<T>,
356 ) -> Result<Option<T>, ConfigValueParseError> {
385 ) -> Result<Option<T>, HgError>
386 where
387 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
388 {
357 389 match self.get_inner(section, item) {
358 390 Some((layer, v)) => match parse(&v.bytes) {
359 391 Some(b) => Ok(Some(b)),
360 392 None => Err(Box::new(ConfigValueParseErrorDetails {
361 393 origin: layer.origin.to_owned(),
362 394 line: v.line,
363 395 value: v.bytes.to_owned(),
364 396 section: section.to_owned(),
365 397 item: item.to_owned(),
366 398 expected_type,
367 })),
399 })
400 .into()),
368 401 },
369 None => Ok(None),
402 None => match self.get_default(section, item)? {
403 Some(default) => Ok(default.try_into()?),
404 None => {
405 Ok(None)
406 }
407 },
370 408 }
371 409 }
372 410
373 411 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
374 412 /// Otherwise, returns an `Ok(value)` if found, or `None`.
375 413 pub fn get_str(
376 414 &self,
377 415 section: &[u8],
378 416 item: &[u8],
379 ) -> Result<Option<&str>, ConfigValueParseError> {
417 ) -> Result<Option<&str>, HgError> {
380 418 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
381 419 str::from_utf8(value).ok()
382 420 })
383 421 }
384 422
385 423 /// Returns an `Err` if the first value found is not a valid unsigned
386 424 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
387 425 pub fn get_u32(
388 426 &self,
389 427 section: &[u8],
390 428 item: &[u8],
391 ) -> Result<Option<u32>, ConfigValueParseError> {
429 ) -> Result<Option<u32>, HgError> {
392 430 self.get_parse(section, item, "valid integer", |value| {
393 431 str::from_utf8(value).ok()?.parse().ok()
394 432 })
395 433 }
396 434
397 435 /// Returns an `Err` if the first value found is not a valid file size
398 436 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
399 437 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
400 438 pub fn get_byte_size(
401 439 &self,
402 440 section: &[u8],
403 441 item: &[u8],
404 ) -> Result<Option<u64>, ConfigValueParseError> {
442 ) -> Result<Option<u64>, HgError> {
405 443 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
406 444 }
407 445
408 446 /// Returns an `Err` if the first value found is not a valid boolean.
409 447 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
410 448 /// found, or `None`.
411 449 pub fn get_option(
412 450 &self,
413 451 section: &[u8],
414 452 item: &[u8],
415 ) -> Result<Option<bool>, ConfigValueParseError> {
453 ) -> Result<Option<bool>, HgError> {
416 454 self.get_parse(section, item, "boolean", values::parse_bool)
417 455 }
418 456
419 457 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
420 458 /// if the value is not found, an `Err` if it's not a valid boolean.
421 459 pub fn get_bool(
422 460 &self,
423 461 section: &[u8],
424 462 item: &[u8],
425 ) -> Result<bool, ConfigValueParseError> {
463 ) -> Result<bool, HgError> {
426 464 Ok(self.get_option(section, item)?.unwrap_or(false))
427 465 }
428 466
429 467 /// Returns `true` if the extension is enabled, `false` otherwise
430 468 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
431 469 let value = self.get(b"extensions", extension);
432 470 match value {
433 471 Some(c) => !c.starts_with(b"!"),
434 472 None => false,
435 473 }
436 474 }
437 475
438 476 /// If there is an `item` value in `section`, parse and return a list of
439 477 /// byte strings.
440 478 pub fn get_list(
441 479 &self,
442 480 section: &[u8],
443 481 item: &[u8],
444 482 ) -> Option<Vec<Vec<u8>>> {
445 483 self.get(section, item).map(values::parse_list)
446 484 }
447 485
448 486 /// Returns the raw value bytes of the first one found, or `None`.
449 487 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
450 488 self.get_inner(section, item)
451 489 .map(|(_, value)| value.bytes.as_ref())
452 490 }
453 491
454 492 /// Returns the raw value bytes of the first one found, or `None`.
455 493 pub fn get_with_origin(
456 494 &self,
457 495 section: &[u8],
458 496 item: &[u8],
459 497 ) -> Option<(&[u8], &ConfigOrigin)> {
460 498 self.get_inner(section, item)
461 499 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
462 500 }
463 501
464 502 /// Returns the layer and the value of the first one found, or `None`.
465 503 fn get_inner(
466 504 &self,
467 505 section: &[u8],
468 506 item: &[u8],
469 507 ) -> Option<(&ConfigLayer, &ConfigValue)> {
470 508 // Filter out the config items that are hidden by [PLAIN].
471 509 // This differs from python hg where we delete them from the config.
472 510 let should_ignore = should_ignore(&self.plain, section, item);
473 511 for layer in self.layers.iter().rev() {
474 512 if !layer.trusted {
475 513 continue;
476 514 }
477 515 //The [PLAIN] config should not affect the defaults.
478 516 //
479 517 // However, PLAIN should also affect the "tweaked" defaults (unless
480 518 // "tweakdefault" is part of "HGPLAINEXCEPT").
481 519 //
482 520 // In practice the tweak-default layer is only added when it is
483 521 // relevant, so we can safely always take it into
484 522 // account here.
485 523 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
486 524 {
487 525 continue;
488 526 }
489 527 if let Some(v) = layer.get(section, item) {
490 528 return Some((layer, v));
491 529 }
492 530 }
493 531 None
494 532 }
495 533
496 534 /// Return all keys defined for the given section
497 535 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
498 536 self.layers
499 537 .iter()
500 538 .flat_map(|layer| layer.iter_keys(section))
501 539 .collect()
502 540 }
503 541
504 542 /// Returns whether any key is defined in the given section
505 543 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
506 544 self.layers
507 545 .iter()
508 546 .any(|layer| layer.has_non_empty_section(section))
509 547 }
510 548
511 549 /// Yields (key, value) pairs for everything in the given section
512 550 pub fn iter_section<'a>(
513 551 &'a self,
514 552 section: &'a [u8],
515 553 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
516 554 // Deduplicate keys redefined in multiple layers
517 555 let mut keys_already_seen = HashSet::new();
518 556 let mut key_is_new =
519 557 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
520 558 keys_already_seen.insert(key)
521 559 };
522 560 // This is similar to `flat_map` + `filter_map`, except with a single
523 561 // closure that owns `key_is_new` (and therefore the
524 562 // `keys_already_seen` set):
525 563 let mut layer_iters = self
526 564 .layers
527 565 .iter()
528 566 .rev()
529 567 .map(move |layer| layer.iter_section(section))
530 568 .peekable();
531 569 std::iter::from_fn(move || loop {
532 570 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
533 571 return Some(pair);
534 572 } else {
535 573 layer_iters.next();
536 574 }
537 575 })
538 576 }
539 577
540 578 /// Get raw values bytes from all layers (even untrusted ones) in order
541 579 /// of precedence.
542 580 #[cfg(test)]
543 581 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
544 582 let mut res = vec![];
545 583 for layer in self.layers.iter().rev() {
546 584 if let Some(v) = layer.get(section, item) {
547 585 res.push(v.bytes.as_ref());
548 586 }
549 587 }
550 588 res
551 589 }
552 590
553 591 // a config layer that's introduced by ui.tweakdefaults
554 592 fn tweakdefaults_layer() -> ConfigLayer {
555 593 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
556 594
557 595 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
558 596 layer.add(
559 597 section[..].into(),
560 598 item[..].into(),
561 599 value[..].into(),
562 600 None,
563 601 );
564 602 };
565 603 // duplication of [tweakrc] from [ui.py]
566 604 add(b"ui", b"rollback", b"False");
567 605 add(b"ui", b"statuscopies", b"yes");
568 606 add(b"ui", b"interface", b"curses");
569 607 add(b"ui", b"relative-paths", b"yes");
570 608 add(b"commands", b"grep.all-files", b"True");
571 609 add(b"commands", b"update.check", b"noconflict");
572 610 add(b"commands", b"status.verbose", b"True");
573 611 add(b"commands", b"resolve.explicit-re-merge", b"True");
574 612 add(b"git", b"git", b"1");
575 613 add(b"git", b"showfunc", b"1");
576 614 add(b"git", b"word-diff", b"1");
577 615 layer
578 616 }
579 617
580 618 // introduce the tweaked defaults as implied by ui.tweakdefaults
581 619 pub fn tweakdefaults(&mut self) {
582 620 self.layers.insert(0, Config::tweakdefaults_layer());
583 621 }
584 622 }
585 623
586 624 #[cfg(test)]
587 625 mod tests {
588 626 use super::*;
589 627 use pretty_assertions::assert_eq;
590 628 use std::fs::File;
591 629 use std::io::Write;
592 630
593 631 #[test]
594 632 fn test_include_layer_ordering() {
595 633 let tmpdir = tempfile::tempdir().unwrap();
596 634 let tmpdir_path = tmpdir.path();
597 635 let mut included_file =
598 636 File::create(&tmpdir_path.join("included.rc")).unwrap();
599 637
600 638 included_file.write_all(b"[section]\nitem=value1").unwrap();
601 639 let base_config_path = tmpdir_path.join("base.rc");
602 640 let mut config_file = File::create(&base_config_path).unwrap();
603 641 let data =
604 642 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
605 643 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
606 644 config_file.write_all(data).unwrap();
607 645
608 646 let sources = vec![ConfigSource::AbsPath(base_config_path)];
609 647 let config = Config::load_from_explicit_sources(sources)
610 648 .expect("expected valid config");
611 649
612 650 let (_, value) = config.get_inner(b"section", b"item").unwrap();
613 651 assert_eq!(
614 652 value,
615 653 &ConfigValue {
616 654 bytes: b"value2".to_vec(),
617 655 line: Some(4)
618 656 }
619 657 );
620 658
621 659 let value = config.get(b"section", b"item").unwrap();
622 660 assert_eq!(value, b"value2",);
623 661 assert_eq!(
624 662 config.get_all(b"section", b"item"),
625 663 [b"value2", b"value1", b"value0"]
626 664 );
627 665
628 666 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
629 667 assert_eq!(
630 668 config.get_byte_size(b"section2", b"size").unwrap(),
631 669 Some(1024 + 512)
632 670 );
633 671 assert!(config.get_u32(b"section2", b"not-count").is_err());
634 672 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
635 673 }
636 674 }
@@ -1,675 +1,689
1 1 // status.rs
2 2 //
3 3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::error::CommandError;
9 9 use crate::ui::{
10 format_pattern_file_warning, print_narrow_sparse_warnings, Ui,
10 format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths,
11 RelativePaths, Ui,
11 12 };
12 13 use crate::utils::path_utils::RelativizePaths;
13 14 use clap::Arg;
14 15 use format_bytes::format_bytes;
15 16 use hg::config::Config;
16 17 use hg::dirstate::has_exec_bit;
17 18 use hg::dirstate::status::StatusPath;
18 19 use hg::dirstate::TruncatedTimestamp;
19 20 use hg::errors::{HgError, IoResultExt};
20 21 use hg::lock::LockError;
21 22 use hg::manifest::Manifest;
22 23 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
23 24 use hg::repo::Repo;
24 25 use hg::utils::debug::debug_wait_for_file;
25 26 use hg::utils::files::get_bytes_from_os_string;
26 27 use hg::utils::files::get_path_from_bytes;
27 28 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 29 use hg::DirstateStatus;
29 30 use hg::PatternFileWarning;
30 31 use hg::StatusError;
31 32 use hg::StatusOptions;
32 33 use hg::{self, narrow, sparse};
33 34 use log::info;
34 35 use rayon::prelude::*;
35 36 use std::io;
36 37 use std::path::PathBuf;
37 38
38 39 pub const HELP_TEXT: &str = "
39 40 Show changed files in the working directory
40 41
41 42 This is a pure Rust version of `hg status`.
42 43
43 44 Some options might be missing, check the list below.
44 45 ";
45 46
46 47 pub fn args() -> clap::Command {
47 48 clap::command!("status")
48 49 .alias("st")
49 50 .about(HELP_TEXT)
50 51 .arg(
51 52 Arg::new("all")
52 53 .help("show status of all files")
53 54 .short('A')
54 55 .action(clap::ArgAction::SetTrue)
55 56 .long("all"),
56 57 )
57 58 .arg(
58 59 Arg::new("modified")
59 60 .help("show only modified files")
60 61 .short('m')
61 62 .action(clap::ArgAction::SetTrue)
62 63 .long("modified"),
63 64 )
64 65 .arg(
65 66 Arg::new("added")
66 67 .help("show only added files")
67 68 .short('a')
68 69 .action(clap::ArgAction::SetTrue)
69 70 .long("added"),
70 71 )
71 72 .arg(
72 73 Arg::new("removed")
73 74 .help("show only removed files")
74 75 .short('r')
75 76 .action(clap::ArgAction::SetTrue)
76 77 .long("removed"),
77 78 )
78 79 .arg(
79 80 Arg::new("clean")
80 81 .help("show only clean files")
81 82 .short('c')
82 83 .action(clap::ArgAction::SetTrue)
83 84 .long("clean"),
84 85 )
85 86 .arg(
86 87 Arg::new("deleted")
87 88 .help("show only deleted files")
88 89 .short('d')
89 90 .action(clap::ArgAction::SetTrue)
90 91 .long("deleted"),
91 92 )
92 93 .arg(
93 94 Arg::new("unknown")
94 95 .help("show only unknown (not tracked) files")
95 96 .short('u')
96 97 .action(clap::ArgAction::SetTrue)
97 98 .long("unknown"),
98 99 )
99 100 .arg(
100 101 Arg::new("ignored")
101 102 .help("show only ignored files")
102 103 .short('i')
103 104 .action(clap::ArgAction::SetTrue)
104 105 .long("ignored"),
105 106 )
106 107 .arg(
107 108 Arg::new("copies")
108 109 .help("show source of copied files (DEFAULT: ui.statuscopies)")
109 110 .short('C')
110 111 .action(clap::ArgAction::SetTrue)
111 112 .long("copies"),
112 113 )
113 114 .arg(
114 115 Arg::new("print0")
115 116 .help("end filenames with NUL, for use with xargs")
116 117 .short('0')
117 118 .action(clap::ArgAction::SetTrue)
118 119 .long("print0"),
119 120 )
120 121 .arg(
121 122 Arg::new("no-status")
122 123 .help("hide status prefix")
123 124 .short('n')
124 125 .action(clap::ArgAction::SetTrue)
125 126 .long("no-status"),
126 127 )
127 128 .arg(
128 129 Arg::new("verbose")
129 130 .help("enable additional output")
130 131 .short('v')
131 132 .action(clap::ArgAction::SetTrue)
132 133 .long("verbose"),
133 134 )
134 135 }
135 136
136 137 /// Pure data type allowing the caller to specify file states to display
137 138 #[derive(Copy, Clone, Debug)]
138 139 pub struct DisplayStates {
139 140 pub modified: bool,
140 141 pub added: bool,
141 142 pub removed: bool,
142 143 pub clean: bool,
143 144 pub deleted: bool,
144 145 pub unknown: bool,
145 146 pub ignored: bool,
146 147 }
147 148
148 149 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
149 150 modified: true,
150 151 added: true,
151 152 removed: true,
152 153 clean: false,
153 154 deleted: true,
154 155 unknown: true,
155 156 ignored: false,
156 157 };
157 158
158 159 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
159 160 modified: true,
160 161 added: true,
161 162 removed: true,
162 163 clean: true,
163 164 deleted: true,
164 165 unknown: true,
165 166 ignored: true,
166 167 };
167 168
168 169 impl DisplayStates {
169 170 pub fn is_empty(&self) -> bool {
170 171 !(self.modified
171 172 || self.added
172 173 || self.removed
173 174 || self.clean
174 175 || self.deleted
175 176 || self.unknown
176 177 || self.ignored)
177 178 }
178 179 }
179 180
180 181 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
181 182 Ok(repo.dirstate_parents()?.is_merge())
182 183 }
183 184
184 185 fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
185 186 // These are all the known values for the [fname] argument of
186 187 // [addunfinished] function in [state.py]
187 188 let known_state_files: &[&str] = &[
188 189 "bisect.state",
189 190 "graftstate",
190 191 "histedit-state",
191 192 "rebasestate",
192 193 "shelvedstate",
193 194 "transplant/journal",
194 195 "updatestate",
195 196 ];
196 197 if has_unfinished_merge(repo)? {
197 198 return Ok(true);
198 199 };
199 200 for f in known_state_files {
200 201 if repo.hg_vfs().join(f).exists() {
201 202 return Ok(true);
202 203 }
203 204 }
204 205 Ok(false)
205 206 }
206 207
207 208 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
208 209 // TODO: lift these limitations
209 210 if invocation
210 211 .config
211 212 .get(b"commands", b"status.terse")
212 213 .is_some()
213 214 {
214 215 return Err(CommandError::unsupported(
215 216 "status.terse is not yet supported with rhg status",
216 217 ));
217 218 }
218 219
219 220 let ui = invocation.ui;
220 221 let config = invocation.config;
221 222 let args = invocation.subcommand_args;
222 223
223 224 let print0 = args.get_flag("print0");
224 225 let verbose = args.get_flag("verbose")
225 226 || config.get_bool(b"ui", b"verbose")?
226 227 || config.get_bool(b"commands", b"status.verbose")?;
227 228 let verbose = verbose && !print0;
228 229
229 230 let all = args.get_flag("all");
230 231 let display_states = if all {
231 232 // TODO when implementing `--quiet`: it excludes clean files
232 233 // from `--all`
233 234 ALL_DISPLAY_STATES
234 235 } else {
235 236 let requested = DisplayStates {
236 237 modified: args.get_flag("modified"),
237 238 added: args.get_flag("added"),
238 239 removed: args.get_flag("removed"),
239 240 clean: args.get_flag("clean"),
240 241 deleted: args.get_flag("deleted"),
241 242 unknown: args.get_flag("unknown"),
242 243 ignored: args.get_flag("ignored"),
243 244 };
244 245 if requested.is_empty() {
245 246 DEFAULT_DISPLAY_STATES
246 247 } else {
247 248 requested
248 249 }
249 250 };
250 251 let no_status = args.get_flag("no-status");
251 252 let list_copies = all
252 253 || args.get_flag("copies")
253 254 || config.get_bool(b"ui", b"statuscopies")?;
254 255
255 256 let repo = invocation.repo?;
256 257
257 258 if verbose && has_unfinished_state(repo)? {
258 259 return Err(CommandError::unsupported(
259 260 "verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
260 261 ));
261 262 }
262 263
263 264 let mut dmap = repo.dirstate_map_mut()?;
264 265
265 266 let check_exec = hg::checkexec::check_exec(repo.working_directory_path());
266 267
267 268 let options = StatusOptions {
268 269 check_exec,
269 270 list_clean: display_states.clean,
270 271 list_unknown: display_states.unknown,
271 272 list_ignored: display_states.ignored,
272 273 list_copies,
273 274 collect_traversed_dirs: false,
274 275 };
275 276
276 277 type StatusResult<'a> =
277 278 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
278 279
279 280 let after_status = |res: StatusResult| -> Result<_, CommandError> {
280 281 let (mut ds_status, pattern_warnings) = res?;
281 282 for warning in pattern_warnings {
282 283 ui.write_stderr(&format_pattern_file_warning(&warning, repo))?;
283 284 }
284 285
285 286 for (path, error) in ds_status.bad {
286 287 let error = match error {
287 288 hg::BadMatch::OsError(code) => {
288 289 std::io::Error::from_raw_os_error(code).to_string()
289 290 }
290 291 hg::BadMatch::BadType(ty) => {
291 292 format!("unsupported file type (type is {})", ty)
292 293 }
293 294 };
294 295 ui.write_stderr(&format_bytes!(
295 296 b"{}: {}\n",
296 297 path.as_bytes(),
297 298 error.as_bytes()
298 299 ))?
299 300 }
300 301 if !ds_status.unsure.is_empty() {
301 302 info!(
302 303 "Files to be rechecked by retrieval from filelog: {:?}",
303 304 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
304 305 );
305 306 }
306 307 let mut fixup = Vec::new();
307 308 if !ds_status.unsure.is_empty()
308 309 && (display_states.modified || display_states.clean)
309 310 {
310 311 let p1 = repo.dirstate_parents()?.p1;
311 312 let manifest = repo.manifest_for_node(p1).map_err(|e| {
312 313 CommandError::from((e, &*format!("{:x}", p1.short())))
313 314 })?;
314 315 let working_directory_vfs = repo.working_directory_vfs();
315 316 let store_vfs = repo.store_vfs();
316 317 let res: Vec<_> = ds_status
317 318 .unsure
318 319 .into_par_iter()
319 320 .map(|to_check| {
320 321 // The compiler seems to get a bit confused with complex
321 322 // inference when using a parallel iterator + map
322 323 // + map_err + collect, so let's just inline some of the
323 324 // logic.
324 325 match unsure_is_modified(
325 326 working_directory_vfs,
326 327 store_vfs,
327 328 check_exec,
328 329 &manifest,
329 330 &to_check.path,
330 331 ) {
331 332 Err(HgError::IoError { .. }) => {
332 333 // IO errors most likely stem from the file being
333 334 // deleted even though we know it's in the
334 335 // dirstate.
335 336 Ok((to_check, UnsureOutcome::Deleted))
336 337 }
337 338 Ok(outcome) => Ok((to_check, outcome)),
338 339 Err(e) => Err(e),
339 340 }
340 341 })
341 342 .collect::<Result<_, _>>()?;
342 343 for (status_path, outcome) in res.into_iter() {
343 344 match outcome {
344 345 UnsureOutcome::Clean => {
345 346 if display_states.clean {
346 347 ds_status.clean.push(status_path.clone());
347 348 }
348 349 fixup.push(status_path.path.into_owned())
349 350 }
350 351 UnsureOutcome::Modified => {
351 352 if display_states.modified {
352 353 ds_status.modified.push(status_path);
353 354 }
354 355 }
355 356 UnsureOutcome::Deleted => {
356 357 if display_states.deleted {
357 358 ds_status.deleted.push(status_path);
358 359 }
359 360 }
360 361 }
361 362 }
362 363 }
363 let relative_paths = config
364
365 let relative_status = config
364 366 .get_option(b"commands", b"status.relative")?
365 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
367 .expect("commands.status.relative should have a default value");
368
369 let relativize_paths = relative_status || {
370 // TODO should be dependent on whether patterns are passed once
371 // we support those.
372 // See in Python code with `getuipathfn` usage in `commands.py`.
373 let legacy_relative_behavior = false;
374 match relative_paths(invocation.config)? {
375 RelativePaths::Legacy => legacy_relative_behavior,
376 RelativePaths::Bool(v) => v,
377 }
378 };
379
366 380 let output = DisplayStatusPaths {
367 381 ui,
368 382 no_status,
369 relativize: if relative_paths {
383 relativize: if relativize_paths {
370 384 Some(RelativizePaths::new(repo)?)
371 385 } else {
372 386 None
373 387 },
374 388 print0,
375 389 };
376 390 if display_states.modified {
377 391 output.display(b"M ", "status.modified", ds_status.modified)?;
378 392 }
379 393 if display_states.added {
380 394 output.display(b"A ", "status.added", ds_status.added)?;
381 395 }
382 396 if display_states.removed {
383 397 output.display(b"R ", "status.removed", ds_status.removed)?;
384 398 }
385 399 if display_states.deleted {
386 400 output.display(b"! ", "status.deleted", ds_status.deleted)?;
387 401 }
388 402 if display_states.unknown {
389 403 output.display(b"? ", "status.unknown", ds_status.unknown)?;
390 404 }
391 405 if display_states.ignored {
392 406 output.display(b"I ", "status.ignored", ds_status.ignored)?;
393 407 }
394 408 if display_states.clean {
395 409 output.display(b"C ", "status.clean", ds_status.clean)?;
396 410 }
397 411
398 412 let dirstate_write_needed = ds_status.dirty;
399 413 let filesystem_time_at_status_start =
400 414 ds_status.filesystem_time_at_status_start;
401 415
402 416 Ok((
403 417 fixup,
404 418 dirstate_write_needed,
405 419 filesystem_time_at_status_start,
406 420 ))
407 421 };
408 422 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
409 423 let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
410 424 let matcher = match (repo.has_narrow(), repo.has_sparse()) {
411 425 (true, true) => {
412 426 Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
413 427 }
414 428 (true, false) => narrow_matcher,
415 429 (false, true) => sparse_matcher,
416 430 (false, false) => Box::new(AlwaysMatcher),
417 431 };
418 432
419 433 print_narrow_sparse_warnings(
420 434 &narrow_warnings,
421 435 &sparse_warnings,
422 436 ui,
423 437 repo,
424 438 )?;
425 439 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
426 440 dmap.with_status(
427 441 matcher.as_ref(),
428 442 repo.working_directory_path().to_owned(),
429 443 ignore_files(repo, config),
430 444 options,
431 445 after_status,
432 446 )?;
433 447
434 448 // Development config option to test write races
435 449 if let Err(e) =
436 450 debug_wait_for_file(config, "status.pre-dirstate-write-file")
437 451 {
438 452 ui.write_stderr(e.as_bytes()).ok();
439 453 }
440 454
441 455 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
442 456 && !dirstate_write_needed
443 457 {
444 458 // Nothing to update
445 459 return Ok(());
446 460 }
447 461
448 462 // Update the dirstate on disk if we can
449 463 let with_lock_result =
450 464 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
451 465 if let Some(mtime_boundary) = filesystem_time_at_status_start {
452 466 for hg_path in fixup {
453 467 use std::os::unix::fs::MetadataExt;
454 468 let fs_path = hg_path_to_path_buf(&hg_path)
455 469 .expect("HgPath conversion");
456 470 // Specifically do not reuse `fs_metadata` from
457 471 // `unsure_is_clean` which was needed before reading
458 472 // contents. Here we access metadata again after reading
459 473 // content, in case it changed in the meantime.
460 474 let metadata_res = repo
461 475 .working_directory_vfs()
462 476 .symlink_metadata(&fs_path);
463 477 let fs_metadata = match metadata_res {
464 478 Ok(meta) => meta,
465 479 Err(err) => match err {
466 480 HgError::IoError { .. } => {
467 481 // The file has probably been deleted. In any
468 482 // case, it was in the dirstate before, so
469 483 // let's ignore the error.
470 484 continue;
471 485 }
472 486 _ => return Err(err.into()),
473 487 },
474 488 };
475 489 if let Some(mtime) =
476 490 TruncatedTimestamp::for_reliable_mtime_of(
477 491 &fs_metadata,
478 492 &mtime_boundary,
479 493 )
480 494 .when_reading_file(&fs_path)?
481 495 {
482 496 let mode = fs_metadata.mode();
483 497 let size = fs_metadata.len();
484 498 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
485 499 dirstate_write_needed = true
486 500 }
487 501 }
488 502 }
489 503 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
490 504 if dirstate_write_needed {
491 505 repo.write_dirstate()?
492 506 }
493 507 Ok(())
494 508 });
495 509 match with_lock_result {
496 510 Ok(closure_result) => closure_result?,
497 511 Err(LockError::AlreadyHeld) => {
498 512 // Not updating the dirstate is not ideal but not critical:
499 513 // don’t keep our caller waiting until some other Mercurial
500 514 // process releases the lock.
501 515 log::info!("not writing dirstate from `status`: lock is held")
502 516 }
503 517 Err(LockError::Other(HgError::IoError { error, .. }))
504 518 if error.kind() == io::ErrorKind::PermissionDenied =>
505 519 {
506 520 // `hg status` on a read-only repository is fine
507 521 }
508 522 Err(LockError::Other(error)) => {
509 523 // Report other I/O errors
510 524 Err(error)?
511 525 }
512 526 }
513 527 Ok(())
514 528 }
515 529
516 530 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
517 531 let mut ignore_files = Vec::new();
518 532 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
519 533 if repo_ignore.exists() {
520 534 ignore_files.push(repo_ignore)
521 535 }
522 536 for (key, value) in config.iter_section(b"ui") {
523 537 if key == b"ignore" || key.starts_with(b"ignore.") {
524 538 let path = get_path_from_bytes(value);
525 539 // TODO: expand "~/" and environment variable here, like Python
526 540 // does with `os.path.expanduser` and `os.path.expandvars`
527 541
528 542 let joined = repo.working_directory_path().join(path);
529 543 ignore_files.push(joined);
530 544 }
531 545 }
532 546 ignore_files
533 547 }
534 548
535 549 struct DisplayStatusPaths<'a> {
536 550 ui: &'a Ui,
537 551 no_status: bool,
538 552 relativize: Option<RelativizePaths>,
539 553 print0: bool,
540 554 }
541 555
542 556 impl DisplayStatusPaths<'_> {
543 557 // Probably more elegant to use a Deref or Borrow trait rather than
544 558 // harcode HgPathBuf, but probably not really useful at this point
545 559 fn display(
546 560 &self,
547 561 status_prefix: &[u8],
548 562 label: &'static str,
549 563 mut paths: Vec<StatusPath<'_>>,
550 564 ) -> Result<(), CommandError> {
551 565 paths.sort_unstable();
552 566 // TODO: get the stdout lock once for the whole loop
553 567 // instead of in each write
554 568 for StatusPath { path, copy_source } in paths {
555 569 let relative_path;
556 570 let relative_source;
557 571 let (path, copy_source) = if let Some(relativize) =
558 572 &self.relativize
559 573 {
560 574 relative_path = relativize.relativize(&path);
561 575 relative_source =
562 576 copy_source.as_ref().map(|s| relativize.relativize(s));
563 577 (&*relative_path, relative_source.as_deref())
564 578 } else {
565 579 (path.as_bytes(), copy_source.as_ref().map(|s| s.as_bytes()))
566 580 };
567 581 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
568 582 // in order to stream to stdout instead of allocating an
569 583 // itermediate `Vec<u8>`.
570 584 if !self.no_status {
571 585 self.ui.write_stdout_labelled(status_prefix, label)?
572 586 }
573 587 let linebreak = if self.print0 { b"\x00" } else { b"\n" };
574 588 self.ui.write_stdout_labelled(
575 589 &format_bytes!(b"{}{}", path, linebreak),
576 590 label,
577 591 )?;
578 592 if let Some(source) = copy_source.filter(|_| !self.no_status) {
579 593 let label = "status.copied";
580 594 self.ui.write_stdout_labelled(
581 595 &format_bytes!(b" {}{}", source, linebreak),
582 596 label,
583 597 )?
584 598 }
585 599 }
586 600 Ok(())
587 601 }
588 602 }
589 603
590 604 /// Outcome of the additional check for an ambiguous tracked file
591 605 enum UnsureOutcome {
592 606 /// The file is actually clean
593 607 Clean,
594 608 /// The file has been modified
595 609 Modified,
596 610 /// The file was deleted on disk (or became another type of fs entry)
597 611 Deleted,
598 612 }
599 613
600 614 /// Check if a file is modified by comparing actual repo store and file system.
601 615 ///
602 616 /// This meant to be used for those that the dirstate cannot resolve, due
603 617 /// to time resolution limits.
604 618 fn unsure_is_modified(
605 619 working_directory_vfs: hg::vfs::Vfs,
606 620 store_vfs: hg::vfs::Vfs,
607 621 check_exec: bool,
608 622 manifest: &Manifest,
609 623 hg_path: &HgPath,
610 624 ) -> Result<UnsureOutcome, HgError> {
611 625 let vfs = working_directory_vfs;
612 626 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
613 627 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
614 628 let is_symlink = fs_metadata.file_type().is_symlink();
615 629
616 630 let entry = manifest
617 631 .find_by_path(hg_path)?
618 632 .expect("ambgious file not in p1");
619 633
620 634 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
621 635 // dirstate
622 636 let fs_flags = if is_symlink {
623 637 Some(b'l')
624 638 } else if check_exec && has_exec_bit(&fs_metadata) {
625 639 Some(b'x')
626 640 } else {
627 641 None
628 642 };
629 643
630 644 let entry_flags = if check_exec {
631 645 entry.flags
632 646 } else if entry.flags == Some(b'x') {
633 647 None
634 648 } else {
635 649 entry.flags
636 650 };
637 651
638 652 if entry_flags != fs_flags {
639 653 return Ok(UnsureOutcome::Modified);
640 654 }
641 655 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
642 656 let fs_len = fs_metadata.len();
643 657 let file_node = entry.node_id()?;
644 658 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
645 659 HgError::corrupted(format!(
646 660 "filelog {:?} missing node {:?} from manifest",
647 661 hg_path, file_node
648 662 ))
649 663 })?;
650 664 if filelog_entry.file_data_len_not_equal_to(fs_len) {
651 665 // No need to read file contents:
652 666 // it cannot be equal if it has a different length.
653 667 return Ok(UnsureOutcome::Modified);
654 668 }
655 669
656 670 let p1_filelog_data = filelog_entry.data()?;
657 671 let p1_contents = p1_filelog_data.file_data()?;
658 672 if p1_contents.len() as u64 != fs_len {
659 673 // No need to read file contents:
660 674 // it cannot be equal if it has a different length.
661 675 return Ok(UnsureOutcome::Modified);
662 676 }
663 677
664 678 let fs_contents = if is_symlink {
665 679 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
666 680 } else {
667 681 vfs.read(fs_path)?
668 682 };
669 683
670 684 Ok(if p1_contents != &*fs_contents {
671 685 UnsureOutcome::Modified
672 686 } else {
673 687 UnsureOutcome::Clean
674 688 })
675 689 }
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now