Skip to content

Commit 7d8d167

Browse files
committed
use gix_fs::current_dir(precompose_unicode).
That way, paths will be precomposed when we work with them.
1 parent e7b2ac1 commit 7d8d167

File tree

16 files changed

+142
-38
lines changed

16 files changed

+142
-38
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-discover/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ gix-sec = { version = "^0.10.3", path = "../gix-sec" }
1717
gix-path = { version = "^0.10.3", path = "../gix-path" }
1818
gix-ref = { version = "^0.40.1", path = "../gix-ref" }
1919
gix-hash = { version = "^0.14.1", path = "../gix-hash" }
20+
gix-fs = { version = "^0.9.1", path = "../gix-fs" }
2021

2122
bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }
2223
thiserror = "1.0.26"

gix-discover/src/upwards/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ pub(crate) mod function {
3939
// us the parent directory. (`Path::parent` just strips off the last
4040
// path component, which means it will not do what you expect when
4141
// working with paths paths that contain '..'.)
42-
let cwd = current_dir.map_or_else(|| std::env::current_dir().map(Cow::Owned), |cwd| Ok(Cow::Borrowed(cwd)))?;
42+
let cwd = current_dir.map_or_else(
43+
|| {
44+
// The paths we return are relevant to the repository, but at this time it's impossible to know
45+
// what `core.precomposeUnicode` is going to be. Hence the one using these paths will have to
46+
// transform the paths as needed, because we can't. `false` means to leave the obtained path as is.
47+
gix_fs::current_dir(false).map(Cow::Owned)
48+
},
49+
|cwd| Ok(Cow::Borrowed(cwd)),
50+
)?;
4351
#[cfg(windows)]
4452
let directory = dunce::simplified(directory);
4553
let dir = gix_path::normalize(directory.into(), cwd.as_ref()).ok_or_else(|| Error::InvalidInput {

gix-discover/src/upwards/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ pub struct Options<'a> {
6363
/// that this is merely an optimization for those who discover a lot of repositories in the same process.
6464
///
6565
/// If unset, the current working directory will be obtained automatically.
66+
/// Note that the path here might or might not contained decomposed unicode, which may end up in a path
67+
/// relevant us, like the git-dir or the worktree-dir. However, when opening the repository, it will
68+
/// change decomposed unicode to precomposed unicode based on the value of `core.precomposeUnicode`, and we
69+
/// don't have to deal with that value here just yet.
6670
pub current_dir: Option<&'a std::path::Path>,
6771
}
6872

gix-odb/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ gix-path = { version = "^0.10.3", path = "../gix-path" }
2525
gix-quote = { version = "^0.4.10", path = "../gix-quote" }
2626
gix-object = { version = "^0.40.1", path = "../gix-object" }
2727
gix-pack = { version = "^0.46.1", path = "../gix-pack", default-features = false }
28+
gix-fs = { version = "^0.9.1", path = "../gix-fs" }
2829
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
2930

3031
tempfile = "3.1.0"

gix-odb/src/store_impls/dynamic/init.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct Options {
1717
/// If false, no multi-pack indices will be used. If true, they will be used if their hash matches `object_hash`.
1818
pub use_multi_pack_index: bool,
1919
/// The current directory of the process at the time of instantiation.
20-
/// If unset, it will be retrieved using `std::env::current_dir()`.
20+
/// If unset, it will be retrieved using `gix_fs::current_dir(false)`.
2121
pub current_dir: Option<std::path::PathBuf>,
2222
}
2323

@@ -80,7 +80,13 @@ impl Store {
8080
}: Options,
8181
) -> std::io::Result<Self> {
8282
let _span = gix_features::trace::detail!("gix_odb::Store::at()");
83-
let current_dir = current_dir.map_or_else(std::env::current_dir, Ok)?;
83+
let current_dir = current_dir.map_or_else(
84+
|| {
85+
// It's only used for real-pathing alternate paths and there it just needs to be consistent (enough).
86+
gix_fs::current_dir(false)
87+
},
88+
Ok,
89+
)?;
8490
if !objects_dir.is_dir() {
8591
return Err(std::io::Error::new(
8692
std::io::ErrorKind::Other, // TODO: use NotADirectory when stabilized

gix-path/src/convert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ pub fn to_windows_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr
246246
/// instead.
247247
///
248248
/// Note that we might access the `current_dir` if we run out of path components to pop off, which is expected to be absolute
249-
/// as typical return value of `std::env::current_dir()`.
249+
/// as typical return value of `std::env::current_dir()` or `gix_fs::current_dir(…)` when `core.precomposeUnicode` is known.
250250
/// As a `current_dir` like `/c` can be exhausted by paths like `../../r`, `None` will be returned to indicate the inability
251251
/// to produce a logically consistent path.
252252
pub fn normalize<'a>(path: Cow<'a, Path>, current_dir: &Path) -> Option<Cow<'a, Path>> {

gix-path/src/realpath.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub(crate) mod function {
3030
/// Do not fail for non-existing components, but assume these are as is.
3131
///
3232
/// If `path` is relative, the current working directory be used to make it absolute.
33+
/// Note that the returned path will be verbatim, and repositories with `core.precomposeUnicode`
34+
/// set will probably want to precompose the paths unicode.
3335
pub fn realpath(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
3436
let path = path.as_ref();
3537
let cwd = path

gix/src/config/cache/incubate.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![allow(clippy::result_large_err)]
2+
23
use super::{util, Error};
3-
use crate::config::tree::{Core, Extensions};
4+
use crate::config::cache::util::ApplyLeniency;
5+
use crate::config::tree::{Core, Extensions, Key};
46

57
/// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the
68
/// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`.
@@ -12,6 +14,7 @@ pub(crate) struct StageOne {
1214
pub lossy: Option<bool>,
1315
pub object_hash: gix_hash::Kind,
1416
pub reflog: Option<gix_ref::store::WriteReflog>,
17+
pub precompose_unicode: bool,
1518
}
1619

1720
/// Initialization
@@ -69,6 +72,13 @@ impl StageOne {
6972
)?;
7073
config.append(worktree_config);
7174
};
75+
let precompose_unicode = config
76+
.boolean("core", None, Core::PRECOMPOSE_UNICODE.name())
77+
.map(|v| Core::PRECOMPOSE_UNICODE.enrich_error(v))
78+
.transpose()
79+
.with_leniency(lenient)
80+
.map_err(Error::ConfigBoolean)?
81+
.unwrap_or_default();
7282

7383
let reflog = util::query_refupdates(&config, lenient)?;
7484
Ok(StageOne {
@@ -78,6 +88,7 @@ impl StageOne {
7888
lossy,
7989
object_hash,
8090
reflog,
91+
precompose_unicode,
8192
})
8293
}
8394
}

gix/src/config/cache/init.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ impl Cache {
2727
is_bare,
2828
object_hash,
2929
reflog: _,
30+
precompose_unicode: _,
3031
}: StageOne,
3132
git_dir: &std::path::Path,
3233
branch_name: Option<&gix_ref::FullNameRef>,

gix/src/create.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ pub fn into(
205205
write_file(tpl, PathCursor(&mut dot_git).at(filename))?;
206206
}
207207

208-
{
208+
let caps = {
209209
let mut config = gix_config::File::default();
210-
{
210+
let caps = {
211211
let caps = fs_capabilities.unwrap_or_else(|| gix_fs::Capabilities::probe(&dot_git));
212212
let mut core = config.new_section("core", None).expect("valid section name");
213213

@@ -218,14 +218,16 @@ pub fn into(
218218
core.push(key("symlinks"), Some(bool(caps.symlink).into()));
219219
core.push(key("ignorecase"), Some(bool(caps.ignore_case).into()));
220220
core.push(key("precomposeunicode"), Some(bool(caps.precompose_unicode).into()));
221-
}
221+
caps
222+
};
222223
let mut cursor = PathCursor(&mut dot_git);
223224
let config_path = cursor.at("config");
224225
std::fs::write(config_path, config.to_bstring()).map_err(|err| Error::IoWrite {
225226
source: err,
226227
path: config_path.to_owned(),
227228
})?;
228-
}
229+
caps
230+
};
229231

230232
Ok(gix_discover::repository::Path::from_dot_git_dir(
231233
dot_git,
@@ -234,7 +236,7 @@ pub fn into(
234236
} else {
235237
gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
236238
},
237-
&std::env::current_dir()?,
239+
&gix_fs::current_dir(caps.precompose_unicode)?,
238240
)
239241
.expect("by now the `dot_git` dir is valid as we have accessed it"))
240242
}

gix/src/discover.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ impl ThreadSafeRepository {
4343
let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
4444
let mut options = trust_map.into_value_by_level(trust);
4545
options.git_dir_trust = trust.into();
46-
options.current_dir = Some(std::env::current_dir().map_err(upwards::Error::CurrentDir)?);
46+
// Note that we will adjust the `current_dir` later so it matches the value of `core.precomposeUnicode`.
47+
options.current_dir = Some(gix_fs::current_dir(false).map_err(upwards::Error::CurrentDir)?);
4748
Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into)
4849
}
4950

gix/src/init.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ impl ThreadSafeRepository {
6868
let path = crate::create::into(directory.as_ref(), kind, create_options)?;
6969
let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
7070
open_options.git_dir_trust = Some(gix_sec::Trust::Full);
71-
open_options.current_dir = std::env::current_dir()?.into();
71+
// The repo will use `core.precomposeUnicode` to adjust the value as needed.
72+
open_options.current_dir = gix_fs::current_dir(false)?.into();
7273
let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options)?;
7374

7475
let branch_name = repo

gix/src/open/repository.rs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use gix_features::threading::OwnShared;
55
use gix_macros::momo;
66

77
use super::{Error, Options};
8-
use crate::config::cache::util::ApplyLeniency;
98
use crate::{
109
config,
1110
config::{
@@ -88,7 +87,9 @@ impl ThreadSafeRepository {
8887
}
8988
}
9089
};
91-
let cwd = std::env::current_dir()?;
90+
91+
// The be altered later based on `core.precomposeUnicode`.
92+
let cwd = gix_fs::current_dir(false)?;
9293
let (git_dir, worktree_dir) = gix_discover::repository::Path::from_dot_git_dir(path, kind, &cwd)
9394
.expect("we have sanitized path with is_git()")
9495
.into_repository_and_work_tree_directories();
@@ -137,7 +138,8 @@ impl ThreadSafeRepository {
137138
}
138139
};
139140

140-
let cwd = std::env::current_dir()?;
141+
// The be altered later based on `core.precomposeUnicode`.
142+
let cwd = gix_fs::current_dir(false)?;
141143
let (git_dir, worktree_dir) = gix_discover::repository::Path::from_dot_git_dir(path, path_kind, &cwd)
142144
.expect("we have sanitized path with is_git()")
143145
.into_repository_and_work_tree_directories();
@@ -150,9 +152,9 @@ impl ThreadSafeRepository {
150152
}
151153

152154
pub(crate) fn open_from_paths(
153-
git_dir: PathBuf,
155+
mut git_dir: PathBuf,
154156
mut worktree_dir: Option<PathBuf>,
155-
options: Options,
157+
mut options: Options,
156158
) -> Result<Self, Error> {
157159
let _span = gix_trace::detail!("open_from_paths()");
158160
let Options {
@@ -171,47 +173,58 @@ impl ThreadSafeRepository {
171173
},
172174
ref api_config_overrides,
173175
ref cli_config_overrides,
174-
ref current_dir,
176+
ref mut current_dir,
175177
} = options;
176-
let current_dir = current_dir.as_deref().expect("BUG: current_dir must be set by caller");
177178
let git_dir_trust = git_dir_trust.expect("trust must be determined by now");
178179

179-
// TODO: assure we handle the worktree-dir properly as we can have config per worktree with an extension.
180-
// This would be something read in later as have to first check for extensions. Also this means
181-
// that each worktree, even if accessible through this instance, has to come in its own Repository instance
182-
// as it may have its own configuration. That's fine actually.
183-
let common_dir = gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
180+
let mut common_dir = gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
184181
.transpose()?
185182
.map(|cd| git_dir.join(cd));
186-
let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir);
187-
188183
let repo_config = config::cache::StageOne::new(
189-
common_dir_ref,
184+
common_dir.as_deref().unwrap_or(&git_dir),
190185
git_dir.as_ref(),
191186
git_dir_trust,
192187
lossy_config,
193188
lenient_config,
194189
)?;
190+
191+
if repo_config.precompose_unicode {
192+
git_dir = gix_utils::str::precompose_path(git_dir.into()).into_owned();
193+
if let Some(common_dir) = common_dir.as_mut() {
194+
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*common_dir).into()) {
195+
*common_dir = precomposed;
196+
}
197+
}
198+
if let Some(worktree_dir) = worktree_dir.as_mut() {
199+
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*worktree_dir).into()) {
200+
*worktree_dir = precomposed;
201+
}
202+
}
203+
}
204+
let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir);
205+
206+
let current_dir = {
207+
let current_dir_ref = current_dir.as_mut().expect("BUG: current_dir must be set by caller");
208+
if repo_config.precompose_unicode {
209+
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*current_dir_ref).into()) {
210+
*current_dir_ref = precomposed;
211+
}
212+
}
213+
current_dir_ref.as_path()
214+
};
215+
195216
let mut refs = {
196217
let reflog = repo_config.reflog.unwrap_or(gix_ref::store::WriteReflog::Disable);
197218
let object_hash = repo_config.object_hash;
198-
let precompose_unicode = repo_config
199-
.git_dir_config
200-
.boolean("core", None, Core::PRECOMPOSE_UNICODE.name())
201-
.map(|v| Core::PRECOMPOSE_UNICODE.enrich_error(v))
202-
.transpose()
203-
.with_leniency(lenient_config)
204-
.map_err(|err| Error::Config(err.into()))?
205-
.unwrap_or_default();
206219
match &common_dir {
207220
Some(common_dir) => crate::RefStore::for_linked_worktree(
208221
git_dir.to_owned(),
209222
common_dir.into(),
210223
reflog,
211224
object_hash,
212-
precompose_unicode,
225+
repo_config.precompose_unicode,
213226
),
214-
None => crate::RefStore::at(git_dir.to_owned(), reflog, object_hash, precompose_unicode),
227+
None => crate::RefStore::at(git_dir.to_owned(), reflog, object_hash, repo_config.precompose_unicode),
215228
}
216229
};
217230
let head = refs.find("HEAD").ok();

gix/src/remote/connect.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ impl<'repo> Remote<'repo> {
123123
let (git_dir, _work_dir) = gix_discover::repository::Path::from_dot_git_dir(
124124
dir.clone().into_owned(),
125125
kind,
126-
&std::env::current_dir()?,
126+
// precomposed unicode doesn't matter here as long as the produced path is accessible,
127+
// which is a given either way.
128+
&gix_fs::current_dir(false)?,
127129
)
128130
.ok_or_else(|| Error::InvalidRemoteRepositoryPath {
129131
directory: dir.into_owned(),

gix/tests/repository/open.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
1+
use std::borrow::Cow;
12
use std::error::Error;
23

34
use crate::util::named_subrepo_opts;
45

6+
#[test]
7+
fn on_root_with_decomposed_unicode() -> crate::Result {
8+
let tmp = gix_testtools::tempfile::TempDir::new()?;
9+
10+
let decomposed = "a\u{308}";
11+
12+
let root = tmp.path().join(decomposed);
13+
std::fs::create_dir(&root)?;
14+
15+
let repo = gix::init(root)?;
16+
let precompose_unicode = repo
17+
.config_snapshot()
18+
.boolean("core.precomposeUnicode")
19+
.expect("created by init based on fs-capabilities");
20+
21+
assert!(repo.git_dir().is_dir());
22+
let work_dir = repo.work_dir().expect("non-bare");
23+
assert!(work_dir.is_dir());
24+
25+
if precompose_unicode {
26+
assert!(
27+
matches!(
28+
gix::utils::str::precompose_path(repo.git_dir().into()),
29+
Cow::Borrowed(_),
30+
),
31+
"there is no change, as the path is already precomposed"
32+
);
33+
assert!(matches!(
34+
gix::utils::str::precompose_path(work_dir.into()),
35+
Cow::Borrowed(_),
36+
));
37+
} else {
38+
assert!(
39+
matches!(
40+
gix::utils::str::precompose_path(repo.git_dir().into()),
41+
Cow::Owned(_),
42+
),
43+
"this has an effect as the path isn't precomposed, a necessity on filesystems that don't fold decomposition"
44+
);
45+
assert!(matches!(
46+
gix::utils::str::precompose_path(work_dir.into()),
47+
Cow::Owned(_),
48+
));
49+
}
50+
51+
Ok(())
52+
}
53+
554
#[test]
655
fn bare_repo_with_index() -> crate::Result {
756
let repo = named_subrepo_opts(

0 commit comments

Comments
 (0)