Skip to content

Commit 20f962e

Browse files
committed
fix: allow to open split worktree repositories
1 parent e9295dc commit 20f962e

File tree

8 files changed

+121
-25
lines changed

8 files changed

+121
-25
lines changed

gix/src/create.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ pub fn into(
230230
Ok(gix_discover::repository::Path::from_dot_git_dir(
231231
dot_git,
232232
if bare {
233-
gix_discover::repository::Kind::Bare
233+
gix_discover::repository::Kind::PossiblyBare
234234
} else {
235235
gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
236236
},

gix/src/open/repository.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,20 @@ impl ThreadSafeRepository {
240240
let wt_path = wt
241241
.interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref()))
242242
.map_err(config::Error::PathInterpolation)?;
243-
worktree_dir = {
244-
gix_path::normalize(git_dir.join(wt_path).into(), current_dir)
245-
.and_then(|wt| wt.as_ref().is_dir().then(|| wt.into_owned()))
243+
worktree_dir = gix_path::normalize(git_dir.join(wt_path).into(), current_dir).map(Cow::into_owned);
244+
#[allow(unused_variables)]
245+
if let Some(worktree_path) = worktree_dir.as_deref().filter(|wtd| !wtd.is_dir()) {
246+
gix_trace::warn!("The configured worktree path '{}' is not a directory or doesn't exist - `core.worktree` may be misleading", worktree_path.display());
246247
}
248+
} else if !config.lenient_config
249+
&& config
250+
.resolved
251+
.boolean_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
252+
.is_some()
253+
{
254+
return Err(Error::from(config::Error::ConfigTypedString(
255+
config::key::GenericErrorWithValue::from(&Core::WORKTREE),
256+
)));
247257
}
248258
}
249259

gix/src/repository/kind.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl From<gix_discover::repository::Kind> for Kind {
1313
gix_discover::repository::Kind::Submodule { .. } | gix_discover::repository::Kind::SubmoduleGitDir => {
1414
Kind::WorkTree { is_linked: false }
1515
}
16-
gix_discover::repository::Kind::Bare => Kind::Bare,
16+
gix_discover::repository::Kind::PossiblyBare => Kind::Bare,
1717
gix_discover::repository::Kind::WorkTreeGitDir { .. } => Kind::WorkTree { is_linked: true },
1818
gix_discover::repository::Kind::WorkTree { linked_git_dir } => Kind::WorkTree {
1919
is_linked: linked_git_dir.is_some(),

gix/tests/fixtures/make_worktree_repo.sh

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,42 @@ mkdir repo
1717
git commit -q -am c2
1818
)
1919

20-
if [ "$bare" == "bare" ]; then
20+
(if [ "$bare" == "bare" ]; then
2121
git clone --bare --shared repo repo.git
2222
cd repo.git
2323
else
2424
cd repo
2525
fi
2626

27-
git worktree add ../wt-a
28-
git worktree add ../prev/wt-a HEAD~1
29-
git worktree add ../wt-b HEAD~1
30-
git worktree add ../wt-a/nested-wt-b HEAD~1
31-
git worktree add --lock ../wt-c-locked
32-
git worktree add ../wt-deleted && rm -Rf ../wt-deleted
27+
git worktree add ../wt-a
28+
git worktree add ../prev/wt-a HEAD~1
29+
git worktree add ../wt-b HEAD~1
30+
git worktree add ../wt-a/nested-wt-b HEAD~1
31+
git worktree add --lock ../wt-c-locked
32+
git worktree add ../wt-deleted && rm -Rf ../wt-deleted
3333

34-
git worktree list --porcelain > ../worktree-list.baseline
34+
git worktree list --porcelain > ../worktree-list.baseline
35+
)
36+
37+
38+
git --git-dir=repo-with-worktree-in-config-unborn-no-worktreedir --work-tree=does-not-exist-yet init
39+
worktree=repo-with-worktree-in-config-unborn-worktree
40+
git --git-dir=repo-with-worktree-in-config-unborn --work-tree=$worktree init && mkdir $worktree
41+
42+
repo=repo-with-worktree-in-config-unborn-empty-worktreedir
43+
git --git-dir=$repo --work-tree="." init
44+
git -C $repo config core.worktree ''
45+
46+
repo=repo-with-worktree-in-config-unborn-worktreedir-missing-value
47+
git --git-dir=$repo init
48+
touch $repo/index
49+
git -C $repo config core.bare false
50+
echo " worktree" >> $repo/config
51+
52+
worktree=repo-with-worktree-in-config-worktree
53+
git --git-dir=repo-with-worktree-in-config --work-tree=$worktree init
54+
mkdir $worktree && touch $worktree/file
55+
(cd repo-with-worktree-in-config
56+
git add file
57+
git commit -m "make sure na index exists"
58+
)

gix/tests/repository/open.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::util::named_subrepo_opts;
2+
use std::error::Error;
23

34
#[test]
45
fn bare_repo_with_index() -> crate::Result {
@@ -26,6 +27,59 @@ fn none_bare_repo_without_index() -> crate::Result {
2627
Ok(())
2728
}
2829

30+
#[test]
31+
fn non_bare_split_worktree() -> crate::Result {
32+
for (name, worktree_exists) in [
33+
("repo-with-worktree-in-config-unborn-no-worktreedir", false),
34+
("repo-with-worktree-in-config-unborn", true),
35+
("repo-with-worktree-in-config", true),
36+
] {
37+
let repo = named_subrepo_opts("make_worktree_repo.sh", name, gix::open::Options::isolated())?;
38+
assert!(repo.git_dir().is_dir());
39+
assert!(
40+
!repo.is_bare(),
41+
"worktree is actually configured, and it's non-bare by configuration"
42+
);
43+
assert_eq!(
44+
repo.work_dir().expect("worktree is configured").is_dir(),
45+
worktree_exists
46+
);
47+
}
48+
Ok(())
49+
}
50+
51+
#[test]
52+
fn non_bare_split_worktree_invalid_worktree_path_boolean() -> crate::Result {
53+
let err = named_subrepo_opts(
54+
"make_worktree_repo.sh",
55+
"repo-with-worktree-in-config-unborn-worktreedir-missing-value",
56+
gix::open::Options::isolated().strict_config(true),
57+
)
58+
.unwrap_err();
59+
assert_eq!(
60+
err.source().expect("present").to_string(),
61+
"The key \"core.worktree\" (possibly from GIT_WORK_TREE) was invalid",
62+
"in strict mode, we fail just like git does"
63+
);
64+
Ok(())
65+
}
66+
67+
#[test]
68+
fn non_bare_split_worktree_invalid_worktree_path_empty() -> crate::Result {
69+
// "repo-with-worktree-in-config-unborn-worktreedir-missing-value",
70+
let err = named_subrepo_opts(
71+
"make_worktree_repo.sh",
72+
"repo-with-worktree-in-config-unborn-empty-worktreedir",
73+
gix::open::Options::isolated(),
74+
)
75+
.unwrap_err();
76+
assert!(
77+
matches!(err, gix::open::Error::Config(gix::config::Error::PathInterpolation(_))),
78+
"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"
79+
);
80+
Ok(())
81+
}
82+
2983
mod missing_config_file {
3084

3185
use crate::util::named_subrepo_opts;

gix/tests/repository/worktree.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ mod with_core_worktree_config {
9191
"git can't chdir into missing worktrees, has no error handling there"
9292
);
9393

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

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

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

113113
#[test]

gix/tests/submodule/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
pub fn repo(name: &str) -> crate::Result<gix::Repository> {
22
use crate::util::named_subrepo_opts;
3-
named_subrepo_opts("make_submodules.sh", name, gix::open::Options::isolated())
3+
Ok(named_subrepo_opts(
4+
"make_submodules.sh",
5+
name,
6+
gix::open::Options::isolated(),
7+
)?)
48
}
59

610
mod open {

gix/tests/util/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ pub fn named_repo(name: &str) -> Result<Repository> {
3434
Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local())
3535
}
3636

37-
pub fn named_subrepo_opts(fixture: &str, name: &str, opts: open::Options) -> Result<Repository> {
38-
let repo_path = gix_testtools::scripted_fixture_read_only(fixture)?.join(name);
37+
pub fn named_subrepo_opts(
38+
fixture: &str,
39+
name: &str,
40+
opts: open::Options,
41+
) -> std::result::Result<Repository, gix::open::Error> {
42+
let repo_path = gix_testtools::scripted_fixture_read_only(fixture).unwrap().join(name);
3943
Ok(ThreadSafeRepository::open_opts(repo_path, opts)?.to_thread_local())
4044
}
4145

0 commit comments

Comments
 (0)