Skip to content

Commit f1b3906

Browse files
CopilotByron
andcommitted
feat: Add State::path_is_dir() and State::path_is_dir_icase()
Co-authored-by: Byron <63622+Byron@users.noreply.github.com>
1 parent e411413 commit f1b3906

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

gix-index/src/access/mod.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ impl State {
212212
/// make another call to [`entry_by_path_icase()`](Self::entry_by_path_icase) to check for this
213213
/// possibility. Doing so might also reveal a sparse directory.
214214
///
215-
/// If `ignore_case` is set
215+
/// If `ignore_case` is set, a case-insensitive (ASCII-folding only) search will be performed.
216216
pub fn entry_closest_to_directory_icase<'a>(
217217
&'a self,
218218
directory: &BStr,
@@ -259,6 +259,39 @@ impl State {
259259
None
260260
}
261261

262+
/// Check if `path` is a directory that contains entries in the index.
263+
///
264+
/// Returns `true` if there is at least one entry in the index whose path starts with `path/`,
265+
/// indicating that `path` is a directory containing indexed files.
266+
///
267+
/// For example, if the index contains an entry at `dirname/file`, then calling this method
268+
/// with `dirname` would return `true`, but calling it with `dir` would return `false`.
269+
///
270+
/// Note that this is a case-sensitive search.
271+
pub fn path_is_directory(&self, path: &BStr) -> bool {
272+
self.entry_closest_to_directory(path).is_some()
273+
}
274+
275+
/// Check if `path` is a directory that contains entries in the index, with optional case-insensitive matching.
276+
///
277+
/// Returns `true` if there is at least one entry in the index whose path starts with `path/`,
278+
/// indicating that `path` is a directory containing indexed files.
279+
///
280+
/// If `ignore_case` is `true`, a case-insensitive (ASCII-folding only) search will be performed.
281+
///
282+
/// For example, if the index contains an entry at `dirname/file`, then calling this method
283+
/// with `dirname` (or `DirName` with `ignore_case = true`) would return `true`, but calling it
284+
/// with `dir` would return `false`.
285+
pub fn path_is_directory_icase<'a>(
286+
&'a self,
287+
path: &BStr,
288+
ignore_case: bool,
289+
lookup: &AccelerateLookup<'a>,
290+
) -> bool {
291+
self.entry_closest_to_directory_icase(path, ignore_case, lookup)
292+
.is_some()
293+
}
294+
262295
/// Find the entry index in [`entries()[..upper_bound]`][State::entries()] matching the given repository-relative
263296
/// `path` and `stage`, or `None`.
264297
///

gix-index/tests/index/access.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,112 @@ fn check_prefix(index: &gix_index::State, prefix: &str, expected: &[&str]) {
324324
"{prefix:?}"
325325
);
326326
}
327+
328+
#[test]
329+
fn path_is_directory() {
330+
let file = Fixture::Loose("ignore-case-realistic").open();
331+
332+
// Test that directories containing entries are detected
333+
assert!(
334+
file.path_is_directory("tests".into()),
335+
"tests is a directory containing entries"
336+
);
337+
assert!(
338+
file.path_is_directory("tests/snapshots".into()),
339+
"tests/snapshots is a directory containing entries"
340+
);
341+
assert!(
342+
file.path_is_directory("tests/snapshots/porcelain".into()),
343+
"tests/snapshots/porcelain is a directory"
344+
);
345+
assert!(
346+
file.path_is_directory("tests/tools".into()),
347+
"tests/tools is a directory"
348+
);
349+
350+
// Test that non-existent directories return false
351+
assert!(
352+
!file.path_is_directory("nonexistent".into()),
353+
"nonexistent is not a directory"
354+
);
355+
assert!(!file.path_is_directory("z".into()), "z is not a directory");
356+
assert!(
357+
!file.path_is_directory("test".into()),
358+
"test is not a directory (tests is)"
359+
);
360+
361+
// Test that files are not directories
362+
assert!(
363+
!file.path_is_directory("tests/utilities.sh".into()),
364+
"tests/utilities.sh is a file, not a directory"
365+
);
366+
367+
// Test that partial directory names don't match
368+
assert!(!file.path_is_directory("".into()), "empty path is not a directory");
369+
}
370+
371+
#[test]
372+
fn path_is_directory_icase() {
373+
let file = Fixture::Loose("ignore-case-realistic").open();
374+
let icase = file.prepare_icase_backing();
375+
376+
// Test case-sensitive matching
377+
assert!(
378+
file.path_is_directory_icase("tests".into(), false, &icase),
379+
"tests is a directory (case-sensitive)"
380+
);
381+
assert!(
382+
file.path_is_directory_icase("tests/tools".into(), false, &icase),
383+
"tests/tools is a directory (case-sensitive)"
384+
);
385+
386+
// Test case-insensitive matching
387+
assert!(
388+
file.path_is_directory_icase("TESTS".into(), true, &icase),
389+
"TESTS is a directory (case-insensitive, matches 'tests')"
390+
);
391+
assert!(
392+
file.path_is_directory_icase("tests/TOOLS".into(), true, &icase),
393+
"tests/TOOLS is a directory (case-insensitive, matches 'tests/tools')"
394+
);
395+
assert!(
396+
file.path_is_directory_icase("TESTS/SNAPSHOTS".into(), true, &icase),
397+
"TESTS/SNAPSHOTS is a directory (case-insensitive)"
398+
);
399+
400+
// Test that non-existent paths return false even with icase
401+
assert!(
402+
!file.path_is_directory_icase("nonexistent".into(), true, &icase),
403+
"nonexistent is not a directory even with icase"
404+
);
405+
assert!(
406+
!file.path_is_directory_icase("Z".into(), true, &icase),
407+
"Z is not a directory even with icase"
408+
);
409+
}
410+
411+
#[test]
412+
fn path_is_directory_icase_with_clashes() {
413+
let file = icase_fixture();
414+
let icase = file.prepare_icase_backing();
415+
416+
// Test directory detection with case clashes
417+
assert!(
418+
file.path_is_directory_icase("D".into(), false, &icase),
419+
"D is a directory (case-sensitive)"
420+
);
421+
assert!(
422+
file.path_is_directory_icase("d".into(), true, &icase),
423+
"d matches D directory (case-insensitive)"
424+
);
425+
426+
// Test that files aren't detected as directories
427+
assert!(
428+
!file.path_is_directory_icase("X".into(), false, &icase),
429+
"X is a file, not a directory"
430+
);
431+
assert!(
432+
!file.path_is_directory_icase("x".into(), false, &icase),
433+
"x is a symlink, not a directory"
434+
);
435+
}

0 commit comments

Comments
 (0)