Show More
@@ -8,12 +8,15 b'' | |||||
8 | //! A multiset of directory names. |
|
8 | //! A multiset of directory names. | |
9 | //! |
|
9 | //! | |
10 | //! Used to counts the references to directories in a manifest or dirstate. |
|
10 | //! Used to counts the references to directories in a manifest or dirstate. | |
11 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
|||
12 | use crate::{ |
|
11 | use crate::{ | |
13 | dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, |
|
12 | dirstate::EntryState, | |
14 | FastHashMap, |
|
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 | // could be encapsulated if we care API stability more seriously |
|
21 | // could be encapsulated if we care API stability more seriously | |
19 | pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>; |
|
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 | #[cfg(test)] |
|
197 | #[cfg(test)] | |
133 | mod tests { |
|
198 | mod tests { | |
134 | use super::*; |
|
199 | use super::*; |
@@ -10,11 +10,11 b'' | |||||
10 | //! Functions for fiddling with files. |
|
10 | //! Functions for fiddling with files. | |
11 |
|
11 | |||
12 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
12 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | |
13 | use std::iter::FusedIterator; |
|
|||
14 |
|
13 | |||
15 | use crate::utils::replace_slice; |
|
14 | use crate::utils::replace_slice; | |
16 | use lazy_static::lazy_static; |
|
15 | use lazy_static::lazy_static; | |
17 | use std::fs::Metadata; |
|
16 | use std::fs::Metadata; | |
|
17 | use std::iter::FusedIterator; | |||
18 | use std::path::Path; |
|
18 | use std::path::Path; | |
19 |
|
19 | |||
20 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { |
|
20 | pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { | |
@@ -64,6 +64,28 b" impl<'a> Iterator for Ancestors<'a> {" | |||||
64 |
|
64 | |||
65 | impl<'a> FusedIterator for Ancestors<'a> {} |
|
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 | /// Returns an iterator yielding ancestor directories of the given repository |
|
89 | /// Returns an iterator yielding ancestor directories of the given repository | |
68 | /// path. |
|
90 | /// path. | |
69 | /// |
|
91 | /// | |
@@ -79,6 +101,25 b" pub fn find_dirs<'a>(path: &'a HgPath) -" | |||||
79 | dirs |
|
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 | /// TODO more than ASCII? |
|
123 | /// TODO more than ASCII? | |
83 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { |
|
124 | pub fn normalize_case(path: &HgPath) -> HgPathBuf { | |
84 | #[cfg(windows)] // NTFS compares via upper() |
|
125 | #[cfg(windows)] // NTFS compares via upper() | |
@@ -170,4 +211,28 b' mod tests {' | |||||
170 | assert_eq!(dirs.next(), None); |
|
211 | assert_eq!(dirs.next(), None); | |
171 | assert_eq!(dirs.next(), None); |
|
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 | &self.inner[..] |
|
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 | pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf { |
|
209 | pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf { | |
187 | let mut inner = self.inner.to_owned(); |
|
210 | let mut inner = self.inner.to_owned(); | |
188 | if inner.len() != 0 && inner.last() != Some(&b'/') { |
|
211 | if inner.len() != 0 && inner.last() != Some(&b'/') { |
General Comments 0
You need to be logged in to leave comments.
Login now