Skip to content

Commit 8b689c2

Browse files
committed
feat: add State::remove_entries() and entry_range().
This makes it possible to, among other things, delete all occurrences of a particular entry.
1 parent 6169325 commit 8b689c2

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

gix-index/src/access/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::cmp::Ordering;
2+
use std::ops::Range;
23

34
use bstr::{BStr, ByteSlice, ByteVec};
45
use filetime::FileTime;
@@ -223,6 +224,30 @@ impl State {
223224
pub fn is_sparse(&self) -> bool {
224225
self.is_sparse
225226
}
227+
228+
/// Return the range of entries that exactly match the given `path`, in all available stages, or `None` if no entry with such
229+
/// path exists.
230+
///
231+
/// The range can be used to access the respective entries via [`entries()`](Self::entries()) or [`entries_mut()](Self::entries_mut()).
232+
pub fn entry_range(&self, path: &BStr) -> Option<Range<usize>> {
233+
let mut stage_at_index = 0;
234+
let idx = self
235+
.entries
236+
.binary_search_by(|e| {
237+
let res = e.path(self).cmp(path);
238+
if res.is_eq() {
239+
stage_at_index = e.stage();
240+
}
241+
res
242+
})
243+
.ok()?;
244+
245+
let (start, end) = (
246+
self.walk_entry_stages(path, idx, Ordering::Less).unwrap_or(idx),
247+
self.walk_entry_stages(path, idx, Ordering::Greater).unwrap_or(idx) + 1,
248+
);
249+
Some(start..end)
250+
}
226251
}
227252

228253
/// Mutation
@@ -333,6 +358,25 @@ impl State {
333358
.then_with(|| compare(a, b))
334359
});
335360
}
361+
362+
/// Physically remove all entries for which `should_remove(idx, path, entry)` returns `true`, traversing them from first to last.
363+
///
364+
/// Note that the memory used for the removed entries paths is not freed, as it's append-only.
365+
///
366+
/// ### Performance
367+
///
368+
/// To implement this operation typically, one would rather add [entry::Flags::REMOVE] to each entry to remove
369+
/// them when [writing the index](Self::write_to()).
370+
pub fn remove_entries(&mut self, mut should_remove: impl FnMut(usize, &BStr, &mut Entry) -> bool) {
371+
let mut index = 0;
372+
let paths = &self.path_backing;
373+
self.entries.retain_mut(|e| {
374+
let path = e.path_in(paths);
375+
let res = !should_remove(index, path, e);
376+
index += 1;
377+
res
378+
});
379+
}
336380
}
337381

338382
/// Extensions

gix-index/tests/index/access.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,54 @@ fn entry_by_path_with_conflicting_file() {
2929
2,
3030
"we always find our stage while in a merge"
3131
);
32+
}
33+
34+
#[test]
35+
fn prefixed_entries_with_multi_stage_file() {
36+
let file = Fixture::Loose("conflicting-file").open();
37+
3238
assert_eq!(
3339
file.prefixed_entries("fil".into()).expect("present"),
3440
file.entries(),
3541
"it's possible to get the entire range"
3642
);
43+
assert_eq!(
44+
file.prefixed_entries("file".into()).expect("present"),
45+
file.entries(),
46+
"it's possible to get the entire range even if the same path matches multiple times"
47+
);
3748
assert_eq!(
3849
file.prefixed_entries("".into()).expect("present"),
3950
file.entries(),
4051
"empty prefix matches all"
4152
);
4253
}
4354

55+
#[test]
56+
fn entry_range() {
57+
let file = Fixture::Loose("conflicting-file").open();
58+
59+
assert_eq!(
60+
file.entry_range("file".into()),
61+
Some(0..3),
62+
"three stages, all but stage zero"
63+
);
64+
assert_eq!(file.entry_range("foo".into()), None, "does not exist");
65+
}
66+
67+
#[test]
68+
fn remove_entries() {
69+
let mut file = Fixture::Loose("conflicting-file").open();
70+
71+
file.remove_entries(|idx, _, _| idx == 0);
72+
assert_eq!(file.entries().len(), 2);
73+
file.remove_entries(|idx, _, _| idx == 0);
74+
assert_eq!(file.entries().len(), 1);
75+
file.remove_entries(|idx, _, _| idx == 0);
76+
assert_eq!(file.entries().len(), 0);
77+
file.remove_entries(|_, _, _| unreachable!("should not be called"));
78+
}
79+
4480
#[test]
4581
fn sort_entries() {
4682
let mut file = Fixture::Generated("v4_more_files_IEOT").open();

0 commit comments

Comments
 (0)