1use std::{cmp::Ordering, ops::Range};
2
3use bstr::{BStr, ByteSlice, ByteVec};
4use filetime::FileTime;
5
6use crate::{
7 entry,
8 entry::{Stage, StageRaw},
9 extension, AccelerateLookup, Entry, PathStorage, PathStorageRef, State, Version,
10};
11
12#[allow(dead_code)]
14mod sparse;
15
16impl State {
18 pub fn version(&self) -> Version {
20 self.version
21 }
22
23 pub fn timestamp(&self) -> FileTime {
25 self.timestamp
26 }
27
28 pub fn set_timestamp(&mut self, timestamp: FileTime) {
34 self.timestamp = timestamp;
35 }
36
37 pub fn object_hash(&self) -> gix_hash::Kind {
39 self.object_hash
40 }
41
42 pub fn entries(&self) -> &[Entry] {
44 &self.entries
45 }
46 pub fn path_backing(&self) -> &PathStorageRef {
48 &self.path_backing
49 }
50
51 pub fn entries_with_paths_by_filter_map<'a, T>(
53 &'a self,
54 mut filter_map: impl FnMut(&'a BStr, &Entry) -> Option<T> + 'a,
55 ) -> impl Iterator<Item = (&'a BStr, T)> + 'a {
56 self.entries.iter().filter_map(move |e| {
57 let p = e.path(self);
58 filter_map(p, e).map(|t| (p, t))
59 })
60 }
61 pub fn entries_mut_with_paths_in<'state, 'backing>(
63 &'state mut self,
64 backing: &'backing PathStorageRef,
65 ) -> impl Iterator<Item = (&'state mut Entry, &'backing BStr)> {
66 self.entries.iter_mut().map(move |e| {
67 let path = backing[e.path.clone()].as_bstr();
68 (e, path)
69 })
70 }
71
72 pub fn entry_index_by_path_and_stage(&self, path: &BStr, stage: entry::Stage) -> Option<usize> {
77 let mut stage_cmp = Ordering::Equal;
78 let idx = self
79 .entries
80 .binary_search_by(|e| {
81 let res = e.path(self).cmp(path);
82 if res.is_eq() {
83 stage_cmp = e.stage().cmp(&stage);
84 }
85 res
86 })
87 .ok()?;
88 self.entry_index_by_idx_and_stage(path, idx, stage as StageRaw, stage_cmp)
89 }
90
91 fn walk_entry_stages(&self, path: &BStr, base: usize, direction: Ordering) -> Option<usize> {
95 match direction {
96 Ordering::Greater => self
97 .entries
98 .get(base + 1..)?
99 .iter()
100 .enumerate()
101 .take_while(|(_, e)| e.path(self) == path)
102 .last()
103 .map(|(idx, _)| base + 1 + idx),
104 Ordering::Equal => Some(base),
105 Ordering::Less => self.entries[..base]
106 .iter()
107 .enumerate()
108 .rev()
109 .take_while(|(_, e)| e.path(self) == path)
110 .last()
111 .map(|(idx, _)| idx),
112 }
113 }
114
115 fn entry_index_by_idx_and_stage(
116 &self,
117 path: &BStr,
118 idx: usize,
119 wanted_stage: entry::StageRaw,
120 stage_cmp: Ordering,
121 ) -> Option<usize> {
122 match stage_cmp {
123 Ordering::Greater => self.entries[..idx]
124 .iter()
125 .enumerate()
126 .rev()
127 .take_while(|(_, e)| e.path(self) == path)
128 .find_map(|(idx, e)| (e.stage_raw() == wanted_stage).then_some(idx)),
129 Ordering::Equal => Some(idx),
130 Ordering::Less => self
131 .entries
132 .get(idx + 1..)?
133 .iter()
134 .enumerate()
135 .take_while(|(_, e)| e.path(self) == path)
136 .find_map(|(ofs, e)| (e.stage_raw() == wanted_stage).then_some(idx + ofs + 1)),
137 }
138 }
139
140 pub fn prepare_icase_backing(&self) -> AccelerateLookup<'_> {
145 let _span = gix_features::trace::detail!("prepare_icase_backing", entries = self.entries.len());
146 let mut out = AccelerateLookup::with_capacity(self.entries.len());
147 for entry in &self.entries {
148 let entry_path = entry.path(self);
149 let hash = AccelerateLookup::icase_hash(entry_path);
150 out.icase_entries
151 .insert_unique(hash, entry, |e| AccelerateLookup::icase_hash(e.path(self)));
152 if entry_is_dir(entry) {
153 out.icase_dirs.insert_unique(
154 hash,
155 crate::DirEntry {
156 entry,
157 dir_end: entry.path.end,
158 },
159 |dir| AccelerateLookup::icase_hash(dir.path(self)),
160 );
161 }
162
163 let mut last_pos = entry_path.len();
164 while let Some(slash_idx) = entry_path[..last_pos].rfind_byte(b'/') {
165 let dir = entry_path[..slash_idx].as_bstr();
166 last_pos = slash_idx;
167 let dir_range = entry.path.start..(entry.path.start + dir.len());
168
169 let hash = AccelerateLookup::icase_hash(dir);
170 if out
171 .icase_dirs
172 .find(hash, |dir| {
173 dir.path(self) == self.path_backing[dir_range.clone()].as_bstr()
174 })
175 .is_none()
176 {
177 out.icase_dirs.insert_unique(
178 hash,
179 crate::DirEntry {
180 entry,
181 dir_end: dir_range.end,
182 },
183 |dir| AccelerateLookup::icase_hash(dir.path(self)),
184 );
185 } else {
186 break;
187 }
188 }
189 }
190 gix_features::trace::debug!(directories = out.icase_dirs.len(), "stored directories");
191 out
192 }
193
194 pub fn entry_by_path_icase<'a>(
199 &'a self,
200 path: &BStr,
201 ignore_case: bool,
202 lookup: &AccelerateLookup<'a>,
203 ) -> Option<&'a Entry> {
204 lookup
205 .icase_entries
206 .find(AccelerateLookup::icase_hash(path), |e| {
207 let entry_path = e.path(self);
208 if entry_path == path {
209 return true;
210 }
211 if !ignore_case {
212 return false;
213 }
214 entry_path.eq_ignore_ascii_case(path)
215 })
216 .copied()
217 }
218
219 pub fn entry_closest_to_directory_or_directory_icase<'a>(
224 &'a self,
225 directory: &BStr,
226 ignore_case: bool,
227 lookup: &AccelerateLookup<'a>,
228 ) -> Option<&'a Entry> {
229 lookup
230 .icase_dirs
231 .find(AccelerateLookup::icase_hash(directory), |dir| {
232 let dir_path = dir.path(self);
233 if dir_path == directory {
234 return true;
235 }
236 if !ignore_case {
237 return false;
238 }
239 dir_path.eq_ignore_ascii_case(directory)
240 })
241 .map(|dir| dir.entry)
242 }
243
244 pub fn entry_closest_to_directory_or_directory(&self, directory: &BStr) -> Option<&Entry> {
249 let idx = match self.entry_index_by_path(directory) {
250 Ok(idx) => {
251 let entry = &self.entries[idx];
252 return entry_is_dir(entry).then_some(entry);
253 }
254 Err(closest_idx) => closest_idx,
255 };
256 for entry in &self.entries[idx..] {
257 let path = entry.path(self);
258 if path.get(..directory.len())? != directory {
259 break;
260 }
261 let dir_char = path.get(directory.len())?;
262 if *dir_char > b'/' {
263 break;
264 }
265 if *dir_char < b'/' {
266 continue;
267 }
268 return Some(entry);
269 }
270 None
271 }
272
273 pub fn path_is_directory(&self, path: &BStr) -> bool {
283 self.entry_closest_to_directory_or_directory(path).is_some()
284 }
285
286 pub fn path_is_directory_icase<'a>(
298 &'a self,
299 path: &BStr,
300 ignore_case: bool,
301 lookup: &AccelerateLookup<'a>,
302 ) -> bool {
303 self.entry_closest_to_directory_or_directory_icase(path, ignore_case, lookup)
304 .is_some()
305 }
306
307 pub fn entry_index_by_path_and_stage_bounded(
316 &self,
317 path: &BStr,
318 stage: entry::Stage,
319 upper_bound: usize,
320 ) -> Option<usize> {
321 self.entries[..upper_bound]
322 .binary_search_by(|e| e.path(self).cmp(path).then_with(|| e.stage().cmp(&stage)))
323 .ok()
324 }
325
326 pub fn entry_by_path_and_stage(&self, path: &BStr, stage: entry::Stage) -> Option<&Entry> {
329 self.entry_index_by_path_and_stage(path, stage)
330 .map(|idx| &self.entries[idx])
331 }
332
333 pub fn entry_by_path(&self, path: &BStr) -> Option<&Entry> {
337 let mut stage_at_index = 0;
338 let idx = self
339 .entries
340 .binary_search_by(|e| {
341 let res = e.path(self).cmp(path);
342 if res.is_eq() {
343 stage_at_index = e.stage_raw();
344 }
345 res
346 })
347 .ok()?;
348 let idx = if stage_at_index == 0 || stage_at_index == 2 {
349 idx
350 } else {
351 self.entry_index_by_idx_and_stage(path, idx, Stage::Ours as StageRaw, stage_at_index.cmp(&2))?
352 };
353 Some(&self.entries[idx])
354 }
355
356 pub fn entry_index_by_path(&self, path: &BStr) -> Result<usize, usize> {
359 self.entries.binary_search_by(|e| e.path(self).cmp(path))
360 }
361
362 pub fn prefixed_entries(&self, prefix: &BStr) -> Option<&[Entry]> {
366 self.prefixed_entries_range(prefix).map(|range| &self.entries[range])
367 }
368
369 pub fn prefixed_entries_range(&self, prefix: &BStr) -> Option<Range<usize>> {
373 if prefix.is_empty() {
374 return Some(0..self.entries.len());
375 }
376 let prefix_len = prefix.len();
377 let mut low = self.entries.partition_point(|e| {
378 e.path(self)
379 .get(..prefix_len)
380 .map_or_else(|| e.path(self) <= &prefix[..e.path.len()], |p| p < prefix)
381 });
382 let mut high =
383 low + self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).is_some_and(|p| p <= prefix));
384
385 let low_entry = &self.entries.get(low)?;
386 if low_entry.stage_raw() != 0 {
387 low = self
388 .walk_entry_stages(low_entry.path(self), low, Ordering::Less)
389 .unwrap_or(low);
390 }
391 if let Some(high_entry) = self.entries.get(high) {
392 if high_entry.stage_raw() != 0 {
393 high = self
394 .walk_entry_stages(high_entry.path(self), high, Ordering::Less)
395 .unwrap_or(high);
396 }
397 }
398 (low != high).then_some(low..high)
399 }
400
401 pub fn entry(&self, idx: usize) -> &Entry {
405 &self.entries[idx]
406 }
407
408 pub fn is_sparse(&self) -> bool {
412 self.is_sparse
413 }
414
415 pub fn entry_range(&self, path: &BStr) -> Option<Range<usize>> {
420 let mut stage_at_index = 0;
421 let idx = self
422 .entries
423 .binary_search_by(|e| {
424 let res = e.path(self).cmp(path);
425 if res.is_eq() {
426 stage_at_index = e.stage_raw();
427 }
428 res
429 })
430 .ok()?;
431
432 let (start, end) = (
433 self.walk_entry_stages(path, idx, Ordering::Less).unwrap_or(idx),
434 self.walk_entry_stages(path, idx, Ordering::Greater).unwrap_or(idx) + 1,
435 );
436 Some(start..end)
437 }
438}
439
440impl AccelerateLookup<'_> {
441 fn with_capacity(cap: usize) -> Self {
442 let ratio_of_entries_to_dirs_in_webkit = 20; Self {
444 icase_entries: hashbrown::HashTable::with_capacity(cap),
445 icase_dirs: hashbrown::HashTable::with_capacity(cap / ratio_of_entries_to_dirs_in_webkit),
446 }
447 }
448 fn icase_hash(data: &BStr) -> u64 {
449 use std::hash::Hasher;
450 let mut hasher = fnv::FnvHasher::default();
451 for b in data.as_bytes() {
452 hasher.write_u8(b.to_ascii_lowercase());
453 }
454 hasher.finish()
455 }
456}
457
458impl State {
460 pub fn return_path_backing(&mut self, backing: PathStorage) {
463 debug_assert!(
464 self.path_backing.is_empty(),
465 "BUG: return path backing only after taking it, once"
466 );
467 self.path_backing = backing;
468 }
469
470 pub fn entries_mut(&mut self) -> &mut [Entry] {
472 &mut self.entries
473 }
474
475 pub fn entries_mut_and_pathbacking(&mut self) -> (&mut [Entry], &PathStorageRef) {
477 (&mut self.entries, &self.path_backing)
478 }
479
480 pub fn entries_mut_with_paths(&mut self) -> impl Iterator<Item = (&mut Entry, &BStr)> {
482 let paths = &self.path_backing;
483 self.entries.iter_mut().map(move |e| {
484 let path = paths[e.path.clone()].as_bstr();
485 (e, path)
486 })
487 }
488
489 pub fn into_entries(self) -> (Vec<Entry>, PathStorage) {
493 (self.entries, self.path_backing)
494 }
495
496 pub fn take_path_backing(&mut self) -> PathStorage {
499 assert_eq!(
500 self.entries.is_empty(),
501 self.path_backing.is_empty(),
502 "BUG: cannot take out backing multiple times"
503 );
504 std::mem::take(&mut self.path_backing)
505 }
506
507 pub fn entry_mut_by_path_and_stage(&mut self, path: &BStr, stage: entry::Stage) -> Option<&mut Entry> {
510 self.entry_index_by_path_and_stage(path, stage)
511 .map(|idx| &mut self.entries[idx])
512 }
513
514 pub fn dangerously_push_entry(
525 &mut self,
526 stat: entry::Stat,
527 id: gix_hash::ObjectId,
528 flags: entry::Flags,
529 mode: entry::Mode,
530 path: &BStr,
531 ) {
532 let path = {
533 let path_start = self.path_backing.len();
534 self.path_backing.push_str(path);
535 path_start..self.path_backing.len()
536 };
537
538 self.entries.push(Entry {
539 stat,
540 id,
541 flags,
542 mode,
543 path,
544 });
545 }
546
547 pub fn sort_entries(&mut self) {
549 let path_backing = &self.path_backing;
550 self.entries.sort_by(|a, b| {
551 Entry::cmp_filepaths(a.path_in(path_backing), b.path_in(path_backing))
552 .then_with(|| a.stage().cmp(&b.stage()))
553 });
554 }
555
556 pub fn sort_entries_by(&mut self, mut compare: impl FnMut(&Entry, &Entry) -> Ordering) {
559 let path_backing = &self.path_backing;
560 self.entries.sort_by(|a, b| {
561 Entry::cmp_filepaths(a.path_in(path_backing), b.path_in(path_backing))
562 .then_with(|| a.stage().cmp(&b.stage()))
563 .then_with(|| compare(a, b))
564 });
565 }
566
567 pub fn remove_entries(&mut self, mut should_remove: impl FnMut(usize, &BStr, &mut Entry) -> bool) {
577 let mut index = 0;
578 let paths = &self.path_backing;
579 self.entries.retain_mut(|e| {
580 let path = e.path_in(paths);
581 let res = !should_remove(index, path, e);
582 index += 1;
583 res
584 });
585 }
586
587 pub fn remove_entry_at_index(&mut self, index: usize) -> Entry {
594 self.entries.remove(index)
595 }
596}
597
598impl State {
600 pub fn tree(&self) -> Option<&extension::Tree> {
602 self.tree.as_ref()
603 }
604 pub fn remove_tree(&mut self) -> Option<extension::Tree> {
606 self.tree.take()
607 }
608 pub fn link(&self) -> Option<&extension::Link> {
610 self.link.as_ref()
611 }
612 pub fn resolve_undo(&self) -> Option<&extension::resolve_undo::Paths> {
614 self.resolve_undo.as_ref()
615 }
616 pub fn remove_resolve_undo(&mut self) -> Option<extension::resolve_undo::Paths> {
618 self.resolve_undo.take()
619 }
620 pub fn untracked(&self) -> Option<&extension::UntrackedCache> {
622 self.untracked.as_ref()
623 }
624 pub fn fs_monitor(&self) -> Option<&extension::FsMonitor> {
626 self.fs_monitor.as_ref()
627 }
628 pub fn had_end_of_index_marker(&self) -> bool {
630 self.end_of_index_at_decode_time
631 }
632 pub fn had_offset_table(&self) -> bool {
634 self.offset_table_at_decode_time
635 }
636}
637
638fn entry_is_dir(entry: &Entry) -> bool {
639 entry.mode.is_sparse() || entry.mode.is_submodule()
640}
641
642#[cfg(test)]
643mod tests {
644 use std::path::{Path, PathBuf};
645
646 #[test]
647 fn entry_by_path_with_conflicting_file() {
648 let file = PathBuf::from("tests")
649 .join("fixtures")
650 .join(Path::new("loose_index").join("conflicting-file.git-index"));
651 let file = crate::File::at(file, gix_hash::Kind::Sha1, false, Default::default()).expect("valid file");
652 assert_eq!(
653 file.entries().len(),
654 3,
655 "we have a set of conflict entries for a single file"
656 );
657 for idx in 0..3 {
658 for wanted_stage in 1..=3 {
659 let actual_idx = file
660 .entry_index_by_idx_and_stage(
661 "file".into(),
662 idx,
663 wanted_stage,
664 (idx + 1).cmp(&(wanted_stage as usize)),
665 )
666 .expect("found");
667 assert_eq!(
668 actual_idx + 1,
669 wanted_stage as usize,
670 "the index and stage have a relation, and that is upheld if we search correctly"
671 );
672 }
673 }
674 }
675}