Show More
@@ -11,22 +11,30 b'' | |||||
11 |
|
11 | |||
12 | use crate::{ |
|
12 | use crate::{ | |
13 | dirstate::SIZE_FROM_OTHER_PARENT, |
|
13 | dirstate::SIZE_FROM_OTHER_PARENT, | |
14 | matchers::{Matcher, VisitChildrenSet}, |
|
14 | filepatterns::PatternFileWarning, | |
|
15 | matchers::{get_ignore_function, Matcher, VisitChildrenSet}, | |||
15 | utils::{ |
|
16 | utils::{ | |
16 | files::HgMetadata, |
|
17 | files::{find_dirs, HgMetadata}, | |
17 | hg_path::{ |
|
18 | hg_path::{ | |
18 | hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, |
|
19 | hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, | |
|
20 | HgPathError, | |||
19 | }, |
|
21 | }, | |
|
22 | path_auditor::PathAuditor, | |||
20 | }, |
|
23 | }, | |
21 | CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, |
|
24 | CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, | |
|
25 | PatternError, | |||
22 | }; |
|
26 | }; | |
|
27 | use lazy_static::lazy_static; | |||
23 | use rayon::prelude::*; |
|
28 | use rayon::prelude::*; | |
24 | use std::borrow::Cow; |
|
29 | use std::collections::VecDeque; | |
25 | use std::collections::{HashSet, VecDeque}; |
|
30 | use std::{ | |
26 | use std::fs::{read_dir, DirEntry}; |
|
31 | borrow::Cow, | |
27 | use std::io::ErrorKind; |
|
32 | collections::HashSet, | |
28 | use std::ops::Deref; |
|
33 | fs::{read_dir, DirEntry}, | |
29 | use std::path::Path; |
|
34 | io::ErrorKind, | |
|
35 | ops::Deref, | |||
|
36 | path::Path, | |||
|
37 | }; | |||
30 |
|
38 | |||
31 | /// Wrong type of file from a `BadMatch` |
|
39 | /// Wrong type of file from a `BadMatch` | |
32 | /// Note: a lot of those don't exist on all platforms. |
|
40 | /// Note: a lot of those don't exist on all platforms. | |
@@ -50,6 +58,7 b' pub enum BadMatch {' | |||||
50 | /// Marker enum used to dispatch new status entries into the right collections. |
|
58 | /// Marker enum used to dispatch new status entries into the right collections. | |
51 | /// Is similar to `crate::EntryState`, but represents the transient state of |
|
59 | /// Is similar to `crate::EntryState`, but represents the transient state of | |
52 | /// entries during the lifetime of a command. |
|
60 | /// entries during the lifetime of a command. | |
|
61 | #[derive(Debug)] | |||
53 | enum Dispatch { |
|
62 | enum Dispatch { | |
54 | Unsure, |
|
63 | Unsure, | |
55 | Modified, |
|
64 | Modified, | |
@@ -155,7 +164,7 b' fn dispatch_found(' | |||||
155 | } else if options.list_clean { |
|
164 | } else if options.list_clean { | |
156 | Dispatch::Clean |
|
165 | Dispatch::Clean | |
157 | } else { |
|
166 | } else { | |
158 |
Dispatch:: |
|
167 | Dispatch::None | |
159 | } |
|
168 | } | |
160 | } |
|
169 | } | |
161 | EntryState::Merged => Dispatch::Modified, |
|
170 | EntryState::Merged => Dispatch::Modified, | |
@@ -179,57 +188,89 b' fn dispatch_missing(state: EntryState) -' | |||||
179 | } |
|
188 | } | |
180 | } |
|
189 | } | |
181 |
|
190 | |||
|
191 | lazy_static! { | |||
|
192 | static ref DEFAULT_WORK: HashSet<&'static HgPath> = { | |||
|
193 | let mut h = HashSet::new(); | |||
|
194 | h.insert(HgPath::new(b"")); | |||
|
195 | h | |||
|
196 | }; | |||
|
197 | } | |||
|
198 | ||||
182 | /// Get stat data about the files explicitly specified by match. |
|
199 | /// Get stat data about the files explicitly specified by match. | |
183 | /// TODO subrepos |
|
200 | /// TODO subrepos | |
184 | fn walk_explicit<'a>( |
|
201 | fn walk_explicit<'a>( | |
185 | files: &'a HashSet<&HgPath>, |
|
202 | files: Option<&'a HashSet<&HgPath>>, | |
186 | dmap: &'a DirstateMap, |
|
203 | dmap: &'a DirstateMap, | |
187 | root_dir: impl AsRef<Path> + Sync + Send, |
|
204 | root_dir: impl AsRef<Path> + Sync + Send + 'a, | |
188 | options: StatusOptions, |
|
205 | options: StatusOptions, | |
189 | ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> { |
|
206 | ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> { | |
190 | files.par_iter().filter_map(move |filename| { |
|
207 | files | |
191 | // TODO normalization |
|
208 | .unwrap_or(&DEFAULT_WORK) | |
192 | let normalized = filename.as_ref(); |
|
209 | .par_iter() | |
|
210 | .map(move |filename| { | |||
|
211 | // TODO normalization | |||
|
212 | let normalized = filename.as_ref(); | |||
193 |
|
213 | |||
194 | let buf = match hg_path_to_path_buf(normalized) { |
|
214 | let buf = match hg_path_to_path_buf(normalized) { | |
195 | Ok(x) => x, |
|
215 | Ok(x) => x, | |
196 | Err(e) => return Some(Err(e.into())), |
|
216 | Err(e) => return Some(Err(e.into())), | |
197 | }; |
|
217 | }; | |
198 | let target = root_dir.as_ref().join(buf); |
|
218 | let target = root_dir.as_ref().join(buf); | |
199 | let st = target.symlink_metadata(); |
|
219 | let st = target.symlink_metadata(); | |
200 | match st { |
|
220 | let in_dmap = dmap.get(normalized); | |
201 |
|
|
221 | match st { | |
202 | let file_type = meta.file_type(); |
|
222 | Ok(meta) => { | |
203 |
|
|
223 | let file_type = meta.file_type(); | |
204 | if let Some(entry) = dmap.get(normalized) { |
|
224 | return if file_type.is_file() || file_type.is_symlink() { | |
|
225 | if let Some(entry) = in_dmap { | |||
|
226 | return Some(Ok(( | |||
|
227 | normalized, | |||
|
228 | dispatch_found( | |||
|
229 | &normalized, | |||
|
230 | *entry, | |||
|
231 | HgMetadata::from_metadata(meta), | |||
|
232 | &dmap.copy_map, | |||
|
233 | options, | |||
|
234 | ), | |||
|
235 | ))); | |||
|
236 | } | |||
|
237 | Some(Ok((normalized, Dispatch::Unknown))) | |||
|
238 | } else { | |||
|
239 | if file_type.is_dir() { | |||
|
240 | Some(Ok(( | |||
|
241 | normalized, | |||
|
242 | Dispatch::Directory { | |||
|
243 | was_file: in_dmap.is_some(), | |||
|
244 | }, | |||
|
245 | ))) | |||
|
246 | } else { | |||
|
247 | Some(Ok(( | |||
|
248 | normalized, | |||
|
249 | Dispatch::Bad(BadMatch::BadType( | |||
|
250 | // TODO do more than unknown | |||
|
251 | // Support for all `BadType` variant | |||
|
252 | // varies greatly between platforms. | |||
|
253 | // So far, no tests check the type and | |||
|
254 | // this should be good enough for most | |||
|
255 | // users. | |||
|
256 | BadType::Unknown, | |||
|
257 | )), | |||
|
258 | ))) | |||
|
259 | } | |||
|
260 | }; | |||
|
261 | } | |||
|
262 | Err(_) => { | |||
|
263 | if let Some(entry) = in_dmap { | |||
205 | return Some(Ok(( |
|
264 | return Some(Ok(( | |
206 | normalized, |
|
265 | normalized, | |
207 |
dispatch_ |
|
266 | dispatch_missing(entry.state), | |
208 | &normalized, |
|
|||
209 | *entry, |
|
|||
210 | HgMetadata::from_metadata(meta), |
|
|||
211 | &dmap.copy_map, |
|
|||
212 | options, |
|
|||
213 | ), |
|
|||
214 | ))); |
|
267 | ))); | |
215 | } |
|
268 | } | |
216 | } else { |
|
|||
217 | if dmap.contains_key(normalized) { |
|
|||
218 | return Some(Ok((normalized, Dispatch::Removed))); |
|
|||
219 | } |
|
|||
220 | } |
|
269 | } | |
221 | } |
|
270 | }; | |
222 |
|
|
271 | None | |
223 | if let Some(entry) = dmap.get(normalized) { |
|
272 | }) | |
224 | return Some(Ok(( |
|
273 | .flatten() | |
225 | normalized, |
|
|||
226 | dispatch_missing(entry.state), |
|
|||
227 | ))); |
|
|||
228 | } |
|
|||
229 | } |
|
|||
230 | }; |
|
|||
231 | None |
|
|||
232 | }) |
|
|||
233 | } |
|
274 | } | |
234 |
|
275 | |||
235 | #[derive(Debug, Copy, Clone)] |
|
276 | #[derive(Debug, Copy, Clone)] | |
@@ -415,8 +456,7 b" fn traverse<'a>(" | |||||
415 | Ok(new_results) |
|
456 | Ok(new_results) | |
416 | } |
|
457 | } | |
417 |
|
458 | |||
418 |
/// Stat all entries in the `DirstateMap` and mark them for dispatch |
|
459 | /// Stat all entries in the `DirstateMap` and mark them for dispatch. | |
419 | /// the relevant collections. |
|
|||
420 | fn stat_dmap_entries( |
|
460 | fn stat_dmap_entries( | |
421 | dmap: &DirstateMap, |
|
461 | dmap: &DirstateMap, | |
422 | root_dir: impl AsRef<Path> + Sync + Send, |
|
462 | root_dir: impl AsRef<Path> + Sync + Send, | |
@@ -445,7 +485,7 b' fn stat_dmap_entries(' | |||||
445 | ), |
|
485 | ), | |
446 | )), |
|
486 | )), | |
447 | Err(ref e) |
|
487 | Err(ref e) | |
448 |
if e.kind() == |
|
488 | if e.kind() == ErrorKind::NotFound | |
449 | || e.raw_os_error() == Some(20) => |
|
489 | || e.raw_os_error() == Some(20) => | |
450 | { |
|
490 | { | |
451 | // Rust does not yet have an `ErrorKind` for |
|
491 | // Rust does not yet have an `ErrorKind` for | |
@@ -460,19 +500,19 b' fn stat_dmap_entries(' | |||||
460 | } |
|
500 | } | |
461 |
|
501 | |||
462 | pub struct DirstateStatus<'a> { |
|
502 | pub struct DirstateStatus<'a> { | |
463 |
pub modified: Vec< |
|
503 | pub modified: Vec<Cow<'a, HgPath>>, | |
464 |
pub added: Vec< |
|
504 | pub added: Vec<Cow<'a, HgPath>>, | |
465 |
pub removed: Vec< |
|
505 | pub removed: Vec<Cow<'a, HgPath>>, | |
466 |
pub deleted: Vec< |
|
506 | pub deleted: Vec<Cow<'a, HgPath>>, | |
467 |
pub clean: Vec< |
|
507 | pub clean: Vec<Cow<'a, HgPath>>, | |
468 |
pub ignored: Vec< |
|
508 | pub ignored: Vec<Cow<'a, HgPath>>, | |
469 |
pub unknown: Vec< |
|
509 | pub unknown: Vec<Cow<'a, HgPath>>, | |
470 |
pub bad: Vec<( |
|
510 | pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>, | |
471 | } |
|
511 | } | |
472 |
|
512 | |||
473 | fn build_response<'a>( |
|
513 | fn build_response<'a>( | |
474 |
results: impl IntoIterator<Item = |
|
514 | results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>, | |
475 |
) -> |
|
515 | ) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) { | |
476 | let mut lookup = vec![]; |
|
516 | let mut lookup = vec![]; | |
477 | let mut modified = vec![]; |
|
517 | let mut modified = vec![]; | |
478 | let mut added = vec![]; |
|
518 | let mut added = vec![]; | |
@@ -483,8 +523,7 b" fn build_response<'a>(" | |||||
483 | let mut unknown = vec![]; |
|
523 | let mut unknown = vec![]; | |
484 | let mut bad = vec![]; |
|
524 | let mut bad = vec![]; | |
485 |
|
525 | |||
486 |
for |
|
526 | for (filename, dispatch) in results.into_iter() { | |
487 | let (filename, dispatch) = res?; |
|
|||
488 | match dispatch { |
|
527 | match dispatch { | |
489 | Dispatch::Unknown => unknown.push(filename), |
|
528 | Dispatch::Unknown => unknown.push(filename), | |
490 | Dispatch::Unsure => lookup.push(filename), |
|
529 | Dispatch::Unsure => lookup.push(filename), | |
@@ -500,7 +539,7 b" fn build_response<'a>(" | |||||
500 | } |
|
539 | } | |
501 | } |
|
540 | } | |
502 |
|
541 | |||
503 |
|
|
542 | ( | |
504 | lookup, |
|
543 | lookup, | |
505 | DirstateStatus { |
|
544 | DirstateStatus { | |
506 | modified, |
|
545 | modified, | |
@@ -512,25 +551,206 b" fn build_response<'a>(" | |||||
512 | unknown, |
|
551 | unknown, | |
513 | bad, |
|
552 | bad, | |
514 | }, |
|
553 | }, | |
515 |
) |
|
554 | ) | |
|
555 | } | |||
|
556 | ||||
|
557 | pub enum StatusError { | |||
|
558 | IO(std::io::Error), | |||
|
559 | Path(HgPathError), | |||
|
560 | Pattern(PatternError), | |||
516 | } |
|
561 | } | |
517 |
|
562 | |||
|
563 | pub type StatusResult<T> = Result<T, StatusError>; | |||
|
564 | ||||
|
565 | impl From<PatternError> for StatusError { | |||
|
566 | fn from(e: PatternError) -> Self { | |||
|
567 | StatusError::Pattern(e) | |||
|
568 | } | |||
|
569 | } | |||
|
570 | impl From<HgPathError> for StatusError { | |||
|
571 | fn from(e: HgPathError) -> Self { | |||
|
572 | StatusError::Path(e) | |||
|
573 | } | |||
|
574 | } | |||
|
575 | impl From<std::io::Error> for StatusError { | |||
|
576 | fn from(e: std::io::Error) -> Self { | |||
|
577 | StatusError::IO(e) | |||
|
578 | } | |||
|
579 | } | |||
|
580 | ||||
|
581 | impl ToString for StatusError { | |||
|
582 | fn to_string(&self) -> String { | |||
|
583 | match self { | |||
|
584 | StatusError::IO(e) => e.to_string(), | |||
|
585 | StatusError::Path(e) => e.to_string(), | |||
|
586 | StatusError::Pattern(e) => e.to_string(), | |||
|
587 | } | |||
|
588 | } | |||
|
589 | } | |||
|
590 | ||||
|
591 | /// Get the status of files in the working directory. | |||
|
592 | /// | |||
|
593 | /// This is the current entry-point for `hg-core` and is realistically unusable | |||
|
594 | /// outside of a Python context because its arguments need to provide a lot of | |||
|
595 | /// information that will not be necessary in the future. | |||
518 | pub fn status<'a: 'c, 'b: 'c, 'c>( |
|
596 | pub fn status<'a: 'c, 'b: 'c, 'c>( | |
519 | dmap: &'a DirstateMap, |
|
597 | dmap: &'a DirstateMap, | |
520 | matcher: &'b impl Matcher, |
|
598 | matcher: &'b (impl Matcher + Sync), | |
521 | root_dir: impl AsRef<Path> + Sync + Send + Copy, |
|
599 | root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c, | |
|
600 | ignore_files: &[impl AsRef<Path> + 'c], | |||
522 | options: StatusOptions, |
|
601 | options: StatusOptions, | |
523 | ) -> IoResult<(Vec<&'c HgPath>, DirstateStatus<'c>)> { |
|
602 | ) -> StatusResult<( | |
|
603 | (Vec<Cow<'c, HgPath>>, DirstateStatus<'c>), | |||
|
604 | Vec<PatternFileWarning>, | |||
|
605 | )> { | |||
|
606 | let (ignore_fn, warnings) = get_ignore_function(&ignore_files, root_dir)?; | |||
|
607 | ||||
|
608 | // Is the path or one of its ancestors ignored? | |||
|
609 | let dir_ignore_fn = |dir: &_| { | |||
|
610 | if ignore_fn(dir) { | |||
|
611 | true | |||
|
612 | } else { | |||
|
613 | for p in find_dirs(dir) { | |||
|
614 | if ignore_fn(p) { | |||
|
615 | return true; | |||
|
616 | } | |||
|
617 | } | |||
|
618 | false | |||
|
619 | } | |||
|
620 | }; | |||
|
621 | ||||
524 | let files = matcher.file_set(); |
|
622 | let files = matcher.file_set(); | |
525 | let mut results = vec![]; |
|
623 | ||
526 | if let Some(files) = files { |
|
624 | // Step 1: check the files explicitly mentioned by the user | |
527 |
|
|
625 | let explicit = walk_explicit(files, &dmap, root_dir, options); | |
|
626 | let (work, mut results): (Vec<_>, FastHashMap<_, _>) = explicit | |||
|
627 | .filter_map(Result::ok) | |||
|
628 | .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch)) | |||
|
629 | .partition(|(_, dispatch)| match dispatch { | |||
|
630 | Dispatch::Directory { .. } => true, | |||
|
631 | _ => false, | |||
|
632 | }); | |||
|
633 | ||||
|
634 | // Step 2: recursively check the working directory for changes if needed | |||
|
635 | for (dir, dispatch) in work { | |||
|
636 | match dispatch { | |||
|
637 | Dispatch::Directory { was_file } => { | |||
|
638 | if was_file { | |||
|
639 | results.insert(dir.to_owned(), Dispatch::Removed); | |||
|
640 | } | |||
|
641 | if options.list_ignored | |||
|
642 | || options.list_unknown && !dir_ignore_fn(&dir) | |||
|
643 | { | |||
|
644 | results = traverse( | |||
|
645 | matcher, | |||
|
646 | root_dir, | |||
|
647 | &dmap, | |||
|
648 | &dir, | |||
|
649 | results, | |||
|
650 | &ignore_fn, | |||
|
651 | &dir_ignore_fn, | |||
|
652 | options, | |||
|
653 | )?; | |||
|
654 | } | |||
|
655 | } | |||
|
656 | _ => unreachable!("There can only be directories in `work`"), | |||
|
657 | } | |||
528 | } |
|
658 | } | |
529 |
|
659 | |||
530 | if !matcher.is_exact() { |
|
660 | if !matcher.is_exact() { | |
531 | let stat_results = stat_dmap_entries(&dmap, root_dir, options); |
|
661 | // Step 3: Check the remaining files from the dmap. | |
532 | results.par_extend(stat_results); |
|
662 | // If a dmap file is not in results yet, it was either | |
|
663 | // a) not matched b) ignored, c) missing, or d) under a | |||
|
664 | // symlink directory. | |||
|
665 | ||||
|
666 | if options.list_unknown { | |||
|
667 | let to_visit: Box<dyn Iterator<Item = (&HgPath, &DirstateEntry)>> = | |||
|
668 | if results.is_empty() && matcher.matches_everything() { | |||
|
669 | Box::new(dmap.iter().map(|(f, e)| (f.deref(), e))) | |||
|
670 | } else { | |||
|
671 | Box::new(dmap.iter().filter_map(|(f, e)| { | |||
|
672 | if !results.contains_key(f.deref()) | |||
|
673 | && matcher.matches(f) | |||
|
674 | { | |||
|
675 | Some((f.deref(), e)) | |||
|
676 | } else { | |||
|
677 | None | |||
|
678 | } | |||
|
679 | })) | |||
|
680 | }; | |||
|
681 | let mut to_visit: Vec<_> = to_visit.collect(); | |||
|
682 | to_visit.sort_by(|a, b| a.0.cmp(&b.0)); | |||
|
683 | ||||
|
684 | // We walked all dirs under the roots that weren't ignored, and | |||
|
685 | // everything that matched was stat'ed and is already in results. | |||
|
686 | // The rest must thus be ignored or under a symlink. | |||
|
687 | let mut path_auditor = PathAuditor::new(root_dir); | |||
|
688 | ||||
|
689 | for (ref filename, entry) in to_visit { | |||
|
690 | // Report ignored items in the dmap as long as they are not | |||
|
691 | // under a symlink directory. | |||
|
692 | if path_auditor.check(filename) { | |||
|
693 | // TODO normalize for case-insensitive filesystems | |||
|
694 | let buf = hg_path_to_path_buf(filename)?; | |||
|
695 | results.insert( | |||
|
696 | Cow::Borrowed(filename), | |||
|
697 | match root_dir.as_ref().join(&buf).symlink_metadata() { | |||
|
698 | // File was just ignored, no links, and exists | |||
|
699 | Ok(meta) => { | |||
|
700 | let metadata = HgMetadata::from_metadata(meta); | |||
|
701 | dispatch_found( | |||
|
702 | filename, | |||
|
703 | *entry, | |||
|
704 | metadata, | |||
|
705 | &dmap.copy_map, | |||
|
706 | options, | |||
|
707 | ) | |||
|
708 | } | |||
|
709 | // File doesn't exist | |||
|
710 | Err(_) => dispatch_missing(entry.state), | |||
|
711 | }, | |||
|
712 | ); | |||
|
713 | } else { | |||
|
714 | // It's either missing or under a symlink directory which | |||
|
715 | // we, in this case, report as missing. | |||
|
716 | results.insert( | |||
|
717 | Cow::Borrowed(filename), | |||
|
718 | dispatch_missing(entry.state), | |||
|
719 | ); | |||
|
720 | } | |||
|
721 | } | |||
|
722 | } else { | |||
|
723 | // We may not have walked the full directory tree above, so stat | |||
|
724 | // and check everything we missed. | |||
|
725 | let stat_results = stat_dmap_entries(&dmap, root_dir, options); | |||
|
726 | results.par_extend(stat_results.flatten().map( | |||
|
727 | |(filename, dispatch)| (Cow::Borrowed(filename), dispatch), | |||
|
728 | )); | |||
|
729 | } | |||
533 | } |
|
730 | } | |
534 |
|
731 | |||
535 | build_response(results) |
|
732 | let results = results.into_iter().filter_map(|(filename, dispatch)| { | |
|
733 | match dispatch { | |||
|
734 | Dispatch::Bad(_) => return Some((filename, dispatch)), | |||
|
735 | _ => {} | |||
|
736 | }; | |||
|
737 | // TODO do this in //, not at the end | |||
|
738 | if !dmap.contains_key(filename.deref()) { | |||
|
739 | if (options.list_ignored || matcher.exact_match(&filename)) | |||
|
740 | && dir_ignore_fn(&filename) | |||
|
741 | { | |||
|
742 | if options.list_ignored { | |||
|
743 | return Some((filename.to_owned(), Dispatch::Ignored)); | |||
|
744 | } | |||
|
745 | } else { | |||
|
746 | if !ignore_fn(&filename) { | |||
|
747 | return Some((filename.to_owned(), Dispatch::Unknown)); | |||
|
748 | } | |||
|
749 | } | |||
|
750 | return None; | |||
|
751 | } | |||
|
752 | Some((filename, dispatch)) | |||
|
753 | }); | |||
|
754 | ||||
|
755 | Ok((build_response(results), warnings)) | |||
536 | } |
|
756 | } |
@@ -13,7 +13,9 b' pub use dirstate::{' | |||||
13 | dirs_multiset::{DirsMultiset, DirsMultisetIter}, |
|
13 | dirs_multiset::{DirsMultiset, DirsMultisetIter}, | |
14 | dirstate_map::DirstateMap, |
|
14 | dirstate_map::DirstateMap, | |
15 | parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE}, |
|
15 | parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE}, | |
16 | status::{status, DirstateStatus, StatusOptions}, |
|
16 | status::{ | |
|
17 | status, BadMatch, BadType, DirstateStatus, StatusError, StatusOptions, | |||
|
18 | }, | |||
17 | CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState, |
|
19 | CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState, | |
18 | StateMap, StateMapIter, |
|
20 | StateMap, StateMapIter, | |
19 | }; |
|
21 | }; |
General Comments 0
You need to be logged in to leave comments.
Login now