Skip to content

Commit 595fe87

Browse files
committed
feat!: Stack::at_path() replaces is_dir parameter with mode.
That way, detailed information about the path-to-be is available not only for evaluating attributes or excludes, but also for validating path components (in this case, relevant for `.gitmodules`).
1 parent 874cfd6 commit 595fe87

File tree

4 files changed

+64
-33
lines changed

4 files changed

+64
-33
lines changed

gix-worktree/src/stack/delegate.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::stack::mode_is_dir;
12
use crate::{stack::State, PathIdMapping};
23

34
/// Various aggregate numbers related to the stack delegate itself.
@@ -20,7 +21,7 @@ pub(crate) struct StackDelegate<'a, 'find> {
2021
pub state: &'a mut State,
2122
pub buf: &'a mut Vec<u8>,
2223
#[cfg_attr(not(feature = "attributes"), allow(dead_code))]
23-
pub is_dir: bool,
24+
pub mode: Option<gix_index::entry::Mode>,
2425
pub id_mappings: &'a Vec<PathIdMapping>,
2526
pub objects: &'find dyn gix_object::Find,
2627
pub case: gix_glob::pattern::Case,
@@ -91,11 +92,11 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
9192
validate,
9293
attributes: _,
9394
} => {
94-
validate_last_component(stack, *validate)?;
95+
validate_last_component(stack, self.mode, *validate)?;
9596
create_leading_directory(
9697
is_last_component,
9798
stack,
98-
self.is_dir,
99+
self.mode,
99100
&mut self.statistics.delegate.num_mkdir_calls,
100101
*unlink_on_collision,
101102
)?
@@ -127,8 +128,11 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
127128
}
128129

129130
#[cfg(feature = "attributes")]
130-
fn validate_last_component(stack: &gix_fs::Stack, opts: gix_validate::path::component::Options) -> std::io::Result<()> {
131-
// TODO: add mode-information
131+
fn validate_last_component(
132+
stack: &gix_fs::Stack,
133+
mode: Option<gix_index::entry::Mode>,
134+
opts: gix_validate::path::component::Options,
135+
) -> std::io::Result<()> {
132136
let Some(last_component) = stack.current_relative().components().rev().next() else {
133137
return Ok(());
134138
};
@@ -143,7 +147,13 @@ fn validate_last_component(stack: &gix_fs::Stack, opts: gix_validate::path::comp
143147
)
144148
})?;
145149

146-
if let Err(err) = gix_validate::path::component(last_component.as_ref(), None, opts) {
150+
if let Err(err) = gix_validate::path::component(
151+
last_component.as_ref(),
152+
mode.and_then(|m| {
153+
(m == gix_index::entry::Mode::SYMLINK).then_some(gix_validate::path::component::Mode::Symlink)
154+
}),
155+
opts,
156+
) {
147157
return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
148158
}
149159
Ok(())
@@ -153,11 +163,11 @@ fn validate_last_component(stack: &gix_fs::Stack, opts: gix_validate::path::comp
153163
fn create_leading_directory(
154164
is_last_component: bool,
155165
stack: &gix_fs::Stack,
156-
is_dir: bool,
166+
mode: Option<gix_index::entry::Mode>,
157167
mkdir_calls: &mut usize,
158168
unlink_on_collision: bool,
159169
) -> std::io::Result<()> {
160-
if is_last_component && !is_dir {
170+
if is_last_component && !mode_is_dir(mode).unwrap_or(false) {
161171
return Ok(());
162172
}
163173
*mkdir_calls += 1;

gix-worktree/src/stack/mod.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,42 +105,46 @@ impl Stack {
105105
impl Stack {
106106
/// Append the `relative` path to the root directory of the cache and efficiently create leading directories, while assuring that no
107107
/// symlinks are in that path.
108-
/// Unless `is_dir` is known with `Some(…)`, then `relative` points to a directory itself in which case the entire resulting
109-
/// path is created as directory. If it's not known it is assumed to be a file.
108+
/// Unless `mode` is known with `Some(gix_index::entry::Mode::DIR|COMMIT)`,
109+
/// then `relative` points to a directory itself in which case the entire resulting path is created as directory.
110+
/// If it's not known it is assumed to be a file.
110111
/// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()], with mappnigs
111112
///
112113
/// Provide access to cached information for that `relative` path via the returned platform.
113114
pub fn at_path(
114115
&mut self,
115116
relative: impl AsRef<Path>,
116-
is_dir: Option<bool>,
117+
mode: Option<gix_index::entry::Mode>,
117118
objects: &dyn gix_object::Find,
118119
) -> std::io::Result<Platform<'_>> {
119120
self.statistics.platforms += 1;
120121
let mut delegate = StackDelegate {
121122
state: &mut self.state,
122123
buf: &mut self.buf,
123-
is_dir: is_dir.unwrap_or(false),
124+
mode,
124125
id_mappings: &self.id_mappings,
125126
objects,
126127
case: self.case,
127128
statistics: &mut self.statistics,
128129
};
129130
self.stack
130131
.make_relative_path_current(relative.as_ref(), &mut delegate)?;
131-
Ok(Platform { parent: self, is_dir })
132+
Ok(Platform {
133+
parent: self,
134+
is_dir: mode_is_dir(mode),
135+
})
132136
}
133137

134-
/// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `is_dir` should reflect
135-
/// whether it's a directory or not, or left at `None` if unknown.
138+
/// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `mode` should reflect
139+
/// the kind of item set here, or left at `None` if unknown.
136140
/// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()].
137141
/// All effects are similar to [`at_path()`][Self::at_path()].
138142
///
139-
/// If `relative` ends with `/` and `is_dir` is `None`, it is automatically assumed to be a directory.
143+
/// If `relative` ends with `/` and `mode` is `None`, it is automatically assumed set to be a directory.
140144
pub fn at_entry<'r>(
141145
&mut self,
142146
relative: impl Into<&'r BStr>,
143-
is_dir: Option<bool>,
147+
mode: Option<gix_index::entry::Mode>,
144148
objects: &dyn gix_object::Find,
145149
) -> std::io::Result<Platform<'_>> {
146150
let relative = relative.into();
@@ -156,12 +160,18 @@ impl Stack {
156160

157161
self.at_path(
158162
relative_path,
159-
is_dir.or_else(|| relative.ends_with_str("/").then_some(true)),
163+
mode.or_else(|| relative.ends_with_str("/").then_some(gix_index::entry::Mode::DIR)),
160164
objects,
161165
)
162166
}
163167
}
164168

169+
fn mode_is_dir(mode: Option<gix_index::entry::Mode>) -> Option<bool> {
170+
mode.map(|m|
171+
// This applies to directories and commits (submodules are directories on disk)
172+
m.is_sparse() || m.is_submodule())
173+
}
174+
165175
/// Mutation
166176
impl Stack {
167177
/// Reset the statistics after returning them.

gix-worktree/tests/worktree/stack/create_directory.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::path::Path;
33
use gix_testtools::tempfile::{tempdir, TempDir};
44
use gix_worktree::{stack, Stack};
55

6+
const IS_FILE: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::FILE);
7+
const IS_DIR: Option<gix_index::entry::Mode> = Some(gix_index::entry::Mode::DIR);
8+
69
#[test]
710
fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate::Result {
811
let dir = tempdir()?;
@@ -15,7 +18,7 @@ fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate
1518
);
1619
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
1720

18-
let path = cache.at_path("hello", Some(false), &gix_object::find::Never)?.path();
21+
let path = cache.at_path("hello", IS_FILE, &gix_object::find::Never)?.path();
1922
assert!(!path.parent().unwrap().exists(), "prefix itself is never created");
2023
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 0);
2124
Ok(())
@@ -25,15 +28,15 @@ fn root_is_assumed_to_exist_and_files_in_root_do_not_create_directory() -> crate
2528
fn directory_paths_are_created_in_full() {
2629
let (mut cache, _tmp) = new_cache();
2730

28-
for (name, is_dir) in &[
29-
("dir", Some(true)),
30-
("submodule", Some(true)),
31-
("file", Some(false)),
32-
("exe", Some(false)),
31+
for (name, mode) in [
32+
("dir", IS_DIR),
33+
("submodule", IS_DIR),
34+
("file", IS_FILE),
35+
("exe", IS_FILE),
3336
("link", None),
3437
] {
3538
let path = cache
36-
.at_path(Path::new("dir").join(name), *is_dir, &gix_object::find::Never)
39+
.at_path(Path::new("dir").join(name), mode, &gix_object::find::Never)
3740
.unwrap()
3841
.path();
3942
assert!(path.parent().unwrap().is_dir(), "dir exists");
@@ -47,7 +50,7 @@ fn existing_directories_are_fine() -> crate::Result {
4750
let (mut cache, tmp) = new_cache();
4851
std::fs::create_dir(tmp.path().join("dir"))?;
4952

50-
let path = cache.at_path("dir/file", Some(false), &gix_object::find::Never)?.path();
53+
let path = cache.at_path("dir/file", IS_FILE, &gix_object::find::Never)?.path();
5154
assert!(path.parent().unwrap().is_dir(), "directory is still present");
5255
assert!(!path.exists(), "it won't create the file");
5356
assert_eq!(cache.statistics().delegate.num_mkdir_calls, 1);
@@ -59,7 +62,7 @@ fn validation_to_each_component() -> crate::Result {
5962
let (mut cache, tmp) = new_cache();
6063

6164
let err = cache
62-
.at_path("valid/.gIt", Some(false), &gix_object::find::Never)
65+
.at_path("valid/.gIt", IS_FILE, &gix_object::find::Never)
6366
.unwrap_err();
6467
assert_eq!(
6568
cache.statistics().delegate.num_mkdir_calls,
@@ -89,7 +92,7 @@ fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::R
8992
let relative_path = format!("{dirname}/file");
9093
assert_eq!(
9194
cache
92-
.at_path(&relative_path, Some(false), &gix_object::find::Never)
95+
.at_path(&relative_path, IS_FILE, &gix_object::find::Never)
9396
.unwrap_err()
9497
.kind(),
9598
std::io::ErrorKind::AlreadyExists
@@ -109,9 +112,7 @@ fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::R
109112
*unlink_on_collision = true;
110113
}
111114
let relative_path = format!("{dirname}/file");
112-
let path = cache
113-
.at_path(&relative_path, Some(false), &gix_object::find::Never)?
114-
.path();
115+
let path = cache.at_path(&relative_path, IS_FILE, &gix_object::find::Never)?.path();
115116
assert!(path.parent().unwrap().is_dir(), "directory was forcefully created");
116117
assert!(!path.exists());
117118
}

gix-worktree/tests/worktree/stack/ignore.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use bstr::{BStr, ByteSlice};
2+
use gix_index::entry::Mode;
23
use gix_worktree::{stack::state::ignore::Source, Stack};
4+
use std::fs::Metadata;
35

46
use crate::{hex_to_id, worktree::stack::probe_case};
57

@@ -62,7 +64,7 @@ fn exclude_by_dir_is_handled_just_like_git() {
6264
for (relative_entry, source_and_line) in expectations {
6365
let (source, line, expected_pattern) = source_and_line.expect("every value is matched");
6466
let relative_path = gix_path::from_byte_slice(relative_entry);
65-
let is_dir = dir.join(relative_path).metadata().ok().map(|m| m.is_dir());
67+
let is_dir = dir.join(relative_path).metadata().ok().map(metadata_to_mode);
6668

6769
let platform = cache.at_entry(relative_entry, is_dir, &FindError).unwrap();
6870
let match_ = platform.matching_exclude_pattern().expect("match all values");
@@ -87,6 +89,14 @@ fn exclude_by_dir_is_handled_just_like_git() {
8789
}
8890
}
8991

92+
fn metadata_to_mode(meta: Metadata) -> Mode {
93+
if meta.is_dir() {
94+
gix_index::entry::Mode::DIR
95+
} else {
96+
gix_index::entry::Mode::FILE
97+
}
98+
}
99+
90100
#[test]
91101
fn check_against_baseline() -> crate::Result {
92102
let dir = gix_testtools::scripted_fixture_read_only_standalone("make_ignore_and_attributes_setup.sh")?;
@@ -127,7 +137,7 @@ fn check_against_baseline() -> crate::Result {
127137
};
128138
for (relative_entry, source_and_line) in expectations {
129139
let relative_path = gix_path::from_byte_slice(relative_entry);
130-
let is_dir = worktree_dir.join(relative_path).metadata().ok().map(|m| m.is_dir());
140+
let is_dir = worktree_dir.join(relative_path).metadata().ok().map(metadata_to_mode);
131141

132142
let platform = cache.at_entry(relative_entry, is_dir, &odb)?;
133143

0 commit comments

Comments
 (0)