diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -941,6 +941,7 @@ ), (b'', b'dates', True, _(b'display the saved mtime')), (b'', b'datesort', None, _(b'sort by saved mtime')), + (b'', b'dirs', False, _(b'display directories')), ], _(b'[OPTION]...'), ) @@ -956,7 +957,11 @@ keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename else: keyfunc = None # sort by filename - for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc): + entries = list(pycompat.iteritems(repo.dirstate)) + if opts['dirs']: + entries.extend(repo.dirstate.directories()) + entries.sort(key=keyfunc) + for file_, ent in entries: if ent[3] == -1: timestr = b'unset ' elif nodates: diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -315,6 +315,9 @@ iteritems = items + def directories(self): + return self._map.directories() + def parents(self): return [self._validate(p) for p in self._pl] @@ -1479,6 +1482,10 @@ self._map return self.copymap + def directories(self): + # Rust / dirstate-v2 only + return [] + def clear(self): self._map.clear() self.copymap.clear() @@ -1806,6 +1813,9 @@ def copymap(self): return self._rustmap.copymap() + def directories(self): + return self._rustmap.directories() + def preload(self): self._rustmap diff --git a/rust/hg-core/src/dirstate/parsers.rs b/rust/hg-core/src/dirstate/parsers.rs --- a/rust/hg-core/src/dirstate/parsers.rs +++ b/rust/hg-core/src/dirstate/parsers.rs @@ -129,7 +129,7 @@ } /// Seconds since the Unix epoch -pub struct Timestamp(pub u64); +pub struct Timestamp(pub i64); impl DirstateEntry { pub fn mtime_is_ambiguous(&self, now: i32) -> bool { diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -319,7 +319,7 @@ pub(super) fn cached_directory_mtime( &self, - ) -> Option<&on_disk::Timestamp> { + ) -> Option<&'tree on_disk::Timestamp> { match self { NodeRef::InMemory(_path, node) => match &node.data { NodeData::CachedDirectory { mtime } => Some(mtime), @@ -1068,4 +1068,28 @@ }) })) } + + fn iter_directories( + &self, + ) -> Box< + dyn Iterator< + Item = Result< + (&HgPath, Option), + DirstateV2ParseError, + >, + > + Send + + '_, + > { + Box::new(filter_map_results(self.iter_nodes(), move |node| { + Ok(if node.state()?.is_none() { + Some(( + node.full_path(self.on_disk)?, + node.cached_directory_mtime() + .map(|mtime| Timestamp(mtime.seconds())), + )) + } else { + None + }) + })) + } } diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -143,6 +143,18 @@ ) -> Result, DirstateV2ParseError>; fn iter(&self) -> StateMapIter<'_>; + + fn iter_directories( + &self, + ) -> Box< + dyn Iterator< + Item = Result< + (&HgPath, Option), + DirstateV2ParseError, + >, + > + Send + + '_, + >; } impl DirstateMapMethods for DirstateMap { @@ -350,4 +362,18 @@ fn iter(&self) -> StateMapIter<'_> { Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value)))) } + + fn iter_directories( + &self, + ) -> Box< + dyn Iterator< + Item = Result< + (&HgPath, Option), + DirstateV2ParseError, + >, + > + Send + + '_, + > { + Box::new(std::iter::empty()) + } } diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -352,6 +352,12 @@ } } +impl Timestamp { + pub fn seconds(&self) -> i64 { + self.seconds.get() + } +} + impl From for Timestamp { fn from(system_time: SystemTime) -> Self { let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) { diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -535,6 +535,18 @@ ) } + def directories(&self) -> PyResult { + let dirs = PyList::new(py, &[]); + for item in self.inner(py).borrow().iter_directories() { + let (path, mtime) = item.map_err(|e| v2_error(py, e))?; + let path = PyBytes::new(py, path.as_bytes()); + let mtime = mtime.map(|t| t.0).unwrap_or(-1); + let tuple = (path, (b'd', 0, 0, mtime)); + dirs.append(py, tuple.to_py_object(py).into_object()) + } + Ok(dirs) + } + }); impl DirstateMap { diff --git a/rust/hg-cpython/src/dirstate/dispatch.rs b/rust/hg-cpython/src/dirstate/dispatch.rs --- a/rust/hg-cpython/src/dirstate/dispatch.rs +++ b/rust/hg-cpython/src/dirstate/dispatch.rs @@ -206,4 +206,18 @@ fn iter(&self) -> StateMapIter<'_> { self.get().iter() } + + fn iter_directories( + &self, + ) -> Box< + dyn Iterator< + Item = Result< + (&HgPath, Option), + DirstateV2ParseError, + >, + > + Send + + '_, + > { + self.get().iter_directories() + } } diff --git a/rust/hg-cpython/src/parsers.rs b/rust/hg-cpython/src/parsers.rs --- a/rust/hg-cpython/src/parsers.rs +++ b/rust/hg-cpython/src/parsers.rs @@ -98,7 +98,7 @@ p1: p1.try_into().unwrap(), p2: p2.try_into().unwrap(), }, - Timestamp(now.as_object().extract::(py)?), + Timestamp(now.as_object().extract::(py)?), ) { Ok(packed) => { for (filename, entry) in dirstate_map.iter() { diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -282,7 +282,7 @@ debugdata: changelog, manifest, dir debugdate: extended debugdeltachain: changelog, manifest, dir, template - debugdirstate: nodates, dates, datesort + debugdirstate: nodates, dates, datesort, dirs debugdiscovery: old, nonheads, rev, seed, local-as-revs, remote-as-revs, ssh, remotecmd, insecure, template debugdownload: output debugextensions: template diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -915,3 +915,46 @@ I A.hs I B.hs I ignored-folder/ctest.hs + +#if dirstate-v2 + +Check read_dir caching + + $ cd .. + $ hg init repo8 + $ cd repo8 + $ mkdir subdir + $ touch subdir/a subdir/b + $ hg ci -Aqm '#0' + +The cached mtime is initially unset + + $ hg debugdirstate --dirs --no-dates | grep '^d' + d 0 0 unset subdir + +It is still not set when there are unknown files + + $ touch subdir/unknown + $ hg status + ? subdir/unknown + $ hg debugdirstate --dirs --no-dates | grep '^d' + d 0 0 unset subdir + +Now the directory is eligible for caching, so its mtime is save in the dirstate + + $ rm subdir/unknown + $ hg status + $ hg debugdirstate --dirs --no-dates | grep '^d' + d 0 0 set subdir + +This time the command should be ever so slightly faster since it does not need `read_dir("subdir")` + + $ hg status + +Creating a new file changes the directory’s mtime, invalidating the cache + + $ touch subdir/unknown + $ hg status + ? subdir/unknown + +#endif