Show More
@@ -8,12 +8,15 b'' | |||
|
8 | 8 | //! A multiset of directory names. |
|
9 | 9 | //! |
|
10 | 10 | //! Used to counts the references to directories in a manifest or dirstate. |
|
11 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | |
|
12 | 11 | use crate::{ |
|
13 | dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, | |
|
14 | FastHashMap, | |
|
12 | dirstate::EntryState, | |
|
13 | utils::{ | |
|
14 | files, | |
|
15 | hg_path::{HgPath, HgPathBuf}, | |
|
16 | }, | |
|
17 | DirstateEntry, DirstateMapError, FastHashMap, | |
|
15 | 18 | }; |
|
16 |
use std::collections |
|
|
19 | use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet}; | |
|
17 | 20 | |
|
18 | 21 | // could be encapsulated if we care API stability more seriously |
|
19 | 22 | pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>; |
@@ -129,6 +132,68 b' impl DirsMultiset {' | |||
|
129 | 132 | } |
|
130 | 133 | } |
|
131 | 134 | |
|
135 | /// This is basically a reimplementation of `DirsMultiset` that stores the | |
|
136 | /// children instead of just a count of them, plus a small optional | |
|
137 | /// optimization to avoid some directories we don't need. | |
|
138 | #[derive(PartialEq, Debug)] | |
|
139 | pub struct DirsChildrenMultiset<'a> { | |
|
140 | inner: FastHashMap<&'a HgPath, HashSet<&'a HgPath>>, | |
|
141 | only_include: Option<HashSet<&'a HgPath>>, | |
|
142 | } | |
|
143 | ||
|
144 | impl<'a> DirsChildrenMultiset<'a> { | |
|
145 | pub fn new( | |
|
146 | paths: impl Iterator<Item = &'a HgPathBuf>, | |
|
147 | only_include: Option<&'a HashSet<impl AsRef<HgPath> + 'a>>, | |
|
148 | ) -> Self { | |
|
149 | let mut new = Self { | |
|
150 | inner: HashMap::default(), | |
|
151 | only_include: only_include | |
|
152 | .map(|s| s.iter().map(|p| p.as_ref()).collect()), | |
|
153 | }; | |
|
154 | ||
|
155 | for path in paths { | |
|
156 | new.add_path(path) | |
|
157 | } | |
|
158 | ||
|
159 | new | |
|
160 | } | |
|
161 | fn add_path(&mut self, path: &'a (impl AsRef<HgPath> + 'a)) { | |
|
162 | if path.as_ref().is_empty() { | |
|
163 | return; | |
|
164 | } | |
|
165 | for (directory, basename) in files::find_dirs_with_base(path.as_ref()) | |
|
166 | { | |
|
167 | if !self.is_dir_included(directory) { | |
|
168 | continue; | |
|
169 | } | |
|
170 | self.inner | |
|
171 | .entry(directory) | |
|
172 | .and_modify(|e| { | |
|
173 | e.insert(basename); | |
|
174 | }) | |
|
175 | .or_insert_with(|| { | |
|
176 | let mut set = HashSet::new(); | |
|
177 | set.insert(basename); | |
|
178 | set | |
|
179 | }); | |
|
180 | } | |
|
181 | } | |
|
182 | fn is_dir_included(&self, dir: impl AsRef<HgPath>) -> bool { | |
|
183 | match &self.only_include { | |
|
184 | None => false, | |
|
185 | Some(i) => i.contains(dir.as_ref()), | |
|
186 | } | |
|
187 | } | |
|
188 | ||
|
189 | pub fn get( | |
|
190 | &self, | |
|
191 | path: impl AsRef<HgPath>, | |
|
192 | ) -> Option<&HashSet<&'a HgPath>> { | |
|
193 | self.inner.get(path.as_ref()) | |
|
194 | } | |
|
195 | } | |
|
196 | ||
|
132 | 197 | #[cfg(test)] |
|
133 | 198 | mod tests { |
|
134 | 199 | use super::*; |
@@ -10,11 +10,11 b'' | |||
|
10 | 10 | //! Functions for fiddling with files. |
|
11 | 11 | |
|
12 | 12 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
13 | use std::iter::FusedIterator; | |
|
14 | 13 | |
|
15 | 14 | use crate::utils::replace_slice; |
|
16 | 15 | use lazy_static::lazy_static; |
|
17 | 16 | use std::fs::Metadata; |
|
17 | use std::iter::FusedIterator; | |
|
18 | 18 | use std::path::Path; |
|
19 | 19 | |
|
20 | 20 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { |
@@ -64,6 +64,28 b" impl<'a> Iterator for Ancestors<'a> {" | |||
|
64 | 64 | |
|
65 | 65 | impl<'a> FusedIterator for Ancestors<'a> {} |
|
66 | 66 | |
|
67 | /// An iterator over repository path yielding itself and its ancestors. | |
|
68 | #[derive(Copy, Clone, Debug)] | |
|
69 | pub(crate) struct AncestorsWithBase<'a> { | |
|
70 | next: Option<(&'a HgPath, &'a HgPath)>, | |
|
71 | } | |
|
72 | ||
|
73 | impl<'a> Iterator for AncestorsWithBase<'a> { | |
|
74 | type Item = (&'a HgPath, &'a HgPath); | |
|
75 | ||
|
76 | fn next(&mut self) -> Option<Self::Item> { | |
|
77 | let next = self.next; | |
|
78 | self.next = match self.next { | |
|
79 | Some((s, _)) if s.is_empty() => None, | |
|
80 | Some((s, _)) => Some(s.split_filename()), | |
|
81 | None => None, | |
|
82 | }; | |
|
83 | next | |
|
84 | } | |
|
85 | } | |
|
86 | ||
|
87 | impl<'a> FusedIterator for AncestorsWithBase<'a> {} | |
|
88 | ||
|
67 | 89 | /// Returns an iterator yielding ancestor directories of the given repository |
|
68 | 90 | /// path. |
|
69 | 91 | /// |
@@ -79,6 +101,25 b" pub fn find_dirs<'a>(path: &'a HgPath) -" | |||
|
79 | 101 | dirs |
|
80 | 102 | } |
|
81 | 103 | |
|
104 | /// Returns an iterator yielding ancestor directories of the given repository | |
|
105 | /// path. | |
|
106 | /// | |
|
107 | /// The path is separated by '/', and must not start with '/'. | |
|
108 | /// | |
|
109 | /// The path itself isn't included unless it is b"" (meaning the root | |
|
110 | /// directory.) | |
|
111 | pub(crate) fn find_dirs_with_base<'a>( | |
|
112 | path: &'a HgPath, | |
|
113 | ) -> AncestorsWithBase<'a> { | |
|
114 | let mut dirs = AncestorsWithBase { | |
|
115 | next: Some((path, HgPath::new(b""))), | |
|
116 | }; | |
|
117 | if !path.is_empty() { | |
|
118 | dirs.next(); // skip itself | |
|
119 | } | |
|
120 | dirs | |
|
121 | } | |
|
122 | ||
|
82 | 123 | /// TODO more than ASCII? |
|
83 | 124 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { |
|
84 | 125 | #[cfg(windows)] // NTFS compares via upper() |
@@ -170,4 +211,28 b' mod tests {' | |||
|
170 | 211 | assert_eq!(dirs.next(), None); |
|
171 | 212 | assert_eq!(dirs.next(), None); |
|
172 | 213 | } |
|
214 | ||
|
215 | #[test] | |
|
216 | fn test_find_dirs_with_base_some() { | |
|
217 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz")); | |
|
218 | assert_eq!( | |
|
219 | dirs.next(), | |
|
220 | Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz"))) | |
|
221 | ); | |
|
222 | assert_eq!( | |
|
223 | dirs.next(), | |
|
224 | Some((HgPath::new(b"foo"), HgPath::new(b"bar"))) | |
|
225 | ); | |
|
226 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo")))); | |
|
227 | assert_eq!(dirs.next(), None); | |
|
228 | assert_eq!(dirs.next(), None); | |
|
229 | } | |
|
230 | ||
|
231 | #[test] | |
|
232 | fn test_find_dirs_with_base_empty() { | |
|
233 | let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); | |
|
234 | assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); | |
|
235 | assert_eq!(dirs.next(), None); | |
|
236 | assert_eq!(dirs.next(), None); | |
|
237 | } | |
|
173 | 238 | } |
@@ -183,6 +183,29 b' impl HgPath {' | |||
|
183 | 183 | &self.inner[..] |
|
184 | 184 | }) |
|
185 | 185 | } |
|
186 | /// Returns a tuple of slices `(base, filename)` resulting from the split | |
|
187 | /// at the rightmost `/`, if any. | |
|
188 | /// | |
|
189 | /// # Examples: | |
|
190 | /// | |
|
191 | /// ``` | |
|
192 | /// use hg::utils::hg_path::HgPath; | |
|
193 | /// | |
|
194 | /// let path = HgPath::new(b"cool/hg/path").split_filename(); | |
|
195 | /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path"))); | |
|
196 | /// | |
|
197 | /// let path = HgPath::new(b"pathwithoutsep").split_filename(); | |
|
198 | /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep"))); | |
|
199 | /// ``` | |
|
200 | pub fn split_filename(&self) -> (&Self, &Self) { | |
|
201 | match &self.inner.iter().rposition(|c| *c == b'/') { | |
|
202 | None => (HgPath::new(""), &self), | |
|
203 | Some(size) => ( | |
|
204 | HgPath::new(&self.inner[..*size]), | |
|
205 | HgPath::new(&self.inner[*size + 1..]), | |
|
206 | ), | |
|
207 | } | |
|
208 | } | |
|
186 | 209 | pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf { |
|
187 | 210 | let mut inner = self.inner.to_owned(); |
|
188 | 211 | if inner.len() != 0 && inner.last() != Some(&b'/') { |
General Comments 0
You need to be logged in to leave comments.
Login now