# HG changeset patch # User Raphaël Gomès # Date 1713349728 -7200 # Node ID ee1b648e4453ef5748a506492106018e934b5f22 # Parent b0aaffcb6fcfa52e822ee6e7202ba63827865a8a# Parent 13c004b54cbe296b2fa236281e7769dc28554f3b branching: merge stable into default diff -r b0aaffcb6fcf -r ee1b648e4453 mercurial/bundlecaches.py --- a/mercurial/bundlecaches.py Wed Mar 13 12:02:06 2024 +0100 +++ b/mercurial/bundlecaches.py Wed Apr 17 12:28:48 2024 +0200 @@ -179,6 +179,9 @@ b"obsolescence": param_bool, b"obsolescence-mandatory": param_bool, b"phases": param_bool, + b"changegroup": param_bool, + b"tagsfnodescache": param_bool, + b"revbranchcache": param_bool, } diff -r b0aaffcb6fcf -r ee1b648e4453 mercurial/exchange.py --- a/mercurial/exchange.py Wed Mar 13 12:02:06 2024 +0100 +++ b/mercurial/exchange.py Wed Apr 17 12:28:48 2024 +0200 @@ -158,9 +158,8 @@ params[b'obsolescence-mandatory'] = b'no' if not version: - raise error.Abort( - _(b'could not identify changegroup version in bundle') - ) + params[b'changegroup'] = b'no' + version = b'v2' spec = b'%s-%s' % (comp, version) if params: spec += b';' diff -r b0aaffcb6fcf -r ee1b648e4453 mercurial/match.py --- a/mercurial/match.py Wed Mar 13 12:02:06 2024 +0100 +++ b/mercurial/match.py Wed Apr 17 12:28:48 2024 +0200 @@ -638,7 +638,10 @@ super(patternmatcher, self).__init__(badfn) kindpats.sort() + roots, dirs, parents = _rootsdirsandparents(kindpats) self._files = _explicitfiles(kindpats) + self._dirs_explicit = set(dirs) + self._dirs = parents self._prefix = _prefix(kindpats) self._pats, self._matchfn = _buildmatch(kindpats, b'$', root) @@ -647,14 +650,14 @@ return True return self._matchfn(fn) - @propertycache - def _dirs(self): - return set(pathutil.dirs(self._fileset)) - def visitdir(self, dir): if self._prefix and dir in self._fileset: return b'all' - return dir in self._dirs or path_or_parents_in_set(dir, self._fileset) + return ( + dir in self._dirs + or path_or_parents_in_set(dir, self._fileset) + or path_or_parents_in_set(dir, self._dirs_explicit) + ) def visitchildrenset(self, dir): ret = self.visitdir(dir) @@ -1461,7 +1464,7 @@ allgroups = [] regexps = [] exact = set() - for (kind, pattern, _source) in kindpats: + for kind, pattern, _source in kindpats: if kind == b'filepath': exact.add(pattern) continue diff -r b0aaffcb6fcf -r ee1b648e4453 mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py Wed Mar 13 12:02:06 2024 +0100 +++ b/mercurial/wireprotoserver.py Wed Apr 17 12:28:48 2024 +0200 @@ -527,24 +527,34 @@ def __init__(self, ui, repo, logfh=None, accesshidden=False): self._ui = ui self._repo = repo - self._fin, self._fout = ui.protectfinout() self._accesshidden = accesshidden - - # Log write I/O to stdout and stderr if configured. - if logfh: - self._fout = util.makeloggingfileobject( - logfh, self._fout, b'o', logdata=True - ) - ui.ferr = util.makeloggingfileobject( - logfh, ui.ferr, b'e', logdata=True - ) + self._logfh = logfh def serve_forever(self): self.serveuntil(threading.Event()) - self._ui.restorefinout(self._fin, self._fout) def serveuntil(self, ev): """Serve until a threading.Event is set.""" - _runsshserver( - self._ui, self._repo, self._fin, self._fout, ev, self._accesshidden - ) + with self._ui.protectedfinout() as (fin, fout): + if self._logfh: + # Log write I/O to stdout and stderr if configured. + fout = util.makeloggingfileobject( + self._logfh, + fout, + b'o', + logdata=True, + ) + self._ui.ferr = util.makeloggingfileobject( + self._logfh, + self._ui.ferr, + b'e', + logdata=True, + ) + _runsshserver( + self._ui, + self._repo, + fin, + fout, + ev, + self._accesshidden, + ) diff -r b0aaffcb6fcf -r ee1b648e4453 rust/hg-core/src/dirstate/dirs_multiset.rs --- a/rust/hg-core/src/dirstate/dirs_multiset.rs Wed Mar 13 12:02:06 2024 +0100 +++ b/rust/hg-core/src/dirstate/dirs_multiset.rs Wed Apr 17 12:28:48 2024 +0200 @@ -158,14 +158,13 @@ } impl<'a> DirsChildrenMultiset<'a> { - pub fn new( + pub fn new>( paths: impl Iterator, - only_include: Option<&'a HashSet + 'a>>, + only_include: Option, ) -> Self { let mut new = Self { inner: HashMap::default(), - only_include: only_include - .map(|s| s.iter().map(AsRef::as_ref).collect()), + only_include: only_include.map(|s| s.map(AsRef::as_ref).collect()), }; for path in paths { diff -r b0aaffcb6fcf -r ee1b648e4453 rust/hg-core/src/filepatterns.rs --- a/rust/hg-core/src/filepatterns.rs Wed Mar 13 12:02:06 2024 +0100 +++ b/rust/hg-core/src/filepatterns.rs Wed Apr 17 12:28:48 2024 +0200 @@ -57,7 +57,7 @@ RelRegexp, /// A path relative to repository root, which is matched non-recursively /// (will not match subdirectories) - RootFiles, + RootFilesIn, /// A file of patterns to read and include Include, /// A file of patterns to match against files under the same directory @@ -158,7 +158,7 @@ b"path:" => Ok(PatternSyntax::Path), b"filepath:" => Ok(PatternSyntax::FilePath), b"relpath:" => Ok(PatternSyntax::RelPath), - b"rootfilesin:" => Ok(PatternSyntax::RootFiles), + b"rootfilesin:" => Ok(PatternSyntax::RootFilesIn), b"relglob:" => Ok(PatternSyntax::RelGlob), b"relre:" => Ok(PatternSyntax::RelRegexp), b"glob:" => Ok(PatternSyntax::Glob), @@ -227,7 +227,7 @@ } [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat() } - PatternSyntax::RootFiles => { + PatternSyntax::RootFilesIn => { let mut res = if pattern == b"." { vec![] } else { @@ -316,7 +316,7 @@ | PatternSyntax::Path | PatternSyntax::RelGlob | PatternSyntax::RelPath - | PatternSyntax::RootFiles => normalize_path_bytes(pattern), + | PatternSyntax::RootFilesIn => normalize_path_bytes(pattern), PatternSyntax::Include | PatternSyntax::SubInclude => { return Err(PatternError::NonRegexPattern(entry.clone())) } @@ -342,7 +342,7 @@ m.insert(b"path:".as_ref(), PatternSyntax::Path); m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath); m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath); - m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles); + m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFilesIn); m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob); m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp); m.insert(b"glob:".as_ref(), PatternSyntax::Glob); @@ -385,7 +385,7 @@ | PatternSyntax::Glob | PatternSyntax::RelGlob | PatternSyntax::RelPath - | PatternSyntax::RootFiles + | PatternSyntax::RootFilesIn if normalize => { normalize_path_bytes(pattern_bytes) diff -r b0aaffcb6fcf -r ee1b648e4453 rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs Wed Mar 13 12:02:06 2024 +0100 +++ b/rust/hg-core/src/matchers.rs Wed Apr 17 12:28:48 2024 +0200 @@ -17,7 +17,7 @@ PatternFileWarning, PatternResult, }, utils::{ - files::find_dirs, + files::{dir_ancestors, find_dirs}, hg_path::{HgPath, HgPathBuf, HgPathError}, Escaped, }, @@ -35,12 +35,14 @@ pub enum VisitChildrenSet { /// Don't visit anything Empty, - /// Only visit this directory + /// Visit this directory and probably its children This, - /// Visit this directory and these subdirectories + /// Only visit the children (both files and directories) if they + /// are mentioned in this set. (empty set corresponds to [Empty]) /// TODO Should we implement a `NonEmptyHashSet`? Set(HashSet), /// Visit this directory and all subdirectories + /// (you can stop asking about the children set) Recursive, } @@ -297,6 +299,7 @@ /// Whether all the patterns match a prefix (i.e. recursively) prefix: bool, files: HashSet, + dirs_explicit: HashSet, dirs: DirsMultiset, } @@ -313,8 +316,13 @@ impl<'a> PatternMatcher<'a> { pub fn new(ignore_patterns: Vec) -> PatternResult { - let (files, _) = roots_and_dirs(&ignore_patterns); - let dirs = DirsMultiset::from_manifest(&files)?; + let RootsDirsAndParents { + roots, + dirs: dirs_explicit, + parents, + } = roots_dirs_and_parents(&ignore_patterns)?; + let files = roots; + let dirs = parents; let files: HashSet = HashSet::from_iter(files); let prefix = ignore_patterns.iter().all(|k| { @@ -328,6 +336,7 @@ prefix, files, dirs, + dirs_explicit, }) } } @@ -352,9 +361,13 @@ if self.prefix && self.files.contains(directory) { return VisitChildrenSet::Recursive; } - let path_or_parents_in_set = find_dirs(directory) - .any(|parent_dir| self.files.contains(parent_dir)); - if self.dirs.contains(directory) || path_or_parents_in_set { + if self.dirs.contains(directory) { + return VisitChildrenSet::This; + } + if dir_ancestors(directory).any(|parent_dir| { + self.files.contains(parent_dir) + || self.dirs_explicit.contains(parent_dir) + }) { VisitChildrenSet::This } else { VisitChildrenSet::Empty @@ -390,7 +403,7 @@ /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false); /// /// /// let ignore_patterns = -/// vec![IgnorePattern::new(PatternSyntax::RootFiles, b"dir/subdir", Path::new(""))]; +/// vec![IgnorePattern::new(PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""))]; /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap(); /// /// /// assert!(!matcher.matches(HgPath::new(b"file"))); @@ -405,7 +418,7 @@ prefix: bool, roots: HashSet, dirs: HashSet, - parents: HashSet, + parents: DirsMultiset, } impl core::fmt::Debug for IncludeMatcher<'_> { @@ -861,7 +874,7 @@ }); roots.push(pat.to_owned()); } - PatternSyntax::RootFiles => { + PatternSyntax::RootFilesIn => { let pat = if pattern == b"." { &[] as &[u8] } else { @@ -885,7 +898,7 @@ /// Directories to match non-recursively pub dirs: HashSet, /// Implicitly required directories to go to items in either roots or dirs - pub parents: HashSet, + pub parents: DirsMultiset, } /// Extract roots, dirs and parents from patterns. @@ -894,18 +907,11 @@ ) -> PatternResult { let (roots, dirs) = roots_and_dirs(ignore_patterns); - let mut parents = HashSet::new(); + let mut parents = DirsMultiset::from_manifest(&dirs)?; - parents.extend( - DirsMultiset::from_manifest(&dirs)? - .iter() - .map(ToOwned::to_owned), - ); - parents.extend( - DirsMultiset::from_manifest(&roots)? - .iter() - .map(ToOwned::to_owned), - ); + for path in &roots { + parents.add_path(path)? + } Ok(RootsDirsAndParents { roots: HashSet::from_iter(roots), @@ -958,7 +964,7 @@ // with a regex. if ignore_patterns .iter() - .all(|k| k.syntax == PatternSyntax::RootFiles) + .all(|k| k.syntax == PatternSyntax::RootFilesIn) { let dirs: HashSet<_> = ignore_patterns .iter() @@ -1077,7 +1083,7 @@ .iter() .chain(self.roots.iter()) .chain(self.parents.iter()); - DirsChildrenMultiset::new(thing, Some(&self.parents)) + DirsChildrenMultiset::new(thing, Some(self.parents.iter())) } pub fn debug_get_patterns(&self) -> &[u8] { @@ -1105,6 +1111,9 @@ mod tests { use super::*; use pretty_assertions::assert_eq; + use std::collections::BTreeMap; + use std::collections::BTreeSet; + use std::fmt::Debug; use std::path::Path; #[test] @@ -1141,9 +1150,12 @@ let dirs = HashSet::new(); - let mut parents = HashSet::new(); - parents.insert(HgPathBuf::new()); - parents.insert(HgPathBuf::from_bytes(b"g")); + let parents = DirsMultiset::from_manifest(&[ + HgPathBuf::from_bytes(b"x"), + HgPathBuf::from_bytes(b"g/x"), + HgPathBuf::from_bytes(b"g/y"), + ]) + .unwrap(); assert_eq!( roots_dirs_and_parents(&pats).unwrap(), @@ -1316,61 +1328,60 @@ // VisitdirRootfilesin let m = PatternMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""), )]) .unwrap(); assert_eq!( m.visit_children_set(HgPath::new(b"dir/subdir/x")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"folder")), VisitChildrenSet::Empty ); - // FIXME: These should probably be This. assert_eq!( m.visit_children_set(HgPath::new(b"")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"dir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"dir/subdir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); // VisitchildrensetRootfilesin let m = PatternMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""), )]) .unwrap(); assert_eq!( m.visit_children_set(HgPath::new(b"dir/subdir/x")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"folder")), VisitChildrenSet::Empty ); // FIXME: These should probably be {'dir'}, {'subdir'} and This, - // respectively, or at least This for all three. + // respectively assert_eq!( m.visit_children_set(HgPath::new(b"")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"dir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"dir/subdir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); // VisitdirGlob @@ -1384,10 +1395,9 @@ m.visit_children_set(HgPath::new(b"")), VisitChildrenSet::This ); - // FIXME: This probably should be This assert_eq!( m.visit_children_set(HgPath::new(b"dir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); assert_eq!( m.visit_children_set(HgPath::new(b"folder")), @@ -1418,10 +1428,9 @@ m.visit_children_set(HgPath::new(b"folder")), VisitChildrenSet::Empty ); - // FIXME: This probably should be This assert_eq!( m.visit_children_set(HgPath::new(b"dir")), - VisitChildrenSet::Empty + VisitChildrenSet::This ); // OPT: these should probably be Empty assert_eq!( @@ -1529,7 +1538,7 @@ // VisitchildrensetRootfilesin let matcher = IncludeMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""), )]) @@ -1664,7 +1673,7 @@ )]) .unwrap(); let m2 = IncludeMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir", Path::new(""), )]) @@ -1825,7 +1834,7 @@ ); let m2 = Box::new( IncludeMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir", Path::new(""), )]) @@ -2076,7 +2085,7 @@ ); let m2 = Box::new( IncludeMatcher::new(vec![IgnorePattern::new( - PatternSyntax::RootFiles, + PatternSyntax::RootFilesIn, b"dir", Path::new("/repo"), )]) @@ -2119,4 +2128,323 @@ VisitChildrenSet::This ); } + + mod invariants { + pub mod visit_children_set { + + use crate::{ + matchers::{tests::Tree, Matcher, VisitChildrenSet}, + utils::hg_path::HgPath, + }; + + #[allow(dead_code)] + #[derive(Debug)] + struct Error<'a, M> { + matcher: &'a M, + path: &'a HgPath, + matching: &'a Tree, + visit_children_set: &'a VisitChildrenSet, + } + + fn holds( + matching: &Tree, + not_matching: &Tree, + vcs: &VisitChildrenSet, + ) -> bool { + match vcs { + VisitChildrenSet::Empty => matching.is_empty(), + VisitChildrenSet::This => { + // `This` does not come with any obligations. + true + } + VisitChildrenSet::Recursive => { + // `Recursive` requires that *everything* in the + // subtree matches. This + // requirement is relied on for example in + // DifferenceMatcher implementation. + not_matching.is_empty() + } + VisitChildrenSet::Set(allowed_children) => { + // `allowed_children` does not distinguish between + // files and directories: if it's not included, it + // must not be matched. + for k in matching.dirs.keys() { + if !(allowed_children.contains(k)) { + return false; + } + } + for k in matching.files.iter() { + if !(allowed_children.contains(k)) { + return false; + } + } + true + } + } + } + + pub fn check( + matcher: &M, + path: &HgPath, + matching: &Tree, + not_matching: &Tree, + visit_children_set: &VisitChildrenSet, + ) { + if !holds(matching, not_matching, visit_children_set) { + panic!( + "{:#?}", + Error { + matcher, + path, + visit_children_set, + matching + } + ) + } + } + } + } + + #[derive(Debug, Clone)] + pub struct Tree { + files: BTreeSet, + dirs: BTreeMap, + } + + impl Tree { + fn len(&self) -> usize { + let mut n = 0; + n += self.files.len(); + for d in self.dirs.values() { + n += d.len(); + } + n + } + + fn is_empty(&self) -> bool { + self.files.is_empty() && self.dirs.is_empty() + } + + fn make( + files: BTreeSet, + dirs: BTreeMap, + ) -> Self { + Self { + files, + dirs: dirs + .into_iter() + .filter(|(_k, v)| (!(v.is_empty()))) + .collect(), + } + } + + fn filter_and_check( + &self, + m: &M, + path: &HgPath, + ) -> (Self, Self) { + let (files1, files2): (BTreeSet, BTreeSet) = + self.files + .iter() + .map(|v| v.to_owned()) + .partition(|v| m.matches(&path.join(v))); + let (dirs1, dirs2): ( + BTreeMap, + BTreeMap, + ) = self + .dirs + .iter() + .map(|(k, v)| { + let path = path.join(k); + let (t1, t2) = v.filter_and_check(m, &path); + ((k.clone(), t1), (k.clone(), t2)) + }) + .unzip(); + let matching = Self::make(files1, dirs1); + let not_matching = Self::make(files2, dirs2); + let vcs = m.visit_children_set(path); + invariants::visit_children_set::check( + m, + path, + &matching, + ¬_matching, + &vcs, + ); + (matching, not_matching) + } + + fn check_matcher( + &self, + m: &M, + expect_count: usize, + ) { + let res = self.filter_and_check(m, &HgPathBuf::new()); + if expect_count != res.0.len() { + eprintln!( + "warning: expected {} matches, got {} for {:#?}", + expect_count, + res.0.len(), + m + ); + } + } + } + + fn mkdir(children: &[(&[u8], &Tree)]) -> Tree { + let p = HgPathBuf::from_bytes; + let names = [ + p(b"a"), + p(b"b.txt"), + p(b"file.txt"), + p(b"c.c"), + p(b"c.h"), + p(b"dir1"), + p(b"dir2"), + p(b"subdir"), + ]; + let files: BTreeSet = BTreeSet::from(names); + let dirs = children + .iter() + .map(|(name, t)| (p(name), (*t).clone())) + .collect(); + Tree { files, dirs } + } + + fn make_example_tree() -> Tree { + let leaf = mkdir(&[]); + let abc = mkdir(&[(b"d", &leaf)]); + let ab = mkdir(&[(b"c", &abc)]); + let a = mkdir(&[(b"b", &ab)]); + let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]); + mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)]) + } + + #[test] + fn test_pattern_matcher_visit_children_set() { + let tree = make_example_tree(); + let pattern_dir1_glob_c = + PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::Glob, + b"dir1/*.c", + Path::new(""), + )]) + .unwrap(); + let pattern_dir1 = || { + PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::Path, + b"dir1", + Path::new(""), + )]) + .unwrap() + }; + let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::Glob, + b"dir1/a", + Path::new(""), + )]) + .unwrap(); + let pattern_relglob_c = || { + PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelGlob, + b"*.c", + Path::new(""), + )]) + .unwrap() + }; + let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")]; + let file_dir_subdir_b = FileMatcher::new(files).unwrap(); + + let files = vec![ + HgPathBuf::from_bytes(b"file.txt"), + HgPathBuf::from_bytes(b"a/file.txt"), + HgPathBuf::from_bytes(b"a/b/file.txt"), + // No file in a/b/c + HgPathBuf::from_bytes(b"a/b/c/d/file.txt"), + ]; + let file_abcdfile = FileMatcher::new(files).unwrap(); + let rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RootFilesIn, + b"dir", + Path::new(""), + )]) + .unwrap(); + + let pattern_filepath_dir_subdir = + PatternMatcher::new(vec![IgnorePattern::new( + PatternSyntax::FilePath, + b"dir/subdir", + Path::new(""), + )]) + .unwrap(); + + let include_dir_subdir = + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir", + Path::new(""), + )]) + .unwrap(); + + let more_includematchers = [ + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::Glob, + b"dir/s*", + Path::new(""), + )]) + .unwrap(), + // Test multiple patterns + IncludeMatcher::new(vec![ + IgnorePattern::new( + PatternSyntax::RelPath, + b"dir", + Path::new(""), + ), + IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")), + ]) + .unwrap(), + // Test multiple patterns + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::Glob, + b"**/*.c", + Path::new(""), + )]) + .unwrap(), + ]; + + tree.check_matcher(&pattern_dir1(), 25); + tree.check_matcher(&pattern_dir1_a, 1); + tree.check_matcher(&pattern_dir1_glob_c, 2); + tree.check_matcher(&pattern_relglob_c(), 14); + tree.check_matcher(&AlwaysMatcher, 112); + tree.check_matcher(&NeverMatcher, 0); + tree.check_matcher( + &IntersectionMatcher::new( + Box::new(pattern_relglob_c()), + Box::new(pattern_dir1()), + ), + 3, + ); + tree.check_matcher( + &UnionMatcher::new(vec![ + Box::new(pattern_relglob_c()), + Box::new(pattern_dir1()), + ]), + 36, + ); + tree.check_matcher( + &DifferenceMatcher::new( + Box::new(pattern_relglob_c()), + Box::new(pattern_dir1()), + ), + 11, + ); + tree.check_matcher(&file_dir_subdir_b, 1); + tree.check_matcher(&file_abcdfile, 4); + tree.check_matcher(&rootfilesin_dir, 8); + tree.check_matcher(&pattern_filepath_dir_subdir, 1); + tree.check_matcher(&include_dir_subdir, 9); + tree.check_matcher(&more_includematchers[0], 17); + tree.check_matcher(&more_includematchers[1], 25); + tree.check_matcher(&more_includematchers[2], 35); + } } diff -r b0aaffcb6fcf -r ee1b648e4453 rust/hg-core/src/utils/files.rs --- a/rust/hg-core/src/utils/files.rs Wed Mar 13 12:02:06 2024 +0100 +++ b/rust/hg-core/src/utils/files.rs Wed Apr 17 12:28:48 2024 +0200 @@ -120,6 +120,10 @@ dirs } +pub fn dir_ancestors(path: &HgPath) -> Ancestors { + Ancestors { next: Some(path) } +} + /// Returns an iterator yielding ancestor directories of the given repository /// path. /// diff -r b0aaffcb6fcf -r ee1b648e4453 setup.py --- a/setup.py Wed Mar 13 12:02:06 2024 +0100 +++ b/setup.py Wed Apr 17 12:28:48 2024 +0200 @@ -232,6 +232,10 @@ print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr) print(err, file=sys.stderr) if returncode != 0: + print( + "non zero-return '%s': %d" % (' '.join(cmd), returncode), + file=sys.stderr, + ) return b'' return out diff -r b0aaffcb6fcf -r ee1b648e4453 tests/sshprotoext.py --- a/tests/sshprotoext.py Wed Mar 13 12:02:06 2024 +0100 +++ b/tests/sshprotoext.py Wed Apr 17 12:28:48 2024 +0200 @@ -30,7 +30,7 @@ def serve_forever(self): for i in range(10): - self._fout.write(b'banner: line %d\n' % i) + self._ui.fout.write(b'banner: line %d\n' % i) super(bannerserver, self).serve_forever() @@ -45,17 +45,16 @@ """ def serve_forever(self): - l = self._fin.readline() + ui = self._ui + l = ui.fin.readline() assert l == b'hello\n' # Respond to unknown commands with an empty reply. - wireprotoserver._sshv1respondbytes(self._fout, b'') - l = self._fin.readline() + wireprotoserver._sshv1respondbytes(ui.fout, b'') + l = ui.fin.readline() assert l == b'between\n' - proto = wireprotoserver.sshv1protocolhandler( - self._ui, self._fin, self._fout - ) + proto = wireprotoserver.sshv1protocolhandler(ui, ui.fin, ui.fout) rsp = wireprotov1server.dispatch(self._repo, proto, b'between') - wireprotoserver._sshv1respondbytes(self._fout, rsp.data) + wireprotoserver._sshv1respondbytes(ui.fout, rsp.data) super(prehelloserver, self).serve_forever() diff -r b0aaffcb6fcf -r ee1b648e4453 tests/test-bundle-type.t --- a/tests/test-bundle-type.t Wed Mar 13 12:02:06 2024 +0100 +++ b/tests/test-bundle-type.t Wed Apr 17 12:28:48 2024 +0200 @@ -620,3 +620,93 @@ b9f5f740a8cd76700020e3903ee55ecff78bd3e5 $ hg debugbundle ./v2-cg-03.hg --spec bzip2-v2;cg.version=03 + +tests controlling bundle contents +================================= + + $ hg debugupdatecache -R t1 + +default content +--------------- + + $ hg -R t1 bundle --all --quiet --type 'v2' ./v2.hg + $ hg debugbundle ./v2.hg --spec + bzip2-v2 + $ hg debugbundle ./v2.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, version: 02} (mandatory: True) + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + + $ hg -R t1 bundle --all --quiet --type 'v3' ./v3.hg + $ hg debugbundle ./v3.hg --spec + bzip2-v2;cg.version=03 + $ hg debugbundle ./v3.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, targetphase: 2, version: 03} (mandatory: True) + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + phase-heads -- {} (mandatory: True) + +adding extra parts +------------------ + +We should have a "phase-heads" part here that we did not had in the default content + + $ hg -R t1 bundle --all --quiet --type 'v2;phases=1' ./v2-phases.hg + $ hg debugbundle ./v2-phases.hg --spec + bzip2-v2 + $ hg debugbundle ./v2-phases.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, targetphase: 2, version: 02} (mandatory: True) + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + phase-heads -- {} (mandatory: True) + +skipping default inclusion +-------------------------- + + $ hg -R t1 bundle --all --quiet --type 'v2;tagsfnodescache=false' ./v2-no-tfc.hg + $ hg debugbundle ./v2-no-tfc.hg --spec + bzip2-v2 + $ hg debugbundle ./v2-no-tfc.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, version: 02} (mandatory: True) + cache:rev-branch-cache -- {} (mandatory: False) + + $ hg -R t1 bundle --all --quiet --type 'v3;phases=0' ./v3-no-phases.hg + $ hg debugbundle ./v3-no-phases.hg --spec + bzip2-v2;cg.version=03 + $ hg debugbundle ./v3-no-phases.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, version: 03} (mandatory: True) + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + + $ hg -R t1 bundle --all --quiet --type 'v3;phases=no;tagsfnodescache=0' ./v3-multi-no.hg + $ hg debugbundle ./v3-multi-no.hg --spec + bzip2-v2;cg.version=03 + $ hg debugbundle ./v3-multi-no.hg --quiet + Stream params: {Compression: BZ} + changegroup -- {nbchanges: 7, version: 03} (mandatory: True) + cache:rev-branch-cache -- {} (mandatory: False) + +skipping changegroup +-------------------- + + $ hg -R t1 bundle --all --quiet --type 'v2;changegroup=no' ./v2-no-cg.hg + $ hg debugbundle ./v2-no-cg.hg --spec + bzip2-v2;changegroup=no + $ hg debugbundle ./v2-no-cg.hg --quiet + Stream params: {Compression: BZ} + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + + $ hg -R t1 bundle --all --quiet --type 'v3;changegroup=0' ./v3-no-cg.hg + $ hg debugbundle ./v3-no-cg.hg --spec + bzip2-v2;changegroup=no + $ hg debugbundle ./v3-no-cg.hg --quiet + Stream params: {Compression: BZ} + hgtagsfnodes -- {} (mandatory: False) + cache:rev-branch-cache -- {} (mandatory: False) + phase-heads -- {} (mandatory: True) diff -r b0aaffcb6fcf -r ee1b648e4453 tests/test-match.py --- a/tests/test-match.py Wed Mar 13 12:02:06 2024 +0100 +++ b/tests/test-match.py Wed Apr 17 12:28:48 2024 +0200 @@ -94,12 +94,14 @@ patterns=[b'rootfilesin:dir/subdir'], ) assert isinstance(m, matchmod.patternmatcher) - self.assertFalse(m.visitdir(b'dir/subdir/x')) + # OPT: we shouldn't visit [x] as a directory, + # but we should still visit it as a file. + # Unfortunately, `visitdir` is used for both. + self.assertTrue(m.visitdir(b'dir/subdir/x')) self.assertFalse(m.visitdir(b'folder')) - # FIXME: These should probably be True. - self.assertFalse(m.visitdir(b'')) - self.assertFalse(m.visitdir(b'dir')) - self.assertFalse(m.visitdir(b'dir/subdir')) + self.assertTrue(m.visitdir(b'')) + self.assertTrue(m.visitdir(b'dir')) + self.assertTrue(m.visitdir(b'dir/subdir')) def testVisitchildrensetRootfilesin(self): m = matchmod.match( @@ -108,13 +110,13 @@ patterns=[b'rootfilesin:dir/subdir'], ) assert isinstance(m, matchmod.patternmatcher) - self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set()) + self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this') self.assertEqual(m.visitchildrenset(b'folder'), set()) - # FIXME: These should probably be {'dir'}, {'subdir'} and 'this', - # respectively, or at least 'this' for all three. - self.assertEqual(m.visitchildrenset(b''), set()) - self.assertEqual(m.visitchildrenset(b'dir'), set()) - self.assertEqual(m.visitchildrenset(b'dir/subdir'), set()) + # OPT: These should probably be {'dir'}, {'subdir'} and 'this', + # respectively + self.assertEqual(m.visitchildrenset(b''), b'this') + self.assertEqual(m.visitchildrenset(b'dir'), b'this') + self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this') def testVisitdirGlob(self): m = matchmod.match( diff -r b0aaffcb6fcf -r ee1b648e4453 tests/test-sshserver.py --- a/tests/test-sshserver.py Wed Mar 13 12:02:06 2024 +0100 +++ b/tests/test-sshserver.py Wed Apr 17 12:28:48 2024 +0200 @@ -25,9 +25,8 @@ def assertparse(self, cmd, input, expected): server = mockserver(input) - proto = wireprotoserver.sshv1protocolhandler( - server._ui, server._fin, server._fout - ) + ui = server._ui + proto = wireprotoserver.sshv1protocolhandler(ui, ui.fin, ui.fout) _func, spec = wireprotov1server.commands[cmd] self.assertEqual(proto.getargs(spec), expected) @@ -35,6 +34,9 @@ def mockserver(inbytes): ui = mockui(inbytes) repo = mockrepo(ui) + # note: this test unfortunately doesn't really test anything about + # `sshserver` class anymore: the entirety of logic of that class lives + # in `serveuntil`, and that function is not even called by this test. return wireprotoserver.sshserver(ui, repo) diff -r b0aaffcb6fcf -r ee1b648e4453 tests/test-status.t --- a/tests/test-status.t Wed Mar 13 12:02:06 2024 +0100 +++ b/tests/test-status.t Wed Apr 17 12:28:48 2024 +0200 @@ -842,6 +842,26 @@ C clean C subdir/clean +Test various matchers interatction with dirstate code: + + $ hg status path:subdir + M subdir/modified + R subdir/removed + ! subdir/deleted + ? subdir/unknown + + $ hg status 'glob:subdir/*' + M subdir/modified + R subdir/removed + ! subdir/deleted + ? subdir/unknown + + $ hg status rootfilesin:subdir + M subdir/modified + R subdir/removed + ! subdir/deleted + ? subdir/unknown + Note: `hg status some-name` creates a patternmatcher which is not supported yet by the Rust implementation of status, but includematcher is supported. --include is used below for that reason