|
4 | 4 | //! fingerprints of file system state after task execution. |
5 | 5 |
|
6 | 6 | use std::{ |
| 7 | + collections::BTreeMap, |
7 | 8 | fs::File, |
8 | 9 | hash::Hasher as _, |
9 | 10 | io::{self, BufRead, Read}, |
@@ -38,8 +39,8 @@ pub enum PathFingerprint { |
38 | 39 | /// Directory with optional entry listing. |
39 | 40 | /// `Folder(None)` means the directory was opened but entries were not read |
40 | 41 | /// (e.g., for `openat` calls). |
41 | | - /// `Folder(Some(_))` contains the directory entries. |
42 | | - Folder(Option<HashMap<Str, DirEntryKind>>), |
| 42 | + /// `Folder(Some(_))` contains the directory entries sorted by name. |
| 43 | + Folder(Option<BTreeMap<Str, DirEntryKind>>), |
43 | 44 | } |
44 | 45 |
|
45 | 46 | /// Kind of directory entry |
@@ -141,27 +142,38 @@ fn determine_change_kind<'a>( |
141 | 142 | } |
142 | 143 |
|
143 | 144 | /// Determine whether a folder change is an addition or removal by comparing entries. |
| 145 | +/// Both maps are `BTreeMap` so we iterate them in sorted lockstep. |
144 | 146 | /// Returns the specific entry name that was added or removed, if identifiable. |
145 | 147 | fn determine_folder_change_kind<'a>( |
146 | | - old: Option<&'a HashMap<Str, DirEntryKind>>, |
147 | | - new: Option<&'a HashMap<Str, DirEntryKind>>, |
| 148 | + old: Option<&'a BTreeMap<Str, DirEntryKind>>, |
| 149 | + new: Option<&'a BTreeMap<Str, DirEntryKind>>, |
148 | 150 | ) -> (InputChangeKind, Option<&'a Str>) { |
149 | | - match (old, new) { |
150 | | - (Some(old_entries), Some(new_entries)) => { |
151 | | - for key in old_entries.keys() { |
152 | | - if !new_entries.contains_key(key) { |
153 | | - return (InputChangeKind::Removed, Some(key)); |
154 | | - } |
155 | | - } |
156 | | - for key in new_entries.keys() { |
157 | | - if !old_entries.contains_key(key) { |
158 | | - return (InputChangeKind::Added, Some(key)); |
| 151 | + let (Some(old_entries), Some(new_entries)) = (old, new) else { |
| 152 | + return (InputChangeKind::Added, None); |
| 153 | + }; |
| 154 | + |
| 155 | + let mut old_iter = old_entries.iter(); |
| 156 | + let mut new_iter = new_entries.iter(); |
| 157 | + let mut o = old_iter.next(); |
| 158 | + let mut n = new_iter.next(); |
| 159 | + |
| 160 | + loop { |
| 161 | + match (o, n) { |
| 162 | + (None, None) => return (InputChangeKind::Added, None), |
| 163 | + (Some((name, _)), None) => return (InputChangeKind::Removed, Some(name)), |
| 164 | + (None, Some((name, _))) => return (InputChangeKind::Added, Some(name)), |
| 165 | + (Some((ok, ov)), Some((nk, nv))) => match ok.cmp(nk) { |
| 166 | + std::cmp::Ordering::Equal => { |
| 167 | + if ov != nv { |
| 168 | + return (InputChangeKind::Added, Some(ok)); |
| 169 | + } |
| 170 | + o = old_iter.next(); |
| 171 | + n = new_iter.next(); |
159 | 172 | } |
160 | | - } |
161 | | - // Same keys but different entry kinds — default to Added |
162 | | - (InputChangeKind::Added, None) |
| 173 | + std::cmp::Ordering::Less => return (InputChangeKind::Removed, Some(ok)), |
| 174 | + std::cmp::Ordering::Greater => return (InputChangeKind::Added, Some(nk)), |
| 175 | + }, |
163 | 176 | } |
164 | | - _ => (InputChangeKind::Added, None), |
165 | 177 | } |
166 | 178 | } |
167 | 179 |
|
@@ -248,7 +260,7 @@ fn process_directory( |
248 | 260 | return Ok(PathFingerprint::Folder(None)); |
249 | 261 | } |
250 | 262 |
|
251 | | - let mut entries = HashMap::new(); |
| 263 | + let mut entries = BTreeMap::new(); |
252 | 264 | for entry in std::fs::read_dir(path)? { |
253 | 265 | let entry = entry?; |
254 | 266 | let name = entry.file_name(); |
@@ -286,7 +298,7 @@ fn process_directory_unix(file: &File, path_read: PathRead) -> anyhow::Result<Pa |
286 | 298 | let fd = file.as_fd(); |
287 | 299 | let mut dir = nix::dir::Dir::from_fd(fd.try_clone_to_owned()?)?; |
288 | 300 |
|
289 | | - let mut entries = HashMap::new(); |
| 301 | + let mut entries = BTreeMap::new(); |
290 | 302 | for entry in dir.iter() { |
291 | 303 | let entry = entry?; |
292 | 304 | let name = entry.file_name().to_bytes(); |
|
0 commit comments