Skip to content

Commit

Permalink
fix: allow to open split worktree repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Oct 16, 2023
1 parent e9295dc commit 20f962e
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 25 deletions.
2 changes: 1 addition & 1 deletion gix/src/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ pub fn into(
Ok(gix_discover::repository::Path::from_dot_git_dir(
dot_git,
if bare {
gix_discover::repository::Kind::Bare
gix_discover::repository::Kind::PossiblyBare
} else {
gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
},
Expand Down
16 changes: 13 additions & 3 deletions gix/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,20 @@ impl ThreadSafeRepository {
let wt_path = wt
.interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref()))
.map_err(config::Error::PathInterpolation)?;
worktree_dir = {
gix_path::normalize(git_dir.join(wt_path).into(), current_dir)
.and_then(|wt| wt.as_ref().is_dir().then(|| wt.into_owned()))
worktree_dir = gix_path::normalize(git_dir.join(wt_path).into(), current_dir).map(Cow::into_owned);
#[allow(unused_variables)]
if let Some(worktree_path) = worktree_dir.as_deref().filter(|wtd| !wtd.is_dir()) {
gix_trace::warn!("The configured worktree path '{}' is not a directory or doesn't exist - `core.worktree` may be misleading", worktree_path.display());
}
} else if !config.lenient_config
&& config
.resolved
.boolean_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
.is_some()
{
return Err(Error::from(config::Error::ConfigTypedString(
config::key::GenericErrorWithValue::from(&Core::WORKTREE),
)));
}
}

Expand Down
2 changes: 1 addition & 1 deletion gix/src/repository/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl From<gix_discover::repository::Kind> for Kind {
gix_discover::repository::Kind::Submodule { .. } | gix_discover::repository::Kind::SubmoduleGitDir => {
Kind::WorkTree { is_linked: false }
}
gix_discover::repository::Kind::Bare => Kind::Bare,
gix_discover::repository::Kind::PossiblyBare => Kind::Bare,
gix_discover::repository::Kind::WorkTreeGitDir { .. } => Kind::WorkTree { is_linked: true },
gix_discover::repository::Kind::WorkTree { linked_git_dir } => Kind::WorkTree {
is_linked: linked_git_dir.is_some(),
Expand Down
40 changes: 32 additions & 8 deletions gix/tests/fixtures/make_worktree_repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,42 @@ mkdir repo
git commit -q -am c2
)

if [ "$bare" == "bare" ]; then
(if [ "$bare" == "bare" ]; then
git clone --bare --shared repo repo.git
cd repo.git
else
cd repo
fi

git worktree add ../wt-a
git worktree add ../prev/wt-a HEAD~1
git worktree add ../wt-b HEAD~1
git worktree add ../wt-a/nested-wt-b HEAD~1
git worktree add --lock ../wt-c-locked
git worktree add ../wt-deleted && rm -Rf ../wt-deleted
git worktree add ../wt-a
git worktree add ../prev/wt-a HEAD~1
git worktree add ../wt-b HEAD~1
git worktree add ../wt-a/nested-wt-b HEAD~1
git worktree add --lock ../wt-c-locked
git worktree add ../wt-deleted && rm -Rf ../wt-deleted

git worktree list --porcelain > ../worktree-list.baseline
git worktree list --porcelain > ../worktree-list.baseline
)


git --git-dir=repo-with-worktree-in-config-unborn-no-worktreedir --work-tree=does-not-exist-yet init
worktree=repo-with-worktree-in-config-unborn-worktree
git --git-dir=repo-with-worktree-in-config-unborn --work-tree=$worktree init && mkdir $worktree

repo=repo-with-worktree-in-config-unborn-empty-worktreedir
git --git-dir=$repo --work-tree="." init
git -C $repo config core.worktree ''

repo=repo-with-worktree-in-config-unborn-worktreedir-missing-value
git --git-dir=$repo init
touch $repo/index
git -C $repo config core.bare false
echo " worktree" >> $repo/config

worktree=repo-with-worktree-in-config-worktree
git --git-dir=repo-with-worktree-in-config --work-tree=$worktree init
mkdir $worktree && touch $worktree/file
(cd repo-with-worktree-in-config
git add file
git commit -m "make sure na index exists"
)
54 changes: 54 additions & 0 deletions gix/tests/repository/open.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::util::named_subrepo_opts;
use std::error::Error;

#[test]
fn bare_repo_with_index() -> crate::Result {
Expand Down Expand Up @@ -26,6 +27,59 @@ fn none_bare_repo_without_index() -> crate::Result {
Ok(())
}

#[test]
fn non_bare_split_worktree() -> crate::Result {
for (name, worktree_exists) in [
("repo-with-worktree-in-config-unborn-no-worktreedir", false),
("repo-with-worktree-in-config-unborn", true),
("repo-with-worktree-in-config", true),
] {
let repo = named_subrepo_opts("make_worktree_repo.sh", name, gix::open::Options::isolated())?;
assert!(repo.git_dir().is_dir());
assert!(
!repo.is_bare(),
"worktree is actually configured, and it's non-bare by configuration"
);
assert_eq!(
repo.work_dir().expect("worktree is configured").is_dir(),
worktree_exists
);
}
Ok(())
}

#[test]
fn non_bare_split_worktree_invalid_worktree_path_boolean() -> crate::Result {
let err = named_subrepo_opts(
"make_worktree_repo.sh",
"repo-with-worktree-in-config-unborn-worktreedir-missing-value",
gix::open::Options::isolated().strict_config(true),
)
.unwrap_err();
assert_eq!(
err.source().expect("present").to_string(),
"The key \"core.worktree\" (possibly from GIT_WORK_TREE) was invalid",
"in strict mode, we fail just like git does"
);
Ok(())
}

#[test]
fn non_bare_split_worktree_invalid_worktree_path_empty() -> crate::Result {
// "repo-with-worktree-in-config-unborn-worktreedir-missing-value",
let err = named_subrepo_opts(
"make_worktree_repo.sh",
"repo-with-worktree-in-config-unborn-empty-worktreedir",
gix::open::Options::isolated(),
)
.unwrap_err();
assert!(
matches!(err, gix::open::Error::Config(gix::config::Error::PathInterpolation(_))),
"DEVIATION: could not read path at core.worktree as empty is always invalid, git tries to use an empty path, even though it's better to reject it"
);
Ok(())
}

mod missing_config_file {

use crate::util::named_subrepo_opts;
Expand Down
18 changes: 9 additions & 9 deletions gix/tests/repository/worktree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ mod with_core_worktree_config {
"git can't chdir into missing worktrees, has no error handling there"
);

assert_eq!(
repo.work_dir(),
repo.git_dir().parent(),
"we just ignore missing configured worktree dirs and fall back to the default one"
assert!(
!repo.work_dir().expect("configured").exists(),
"non-existing or invalid worktrees (this one is a file) are taken verbatim and \
may lead to errors later - just like in `git` and we explicitly do not try to be smart about it"
)
}

Expand All @@ -103,11 +103,11 @@ mod with_core_worktree_config {
let repo = repo("relative-worktree-file");
assert_eq!(count_deleted(repo.git_dir()), 0, "git can't chdir into a file");

assert_eq!(
repo.work_dir(),
repo.git_dir().parent(),
"we just ignore missing configured worktree dirs and fall back to the default one"
)
assert!(
repo.work_dir().expect("configured").is_file(),
"non-existing or invalid worktrees (this one is a file) are taken verbatim and \
may lead to errors later - just like in `git` and we explicitly do not try to be smart about it"
);
}

#[test]
Expand Down
6 changes: 5 additions & 1 deletion gix/tests/submodule/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
pub fn repo(name: &str) -> crate::Result<gix::Repository> {
use crate::util::named_subrepo_opts;
named_subrepo_opts("make_submodules.sh", name, gix::open::Options::isolated())
Ok(named_subrepo_opts(
"make_submodules.sh",
name,
gix::open::Options::isolated(),
)?)
}

mod open {
Expand Down
8 changes: 6 additions & 2 deletions gix/tests/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ pub fn named_repo(name: &str) -> Result<Repository> {
Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local())
}

pub fn named_subrepo_opts(fixture: &str, name: &str, opts: open::Options) -> Result<Repository> {
let repo_path = gix_testtools::scripted_fixture_read_only(fixture)?.join(name);
pub fn named_subrepo_opts(
fixture: &str,
name: &str,
opts: open::Options,
) -> std::result::Result<Repository, gix::open::Error> {
let repo_path = gix_testtools::scripted_fixture_read_only(fixture).unwrap().join(name);
Ok(ThreadSafeRepository::open_opts(repo_path, opts)?.to_thread_local())
}

Expand Down

0 comments on commit 20f962e

Please sign in to comment.