Skip to content
Snippets Groups Projects
Commit 831f85fc authored by Georges Racinet's avatar Georges Racinet
Browse files

rhgitaly::mercurial: an iterator over a directory of a manifest

This will be useful to implement the various cases of `GetTreeEntries`.
It could also be extended to be used in `ls_path`, but that would
require treating the special case where the given path is actually a file.

Consider sending this UPSTREAM.
parent 13486a81
No related branches found
No related tags found
2 merge requests!189Merged stable branch into default and bumped VERSION,!188RHGitaly CommitService.GetTreeEntries implementation
......@@ -256,6 +256,96 @@
})
}
/// An iterator over manifest, yielding only entries from a given sub directory
///
/// This could be upstreamed to hg-core, where it would benefit the (private) binary search
/// method to avoid linear scanning for the directory starting point.
struct ManifestDirIterator<'a, 'm, IM>
where
IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>,
{
/// Path of the directory, without trailing slash
path: &'a [u8],
in_dir: bool,
manifest_iter: IM,
}
impl<'a, 'm, IM> ManifestDirIterator<'a, 'm, IM>
where
IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>,
{
#[allow(dead_code)]
fn new(manifest_iter: IM, path: &'a [u8]) -> Self {
ManifestDirIterator {
path,
manifest_iter,
in_dir: false,
}
}
/// Return the length of the string prefix corresponding to path, hence with trailing slash.
///
/// This is meant so that, if `self.path` is `"foo"`, then `"foo/bar"[self.path_len()..]` is
/// `"bar"`. It also handles the case where `self.path` is empty correctly, sparing the callers
/// to check for this special case.
#[allow(dead_code)]
fn prefix_len(&self) -> usize {
let pl = self.path.len();
if pl == 0 {
0
} else {
pl + 1
}
}
/// Derive from `self.path` a string prefix ready for concatenation with a relative path.
#[allow(dead_code)]
fn prefix(&self) -> Vec<u8> {
let mut v = self.path.to_vec();
if !v.is_empty() {
v.push(b'/');
}
v
}
fn is_path_inside(&self, other_path: &[u8]) -> bool {
let pl = self.path.len();
pl == 0
|| (other_path.len() > pl
&& other_path[pl] == b'/'
&& other_path.starts_with(self.path))
}
}
impl<'a, 'm, IM> Iterator for ManifestDirIterator<'a, 'm, IM>
where
IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>,
{
type Item = Result<ManifestEntry<'m>, HgError>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(entry_res) = self.manifest_iter.next() {
match entry_res {
Err(e) => {
return Some(Err(e));
}
Ok(entry) => {
if !self.is_path_inside(entry.path.as_bytes()) {
if self.in_dir {
// we're leaving the directory to list, job done
return None;
} else {
// we have not entered the directory to list yet
continue;
}
}
return Some(Ok(entry));
}
}
}
None
}
}
#[cfg(test)]
mod tests {
......@@ -385,4 +475,40 @@
])
)
}
#[test]
fn test_manifest_dir_iterator() {
let manifest = paths_manifest(vec!["foo/a", "foo/sub/a", "top-file"]);
let iter = ManifestDirIterator::new(manifest.into_iter(), b"");
let res: Vec<&[u8]> = iter.map(|r| r.unwrap().path.as_bytes()).collect();
assert_eq!(
res,
vec![
b"foo/a".as_ref(),
b"foo/sub/a".as_ref(),
b"top-file".as_ref(),
]
);
let manifest = paths_manifest(vec![
"foo.",
"foo/a",
"foo/sub/a",
"foo/sub/b",
"foo/subb",
"foo0",
"other/file",
]);
let iter = ManifestDirIterator::new(manifest.into_iter(), b"foo");
let res: Vec<&[u8]> = iter.map(|r| r.unwrap().path.as_bytes()).collect();
assert_eq!(
res,
vec![
b"foo/a".as_ref(),
b"foo/sub/a".as_ref(),
b"foo/sub/b".as_ref(),
b"foo/subb".as_ref(),
]
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment