Skip to content

Commit

Permalink
use gix_fs::current_dir(precompose_unicode).
Browse files Browse the repository at this point in the history
That way, paths will be precomposed when we work with them.
  • Loading branch information
Byron committed Jan 17, 2024
1 parent e7b2ac1 commit 7d8d167
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 38 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions gix-discover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ gix-sec = { version = "^0.10.3", path = "../gix-sec" }
gix-path = { version = "^0.10.3", path = "../gix-path" }
gix-ref = { version = "^0.40.1", path = "../gix-ref" }
gix-hash = { version = "^0.14.1", path = "../gix-hash" }
gix-fs = { version = "^0.9.1", path = "../gix-fs" }

bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }
thiserror = "1.0.26"
Expand Down
10 changes: 9 additions & 1 deletion gix-discover/src/upwards/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ pub(crate) mod function {
// us the parent directory. (`Path::parent` just strips off the last
// path component, which means it will not do what you expect when
// working with paths paths that contain '..'.)
let cwd = current_dir.map_or_else(|| std::env::current_dir().map(Cow::Owned), |cwd| Ok(Cow::Borrowed(cwd)))?;
let cwd = current_dir.map_or_else(
|| {
// The paths we return are relevant to the repository, but at this time it's impossible to know
// what `core.precomposeUnicode` is going to be. Hence the one using these paths will have to
// transform the paths as needed, because we can't. `false` means to leave the obtained path as is.
gix_fs::current_dir(false).map(Cow::Owned)
},
|cwd| Ok(Cow::Borrowed(cwd)),
)?;
#[cfg(windows)]
let directory = dunce::simplified(directory);
let dir = gix_path::normalize(directory.into(), cwd.as_ref()).ok_or_else(|| Error::InvalidInput {
Expand Down
4 changes: 4 additions & 0 deletions gix-discover/src/upwards/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub struct Options<'a> {
/// that this is merely an optimization for those who discover a lot of repositories in the same process.
///
/// If unset, the current working directory will be obtained automatically.
/// Note that the path here might or might not contained decomposed unicode, which may end up in a path
/// relevant us, like the git-dir or the worktree-dir. However, when opening the repository, it will
/// change decomposed unicode to precomposed unicode based on the value of `core.precomposeUnicode`, and we
/// don't have to deal with that value here just yet.
pub current_dir: Option<&'a std::path::Path>,
}

Expand Down
1 change: 1 addition & 0 deletions gix-odb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ gix-path = { version = "^0.10.3", path = "../gix-path" }
gix-quote = { version = "^0.4.10", path = "../gix-quote" }
gix-object = { version = "^0.40.1", path = "../gix-object" }
gix-pack = { version = "^0.46.1", path = "../gix-pack", default-features = false }
gix-fs = { version = "^0.9.1", path = "../gix-fs" }
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}

tempfile = "3.1.0"
Expand Down
10 changes: 8 additions & 2 deletions gix-odb/src/store_impls/dynamic/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct Options {
/// If false, no multi-pack indices will be used. If true, they will be used if their hash matches `object_hash`.
pub use_multi_pack_index: bool,
/// The current directory of the process at the time of instantiation.
/// If unset, it will be retrieved using `std::env::current_dir()`.
/// If unset, it will be retrieved using `gix_fs::current_dir(false)`.
pub current_dir: Option<std::path::PathBuf>,
}

Expand Down Expand Up @@ -80,7 +80,13 @@ impl Store {
}: Options,
) -> std::io::Result<Self> {
let _span = gix_features::trace::detail!("gix_odb::Store::at()");
let current_dir = current_dir.map_or_else(std::env::current_dir, Ok)?;
let current_dir = current_dir.map_or_else(
|| {
// It's only used for real-pathing alternate paths and there it just needs to be consistent (enough).
gix_fs::current_dir(false)
},
Ok,
)?;
if !objects_dir.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other, // TODO: use NotADirectory when stabilized
Expand Down
2 changes: 1 addition & 1 deletion gix-path/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub fn to_windows_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr
/// instead.
///
/// Note that we might access the `current_dir` if we run out of path components to pop off, which is expected to be absolute
/// as typical return value of `std::env::current_dir()`.
/// as typical return value of `std::env::current_dir()` or `gix_fs::current_dir(…)` when `core.precomposeUnicode` is known.
/// As a `current_dir` like `/c` can be exhausted by paths like `../../r`, `None` will be returned to indicate the inability
/// to produce a logically consistent path.
pub fn normalize<'a>(path: Cow<'a, Path>, current_dir: &Path) -> Option<Cow<'a, Path>> {
Expand Down
2 changes: 2 additions & 0 deletions gix-path/src/realpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub(crate) mod function {
/// Do not fail for non-existing components, but assume these are as is.
///
/// If `path` is relative, the current working directory be used to make it absolute.
/// Note that the returned path will be verbatim, and repositories with `core.precomposeUnicode`
/// set will probably want to precompose the paths unicode.
pub fn realpath(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
let path = path.as_ref();
let cwd = path
Expand Down
13 changes: 12 additions & 1 deletion gix/src/config/cache/incubate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![allow(clippy::result_large_err)]

use super::{util, Error};
use crate::config::tree::{Core, Extensions};
use crate::config::cache::util::ApplyLeniency;
use crate::config::tree::{Core, Extensions, Key};

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

/// Initialization
Expand Down Expand Up @@ -69,6 +72,13 @@ impl StageOne {
)?;
config.append(worktree_config);
};
let precompose_unicode = config
.boolean("core", None, Core::PRECOMPOSE_UNICODE.name())
.map(|v| Core::PRECOMPOSE_UNICODE.enrich_error(v))
.transpose()
.with_leniency(lenient)
.map_err(Error::ConfigBoolean)?
.unwrap_or_default();

let reflog = util::query_refupdates(&config, lenient)?;
Ok(StageOne {
Expand All @@ -78,6 +88,7 @@ impl StageOne {
lossy,
object_hash,
reflog,
precompose_unicode,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions gix/src/config/cache/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl Cache {
is_bare,
object_hash,
reflog: _,
precompose_unicode: _,
}: StageOne,
git_dir: &std::path::Path,
branch_name: Option<&gix_ref::FullNameRef>,
Expand Down
12 changes: 7 additions & 5 deletions gix/src/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ pub fn into(
write_file(tpl, PathCursor(&mut dot_git).at(filename))?;
}

{
let caps = {
let mut config = gix_config::File::default();
{
let caps = {
let caps = fs_capabilities.unwrap_or_else(|| gix_fs::Capabilities::probe(&dot_git));
let mut core = config.new_section("core", None).expect("valid section name");

Expand All @@ -218,14 +218,16 @@ pub fn into(
core.push(key("symlinks"), Some(bool(caps.symlink).into()));
core.push(key("ignorecase"), Some(bool(caps.ignore_case).into()));
core.push(key("precomposeunicode"), Some(bool(caps.precompose_unicode).into()));
}
caps
};
let mut cursor = PathCursor(&mut dot_git);
let config_path = cursor.at("config");
std::fs::write(config_path, config.to_bstring()).map_err(|err| Error::IoWrite {
source: err,
path: config_path.to_owned(),
})?;
}
caps
};

Ok(gix_discover::repository::Path::from_dot_git_dir(
dot_git,
Expand All @@ -234,7 +236,7 @@ pub fn into(
} else {
gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
},
&std::env::current_dir()?,
&gix_fs::current_dir(caps.precompose_unicode)?,
)
.expect("by now the `dot_git` dir is valid as we have accessed it"))
}
Expand Down
3 changes: 2 additions & 1 deletion gix/src/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ impl ThreadSafeRepository {
let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
let mut options = trust_map.into_value_by_level(trust);
options.git_dir_trust = trust.into();
options.current_dir = Some(std::env::current_dir().map_err(upwards::Error::CurrentDir)?);
// Note that we will adjust the `current_dir` later so it matches the value of `core.precomposeUnicode`.
options.current_dir = Some(gix_fs::current_dir(false).map_err(upwards::Error::CurrentDir)?);
Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into)
}

Expand Down
3 changes: 2 additions & 1 deletion gix/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ impl ThreadSafeRepository {
let path = crate::create::into(directory.as_ref(), kind, create_options)?;
let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
open_options.git_dir_trust = Some(gix_sec::Trust::Full);
open_options.current_dir = std::env::current_dir()?.into();
// The repo will use `core.precomposeUnicode` to adjust the value as needed.
open_options.current_dir = gix_fs::current_dir(false)?.into();
let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options)?;

let branch_name = repo
Expand Down
63 changes: 38 additions & 25 deletions gix/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use gix_features::threading::OwnShared;
use gix_macros::momo;

use super::{Error, Options};
use crate::config::cache::util::ApplyLeniency;
use crate::{
config,
config::{
Expand Down Expand Up @@ -88,7 +87,9 @@ impl ThreadSafeRepository {
}
}
};
let cwd = std::env::current_dir()?;

// The be altered later based on `core.precomposeUnicode`.
let cwd = gix_fs::current_dir(false)?;
let (git_dir, worktree_dir) = gix_discover::repository::Path::from_dot_git_dir(path, kind, &cwd)
.expect("we have sanitized path with is_git()")
.into_repository_and_work_tree_directories();
Expand Down Expand Up @@ -137,7 +138,8 @@ impl ThreadSafeRepository {
}
};

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

pub(crate) fn open_from_paths(
git_dir: PathBuf,
mut git_dir: PathBuf,
mut worktree_dir: Option<PathBuf>,
options: Options,
mut options: Options,
) -> Result<Self, Error> {
let _span = gix_trace::detail!("open_from_paths()");
let Options {
Expand All @@ -171,47 +173,58 @@ impl ThreadSafeRepository {
},
ref api_config_overrides,
ref cli_config_overrides,
ref current_dir,
ref mut current_dir,
} = options;
let current_dir = current_dir.as_deref().expect("BUG: current_dir must be set by caller");
let git_dir_trust = git_dir_trust.expect("trust must be determined by now");

// TODO: assure we handle the worktree-dir properly as we can have config per worktree with an extension.
// This would be something read in later as have to first check for extensions. Also this means
// that each worktree, even if accessible through this instance, has to come in its own Repository instance
// as it may have its own configuration. That's fine actually.
let common_dir = gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
let mut common_dir = gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
.transpose()?
.map(|cd| git_dir.join(cd));
let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir);

let repo_config = config::cache::StageOne::new(
common_dir_ref,
common_dir.as_deref().unwrap_or(&git_dir),
git_dir.as_ref(),
git_dir_trust,
lossy_config,
lenient_config,
)?;

if repo_config.precompose_unicode {
git_dir = gix_utils::str::precompose_path(git_dir.into()).into_owned();
if let Some(common_dir) = common_dir.as_mut() {
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*common_dir).into()) {
*common_dir = precomposed;
}
}
if let Some(worktree_dir) = worktree_dir.as_mut() {
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*worktree_dir).into()) {
*worktree_dir = precomposed;
}
}
}
let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir);

let current_dir = {
let current_dir_ref = current_dir.as_mut().expect("BUG: current_dir must be set by caller");
if repo_config.precompose_unicode {
if let Cow::Owned(precomposed) = gix_utils::str::precompose_path((&*current_dir_ref).into()) {
*current_dir_ref = precomposed;
}
}
current_dir_ref.as_path()
};

let mut refs = {
let reflog = repo_config.reflog.unwrap_or(gix_ref::store::WriteReflog::Disable);
let object_hash = repo_config.object_hash;
let precompose_unicode = repo_config
.git_dir_config
.boolean("core", None, Core::PRECOMPOSE_UNICODE.name())
.map(|v| Core::PRECOMPOSE_UNICODE.enrich_error(v))
.transpose()
.with_leniency(lenient_config)
.map_err(|err| Error::Config(err.into()))?
.unwrap_or_default();
match &common_dir {
Some(common_dir) => crate::RefStore::for_linked_worktree(
git_dir.to_owned(),
common_dir.into(),
reflog,
object_hash,
precompose_unicode,
repo_config.precompose_unicode,
),
None => crate::RefStore::at(git_dir.to_owned(), reflog, object_hash, precompose_unicode),
None => crate::RefStore::at(git_dir.to_owned(), reflog, object_hash, repo_config.precompose_unicode),
}
};
let head = refs.find("HEAD").ok();
Expand Down
4 changes: 3 additions & 1 deletion gix/src/remote/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ impl<'repo> Remote<'repo> {
let (git_dir, _work_dir) = gix_discover::repository::Path::from_dot_git_dir(
dir.clone().into_owned(),
kind,
&std::env::current_dir()?,
// precomposed unicode doesn't matter here as long as the produced path is accessible,
// which is a given either way.
&gix_fs::current_dir(false)?,
)
.ok_or_else(|| Error::InvalidRemoteRepositoryPath {
directory: dir.into_owned(),
Expand Down
49 changes: 49 additions & 0 deletions gix/tests/repository/open.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
use std::borrow::Cow;
use std::error::Error;

use crate::util::named_subrepo_opts;

#[test]
fn on_root_with_decomposed_unicode() -> crate::Result {
let tmp = gix_testtools::tempfile::TempDir::new()?;

let decomposed = "a\u{308}";

let root = tmp.path().join(decomposed);
std::fs::create_dir(&root)?;

let repo = gix::init(root)?;
let precompose_unicode = repo
.config_snapshot()
.boolean("core.precomposeUnicode")
.expect("created by init based on fs-capabilities");

assert!(repo.git_dir().is_dir());
let work_dir = repo.work_dir().expect("non-bare");
assert!(work_dir.is_dir());

if precompose_unicode {
assert!(
matches!(
gix::utils::str::precompose_path(repo.git_dir().into()),
Cow::Borrowed(_),
),
"there is no change, as the path is already precomposed"
);
assert!(matches!(
gix::utils::str::precompose_path(work_dir.into()),
Cow::Borrowed(_),
));
} else {
assert!(
matches!(
gix::utils::str::precompose_path(repo.git_dir().into()),
Cow::Owned(_),
),
"this has an effect as the path isn't precomposed, a necessity on filesystems that don't fold decomposition"
);
assert!(matches!(
gix::utils::str::precompose_path(work_dir.into()),
Cow::Owned(_),
));
}

Ok(())
}

#[test]
fn bare_repo_with_index() -> crate::Result {
let repo = named_subrepo_opts(
Expand Down

0 comments on commit 7d8d167

Please sign in to comment.