Skip to content

Commit 16170d9

Browse files
committed
Merge branch 'discover-split-worktree'
2 parents 650461c + dd57957 commit 16170d9

File tree

20 files changed

+294
-55
lines changed

20 files changed

+294
-55
lines changed

gitoxide-core/src/discover.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::path::Path;
2+
3+
pub fn discover(repo: &Path, mut out: impl std::io::Write) -> anyhow::Result<()> {
4+
let mut has_err = false;
5+
writeln!(out, "open (strict) {}:", repo.display())?;
6+
has_err |= print_result(
7+
&mut out,
8+
gix::open_opts(repo, gix::open::Options::default().strict_config(true)),
9+
)?;
10+
11+
if has_err {
12+
writeln!(out, "open (lenient) {}:", repo.display())?;
13+
has_err |= print_result(
14+
&mut out,
15+
gix::open_opts(repo, gix::open::Options::default().strict_config(false)),
16+
)?;
17+
}
18+
19+
writeln!(out)?;
20+
writeln!(out, "discover from {}:", repo.display())?;
21+
has_err |= print_result(&mut out, gix::discover(repo))?;
22+
23+
writeln!(out)?;
24+
writeln!(out, "discover (plumbing) from {}:", repo.display())?;
25+
has_err |= print_result(&mut out, gix::discover::upwards(repo))?;
26+
27+
if has_err {
28+
writeln!(out)?;
29+
anyhow::bail!("At least one operation failed")
30+
}
31+
32+
Ok(())
33+
}
34+
35+
fn print_result<T, E>(mut out: impl std::io::Write, res: Result<T, E>) -> std::io::Result<bool>
36+
where
37+
T: std::fmt::Debug,
38+
E: std::error::Error + Send + Sync + 'static,
39+
{
40+
let mut has_err = false;
41+
let to_print = match res {
42+
Ok(good) => {
43+
format!("{good:#?}")
44+
}
45+
Err(err) => {
46+
has_err = true;
47+
format!("{:?}", anyhow::Error::from(err))
48+
}
49+
};
50+
indent(&mut out, to_print)?;
51+
Ok(has_err)
52+
}
53+
54+
fn indent(mut out: impl std::io::Write, msg: impl Into<String>) -> std::io::Result<()> {
55+
for line in msg.into().lines() {
56+
writeln!(out, "\t{line}")?;
57+
}
58+
Ok(())
59+
}

gitoxide-core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,8 @@ pub mod pack;
7979
pub mod query;
8080
pub mod repository;
8181

82+
mod discover;
83+
pub use discover::discover;
84+
8285
#[cfg(all(feature = "async-client", feature = "blocking-client"))]
8386
compile_error!("Cannot set both 'blocking-client' and 'async-client' features as they are mutually exclusive");

gix-discover/src/is.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,34 @@ fn bare_by_config(git_dir_candidate: &Path) -> std::io::Result<Option<bool>> {
2424
mod config {
2525
use bstr::{BStr, ByteSlice};
2626

27+
/// Note that we intentionally turn repositories that have a worktree configuration into bare repos,
28+
/// as we don't actually parse the worktree from the config file and expect the caller to do the right
29+
/// think when seemingly seeing bare repository.
30+
/// The reason we do this is to not incorrectly pretend this is a worktree.
2731
pub(crate) fn parse_bare(buf: &[u8]) -> Option<bool> {
28-
buf.lines().find_map(|line| {
29-
let line = line.trim().strip_prefix(b"bare")?;
30-
match line.first() {
31-
None => Some(true),
32-
Some(c) if *c == b'=' => parse_bool(line.get(1..)?.trim_start().as_bstr()),
33-
Some(c) if c.is_ascii_whitespace() => match line.split_once_str(b"=") {
34-
Some((_left, right)) => parse_bool(right.trim_start().as_bstr()),
35-
None => Some(true),
36-
},
37-
Some(_other_char_) => None,
32+
let mut is_bare = None;
33+
let mut has_worktree_configuration = false;
34+
for line in buf.lines() {
35+
if is_bare.is_none() {
36+
if let Some(line) = line.trim().strip_prefix(b"bare") {
37+
is_bare = match line.first() {
38+
None => Some(true),
39+
Some(c) if *c == b'=' => parse_bool(line.get(1..)?.trim_start().as_bstr()),
40+
Some(c) if c.is_ascii_whitespace() => match line.split_once_str(b"=") {
41+
Some((_left, right)) => parse_bool(right.trim_start().as_bstr()),
42+
None => Some(true),
43+
},
44+
Some(_other_char_) => None,
45+
};
46+
continue;
47+
}
3848
}
39-
})
49+
if line.trim().strip_prefix(b"worktree").is_some() {
50+
has_worktree_configuration = true;
51+
break;
52+
}
53+
}
54+
is_bare.map(|bare| bare || has_worktree_configuration)
4055
}
4156

4257
fn parse_bool(value: &BStr) -> Option<bool> {
@@ -233,7 +248,7 @@ pub(crate) fn git_with_metadata(
233248
Cow::Borrowed(git_dir)
234249
};
235250
if bare(conformed_git_dir.as_ref()) || conformed_git_dir.extension() == Some(OsStr::new("git")) {
236-
crate::repository::Kind::Bare
251+
crate::repository::Kind::PossiblyBare
237252
} else if submodule_git_dir(conformed_git_dir.as_ref()) {
238253
crate::repository::Kind::SubmoduleGitDir
239254
} else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR))
@@ -246,7 +261,7 @@ pub(crate) fn git_with_metadata(
246261
{
247262
crate::repository::Kind::WorkTree { linked_git_dir: None }
248263
} else {
249-
crate::repository::Kind::Bare
264+
crate::repository::Kind::PossiblyBare
250265
}
251266
}
252267
})

gix-discover/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub mod is_git {
3737
GitFile(#[from] crate::path::from_gitdir_file::Error),
3838
#[error("Could not retrieve metadata of \"{path}\"")]
3939
Metadata { source: std::io::Error, path: PathBuf },
40-
#[error("The repository's config file doesn't exist or didn't have a 'bare' configuration")]
40+
#[error("The repository's config file doesn't exist or didn't have a 'bare' configuration or contained core.worktree without value")]
4141
Inconclusive,
4242
}
4343
}

gix-discover/src/repository.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ mod path {
7878
Path::WorkTree(work_dir)
7979
}
8080
},
81-
Kind::Bare => Path::Repository(dir),
81+
Kind::PossiblyBare => Path::Repository(dir),
8282
}
8383
.into()
8484
}
@@ -89,7 +89,7 @@ mod path {
8989
linked_git_dir: Some(git_dir.to_owned()),
9090
},
9191
Path::WorkTree(_) => Kind::WorkTree { linked_git_dir: None },
92-
Path::Repository(_) => Kind::Bare,
92+
Path::Repository(_) => Kind::PossiblyBare,
9393
}
9494
}
9595

@@ -110,7 +110,11 @@ pub enum Kind {
110110
/// A bare repository does not have a work tree, that is files on disk beyond the `git` repository itself.
111111
///
112112
/// Note that this is merely a guess at this point as we didn't read the configuration yet.
113-
Bare,
113+
///
114+
/// Also note that due to optimizing for performance and *just* making an educated *guess in some situations*,
115+
/// we may consider a non-bare repository bare if it it doesn't have an index yet due to be freshly initialized.
116+
/// The caller is has to handle this, typically by reading the configuration.
117+
PossiblyBare,
114118
/// A `git` repository along with checked out files in a work tree.
115119
WorkTree {
116120
/// If set, this is the git dir associated with this _linked_ worktree.
@@ -135,6 +139,6 @@ pub enum Kind {
135139
impl Kind {
136140
/// Returns true if this is a bare repository, one without a work tree.
137141
pub fn is_bare(&self) -> bool {
138-
matches!(self, Kind::Bare)
142+
matches!(self, Kind::PossiblyBare)
139143
}
140144
}

gix-discover/tests/fixtures/make_basic_repo.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,25 @@ git init non-bare-without-index
7070
git commit -m "init"
7171
rm .git/index
7272
)
73+
74+
git --git-dir=repo-with-worktree-in-config-unborn-no-worktreedir --work-tree=does-not-exist-yet init
75+
worktree=repo-with-worktree-in-config-unborn-worktree
76+
git --git-dir=repo-with-worktree-in-config-unborn --work-tree=$worktree init && mkdir $worktree
77+
78+
repo=repo-with-worktree-in-config-unborn-empty-worktreedir
79+
git --git-dir=$repo --work-tree="." init
80+
touch $repo/index
81+
git -C $repo config core.worktree ''
82+
83+
repo=repo-with-worktree-in-config-unborn-worktreedir-missing-value
84+
git --git-dir=$repo init
85+
touch $repo/index
86+
echo " worktree" >> $repo/config
87+
88+
worktree=repo-with-worktree-in-config-worktree
89+
git --git-dir=repo-with-worktree-in-config --work-tree=$worktree init
90+
mkdir $worktree && touch $worktree/file
91+
(cd repo-with-worktree-in-config
92+
git add file
93+
git commit -m "make sure na index exists"
94+
)

gix-discover/tests/is_git/mod.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_bare_repo() -> crate::Resu
4545
for name in ["bare-no-config-after-init.git", "bare-no-config.git"] {
4646
let repo = repo_path()?.join(name);
4747
let kind = gix_discover::is_git(&repo)?;
48-
assert_eq!(kind, gix_discover::repository::Kind::Bare);
48+
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
4949
}
5050
Ok(())
5151
}
@@ -54,7 +54,7 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_bare_repo() -> crate::Resu
5454
fn bare_repo_with_index_file_looks_still_looks_like_bare() -> crate::Result {
5555
let repo = repo_path()?.join("bare-with-index.git");
5656
let kind = gix_discover::is_git(&repo)?;
57-
assert_eq!(kind, gix_discover::repository::Kind::Bare);
57+
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
5858
Ok(())
5959
}
6060

@@ -63,7 +63,7 @@ fn bare_repo_with_index_file_looks_still_looks_like_bare_if_it_was_renamed() ->
6363
for repo_name in ["bare-with-index-bare", "bare-with-index-no-config-bare"] {
6464
let repo = repo_path()?.join(repo_name);
6565
let kind = gix_discover::is_git(&repo)?;
66-
assert_eq!(kind, gix_discover::repository::Kind::Bare);
66+
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
6767
}
6868
Ok(())
6969
}
@@ -85,3 +85,24 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_nonbare_repo() -> crate::R
8585
}
8686
Ok(())
8787
}
88+
89+
#[test]
90+
fn split_worktree_using_configuration() -> crate::Result {
91+
for name in [
92+
"repo-with-worktree-in-config",
93+
"repo-with-worktree-in-config-unborn",
94+
"repo-with-worktree-in-config-unborn-no-worktreedir",
95+
"repo-with-worktree-in-config-unborn-empty-worktreedir",
96+
"repo-with-worktree-in-config-unborn-worktreedir-missing-value",
97+
] {
98+
let repo = repo_path()?.join(name);
99+
let kind = gix_discover::is_git(&repo)?;
100+
assert_eq!(
101+
kind,
102+
gix_discover::repository::Kind::PossiblyBare,
103+
"{name}: we think these are bare as we don't read the configuration in this case - \
104+
a shortcoming to favor performance which still comes out correct in `gix`"
105+
);
106+
}
107+
Ok(())
108+
}

gix-discover/tests/isolated.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fn upwards_bare_repo_with_index() -> gix_testtools::Result {
1212
let (repo_path, _trust) = gix_discover::upwards(".".as_ref())?;
1313
assert_eq!(
1414
repo_path.kind(),
15-
gix_discover::repository::Kind::Bare,
15+
gix_discover::repository::Kind::PossiblyBare,
1616
"bare stays bare, even with index, as it resolves the path as needed in this special case"
1717
);
1818
Ok(())
@@ -25,7 +25,7 @@ fn in_cwd_upwards_bare_repo_without_index() -> gix_testtools::Result {
2525

2626
let _keep = gix_testtools::set_current_dir(repo.join("bare.git"))?;
2727
let (repo_path, _trust) = gix_discover::upwards(".".as_ref())?;
28-
assert_eq!(repo_path.kind(), gix_discover::repository::Kind::Bare);
28+
assert_eq!(repo_path.kind(), gix_discover::repository::Kind::PossiblyBare);
2929
Ok(())
3030
}
3131

gix-discover/tests/upwards/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn from_bare_git_dir() -> crate::Result {
1717
let dir = repo_path()?.join("bare.git");
1818
let (path, trust) = gix_discover::upwards(&dir)?;
1919
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
20-
assert_eq!(path.kind(), Kind::Bare);
20+
assert_eq!(path.kind(), Kind::PossiblyBare);
2121
assert_eq!(trust, expected_trust());
2222
Ok(())
2323
}
@@ -27,7 +27,7 @@ fn from_bare_with_index() -> crate::Result {
2727
let dir = repo_path()?.join("bare-with-index.git");
2828
let (path, trust) = gix_discover::upwards(&dir)?;
2929
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
30-
assert_eq!(path.kind(), Kind::Bare);
30+
assert_eq!(path.kind(), Kind::PossiblyBare);
3131
assert_eq!(trust, expected_trust());
3232
Ok(())
3333
}
@@ -48,7 +48,7 @@ fn from_bare_git_dir_without_config_file() -> crate::Result {
4848
let dir = repo_path()?.join(name);
4949
let (path, trust) = gix_discover::upwards(&dir)?;
5050
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
51-
assert_eq!(path.kind(), Kind::Bare);
51+
assert_eq!(path.kind(), Kind::PossiblyBare);
5252
assert_eq!(trust, expected_trust());
5353
}
5454
Ok(())
@@ -64,7 +64,7 @@ fn from_inside_bare_git_dir() -> crate::Result {
6464
git_dir,
6565
"the bare .git dir is found while traversing upwards"
6666
);
67-
assert_eq!(path.kind(), Kind::Bare);
67+
assert_eq!(path.kind(), Kind::PossiblyBare);
6868
assert_eq!(trust, expected_trust());
6969
Ok(())
7070
}

gix/src/config/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,11 @@ pub enum Error {
9191
ResolveIncludes(#[from] gix_config::file::includes::Error),
9292
#[error(transparent)]
9393
FromEnv(#[from] gix_config::file::init::from_env::Error),
94-
#[error(transparent)]
95-
PathInterpolation(#[from] gix_config::path::interpolate::Error),
94+
#[error("The path {path:?} at the 'core.worktree' configuration could not be interpolated")]
95+
PathInterpolation {
96+
path: BString,
97+
source: gix_config::path::interpolate::Error,
98+
},
9699
#[error("{source:?} configuration overrides at open or init time could not be applied.")]
97100
ConfigOverrides {
98101
#[source]

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: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,27 @@ impl ThreadSafeRepository {
237237
.resolved
238238
.path_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
239239
{
240+
let wt_clone = wt.clone();
240241
let wt_path = wt
241242
.interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref()))
242-
.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+
.map_err(|err| config::Error::PathInterpolation {
244+
path: wt_clone.value.into_owned(),
245+
source: err,
246+
})?;
247+
worktree_dir = gix_path::normalize(git_dir.join(wt_path).into(), current_dir).map(Cow::into_owned);
248+
#[allow(unused_variables)]
249+
if let Some(worktree_path) = worktree_dir.as_deref().filter(|wtd| !wtd.is_dir()) {
250+
gix_trace::warn!("The configured worktree path '{}' is not a directory or doesn't exist - `core.worktree` may be misleading", worktree_path.display());
246251
}
252+
} else if !config.lenient_config
253+
&& config
254+
.resolved
255+
.boolean_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
256+
.is_some()
257+
{
258+
return Err(Error::from(config::Error::ConfigTypedString(
259+
config::key::GenericErrorWithValue::from(&Core::WORKTREE),
260+
)));
247261
}
248262
}
249263

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(),

0 commit comments

Comments
 (0)