Skip to content

Commit 6487269

Browse files
authored
Merge pull request #1620 from Byron/fix-discovery
fix discovery
2 parents 37c1e4c + c18ebbe commit 6487269

File tree

18 files changed

+102
-123
lines changed

18 files changed

+102
-123
lines changed

Diff for: gix-discover/src/is.rs

+17-112
Original file line numberDiff line numberDiff line change
@@ -9,107 +9,6 @@ pub fn bare(git_dir_candidate: &Path) -> bool {
99
!(git_dir_candidate.join("index").exists() || (git_dir_candidate.file_name() == Some(OsStr::new(DOT_GIT_DIR))))
1010
}
1111

12-
/// Parse `<git_dir_candidate>/config` quickly to evaluate the value of the `bare` line, or return `true` if the file doesn't exist
13-
/// similar to what`guess_repository_type` seems to be doing.
14-
/// Return `None` if the `bare` line can't be found or the value of `bare` can't be determined.
15-
fn bare_by_config(git_dir_candidate: &Path) -> std::io::Result<Option<bool>> {
16-
match std::fs::read(git_dir_candidate.join("config")) {
17-
Ok(buf) => Ok(config::parse_bare(&buf)),
18-
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Some(true)),
19-
Err(err) => Err(err),
20-
}
21-
}
22-
23-
// Copied and adapted from `gix-config-value::boolean`.
24-
mod config {
25-
use bstr::{BStr, ByteSlice};
26-
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.
31-
pub(crate) fn parse_bare(buf: &[u8]) -> Option<bool> {
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-
}
48-
}
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)
55-
}
56-
57-
fn parse_bool(value: &BStr) -> Option<bool> {
58-
Some(if parse_true(value) {
59-
true
60-
} else if parse_false(value) {
61-
false
62-
} else {
63-
use std::str::FromStr;
64-
if let Some(integer) = value.to_str().ok().and_then(|s| i64::from_str(s).ok()) {
65-
integer != 0
66-
} else {
67-
return None;
68-
}
69-
})
70-
}
71-
72-
fn parse_true(value: &BStr) -> bool {
73-
value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true")
74-
}
75-
76-
fn parse_false(value: &BStr) -> bool {
77-
value.eq_ignore_ascii_case(b"no")
78-
|| value.eq_ignore_ascii_case(b"off")
79-
|| value.eq_ignore_ascii_case(b"false")
80-
|| value.is_empty()
81-
}
82-
83-
#[cfg(test)]
84-
mod tests {
85-
use super::*;
86-
87-
#[test]
88-
fn various() {
89-
for (input, expected) in [
90-
("bare=true", Some(true)),
91-
("bare=1", Some(true)),
92-
("bare =1", Some(true)),
93-
("bare= yes", Some(true)),
94-
("bare=false", Some(false)),
95-
("bare=0", Some(false)),
96-
("bare=blah", None),
97-
("bare=", Some(false)),
98-
("bare= \n", Some(false)),
99-
("bare = true \n", Some(true)),
100-
("\t bare = false \n", Some(false)),
101-
("\n\tbare=true", Some(true)),
102-
("\n\tbare=true\n\tfoo", Some(true)),
103-
("\n\tbare ", Some(true)),
104-
("\n\tbare", Some(true)),
105-
("not found\nreally", None),
106-
] {
107-
assert_eq!(parse_bare(input.as_bytes()), expected, "{input:?}");
108-
}
109-
}
110-
}
111-
}
112-
11312
/// Returns true if `git_dir` is located within a `.git/modules` directory, indicating it's a submodule clone.
11413
pub fn submodule_git_dir(git_dir: &Path) -> bool {
11514
let mut last_comp = None;
@@ -141,12 +40,15 @@ pub fn git(git_dir: &Path) -> Result<crate::repository::Kind, crate::is_git::Err
14140
source: err,
14241
path: git_dir.into(),
14342
})?;
144-
git_with_metadata(git_dir, git_dir_metadata)
43+
// precompose-unicode can't be known here, so we just default it to false, hoping it won't matter.
44+
let cwd = gix_fs::current_dir(false)?;
45+
git_with_metadata(git_dir, git_dir_metadata, &cwd)
14546
}
14647

14748
pub(crate) fn git_with_metadata(
14849
git_dir: &Path,
14950
git_dir_metadata: std::fs::Metadata,
51+
cwd: &Path,
15052
) -> Result<crate::repository::Kind, crate::is_git::Error> {
15153
#[derive(Eq, PartialEq)]
15254
enum Kind {
@@ -166,6 +68,8 @@ pub(crate) fn git_with_metadata(
16668
{
16769
// Fast-path: avoid doing the complete search if HEAD is already not there.
16870
// TODO(reftable): use a ref-store to lookup HEAD if ref-tables should be supported, or detect ref-tables beforehand.
71+
// Actually ref-tables still keep a specially marked `HEAD` around, so nothing might be needed here
72+
// Even though our head-check later would fail without supporting it.
16973
if !dot_git.join("HEAD").exists() {
17074
return Err(crate::is_git::Error::MissingHead);
17175
}
@@ -236,25 +140,26 @@ pub(crate) fn git_with_metadata(
236140
},
237141
Kind::MaybeRepo => {
238142
let conformed_git_dir = if git_dir == Path::new(".") {
239-
gix_path::realpath(git_dir)
143+
gix_path::realpath_opts(git_dir, cwd, gix_path::realpath::MAX_SYMLINKS)
240144
.map(Cow::Owned)
241145
.unwrap_or(Cow::Borrowed(git_dir))
242146
} else {
243-
Cow::Borrowed(git_dir)
147+
gix_path::normalize(git_dir.into(), cwd).unwrap_or(Cow::Borrowed(git_dir))
244148
};
245149
if bare(conformed_git_dir.as_ref()) || conformed_git_dir.extension() == Some(OsStr::new("git")) {
246150
crate::repository::Kind::PossiblyBare
247151
} else if submodule_git_dir(conformed_git_dir.as_ref()) {
248152
crate::repository::Kind::SubmoduleGitDir
249-
} else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR))
250-
|| !bare_by_config(conformed_git_dir.as_ref())
251-
.map_err(|err| crate::is_git::Error::Metadata {
252-
source: err,
253-
path: conformed_git_dir.join("config"),
254-
})?
255-
.ok_or(crate::is_git::Error::Inconclusive)?
256-
{
153+
} else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR)) {
257154
crate::repository::Kind::WorkTree { linked_git_dir: None }
155+
// } else if !bare_by_config(conformed_git_dir.as_ref())
156+
// .map_err(|err| crate::is_git::Error::Metadata {
157+
// source: err,
158+
// path: conformed_git_dir.join("config"),
159+
// })?
160+
// .ok_or(crate::is_git::Error::Inconclusive)?
161+
// {
162+
// crate::repository::Kind::WorktreePossiblyInConfiguration
258163
} else {
259164
crate::repository::Kind::PossiblyBare
260165
}

Diff for: gix-discover/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub mod is_git {
3939
Metadata { source: std::io::Error, path: PathBuf },
4040
#[error("The repository's config file doesn't exist or didn't have a 'bare' configuration or contained core.worktree without value")]
4141
Inconclusive,
42+
#[error("Could not obtain current directory when conforming repository path")]
43+
CurrentDir(#[from] std::io::Error),
4244
}
4345
}
4446

Diff for: gix-discover/src/repository.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ pub enum Path {
1414
/// The currently checked out or nascent work tree of a git repository
1515
WorkTree(PathBuf),
1616
/// The git repository itself, typically bare and without known worktree.
17+
/// It could also be non-bare with a worktree configured using git configuration, or no worktree at all despite
18+
/// not being bare (due to mis-configuration for example).
1719
///
18-
/// Note that it might still have linked work-trees which can be accessed later, weather bare or not, or it might be a
20+
/// Note that it might still have linked work-trees which can be accessed later, bare or not, or it might be a
1921
/// submodule git directory in the `.git/modules/**/<name>` directory of the parent repository.
2022
Repository(PathBuf),
2123
}
@@ -112,8 +114,11 @@ pub enum Kind {
112114
/// Note that this is merely a guess at this point as we didn't read the configuration yet.
113115
///
114116
/// 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+
/// we may consider a non-bare repository bare if it doesn't have an index yet due to be freshly initialized.
118+
/// The caller has to handle this, typically by reading the configuration.
119+
///
120+
/// It could also be a directory which is non-bare by configuration, but is *not* named `.git`.
121+
/// Unusual, but it's possible that a worktree is configured via `core.worktree`.
117122
PossiblyBare,
118123
/// A `git` repository along with checked out files in a work tree.
119124
WorkTree {

Diff for: gix-discover/src/upwards/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ pub(crate) mod function {
3838
// Normalize the path so that `Path::parent()` _actually_ gives
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
41-
// working with paths paths that contain '..'.)
41+
// working with paths that contain '..'.)
4242
let cwd = current_dir.map_or_else(
4343
|| {
4444
// 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
45+
// what `core.precomposeUnicode` is going to be. Hence, the one using these paths will have to
4646
// transform the paths as needed, because we can't. `false` means to leave the obtained path as is.
4747
gix_fs::current_dir(false).map(Cow::Owned)
4848
},
@@ -130,7 +130,7 @@ pub(crate) mod function {
130130
cursor_metadata_backup = cursor_metadata.take();
131131
}
132132
if let Ok(kind) = match cursor_metadata.take() {
133-
Some(metadata) => is_git_with_metadata(&cursor, metadata),
133+
Some(metadata) => is_git_with_metadata(&cursor, metadata, &cwd),
134134
None => is_git(&cursor),
135135
} {
136136
match filter_by_trust(&cursor)? {

Diff for: gix-discover/tests/is_git/mod.rs renamed to gix-discover/tests/discover/is_git/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@ fn bare_repo_with_index_file_looks_still_looks_like_bare() -> crate::Result {
5858
Ok(())
5959
}
6060

61+
#[test]
62+
fn non_bare_repo_without_workdir() -> crate::Result {
63+
let repo = repo_path()?.join("non-bare-without-worktree");
64+
let kind = gix_discover::is_git(&repo)?;
65+
assert_eq!(
66+
kind,
67+
gix_discover::repository::Kind::PossiblyBare,
68+
"typically due to misconfiguration, but worktrees could also be configured in Git configuration"
69+
);
70+
Ok(())
71+
}
72+
73+
#[test]
74+
fn non_bare_repo_without_workdir_with_index() -> crate::Result {
75+
let repo = repo_path()?.join("non-bare-without-worktree-with-index");
76+
let kind = gix_discover::is_git(&repo)?;
77+
assert_eq!(
78+
kind,
79+
gix_discover::repository::Kind::PossiblyBare,
80+
"this means it has to be validated later"
81+
);
82+
Ok(())
83+
}
84+
6185
#[test]
6286
fn bare_repo_with_index_file_looks_still_looks_like_bare_if_it_was_renamed() -> crate::Result {
6387
for repo_name in ["bare-with-index-bare", "bare-with-index-no-config-bare"] {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Diff for: gix-discover/tests/fixtures/make_basic_repo.sh

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ mkdir -p some/very/deeply/nested/subdir
1515

1616
git clone --bare --shared . bare.git
1717

18+
git clone --bare --shared . non-bare-without-worktree
19+
(cd non-bare-without-worktree
20+
git config core.bare false
21+
)
22+
23+
git clone --bare --shared . non-bare-without-worktree-with-index
24+
(cd non-bare-without-worktree
25+
git config core.bare false
26+
cp ../.git/index .
27+
)
28+
1829
git worktree add worktrees/a
1930
git worktree add worktrees/b-private-dir-deleted
2031
rm -R .git/worktrees/b-private-dir-deleted

Diff for: gix/src/object/tree/diff/change.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
use super::ChangeDetached;
12
use crate::bstr::{BStr, ByteSlice};
23
use crate::ext::ObjectIdExt;
34
use crate::object::tree::diff::Change;
45
use crate::Repository;
5-
use gix_diff::tree_with_rewrites::Change as ChangeDetached;
66

77
impl Change<'_, '_, '_> {
88
/// Produce a platform for performing a line-diff no matter whether the underlying [Change] is an addition, modification,

Diff for: gix/src/object/tree/diff/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub enum Action {
1212
Cancel,
1313
}
1414

15+
pub use gix_diff::tree_with_rewrites::Change as ChangeDetached;
16+
1517
/// Represents any possible change in order to turn one tree into another.
1618
#[derive(Debug, Clone, Copy)]
1719
pub enum Change<'a, 'old, 'new> {

Diff for: gix/src/open/repository.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(clippy::result_large_err)]
2-
use std::{borrow::Cow, path::PathBuf};
3-
42
use gix_features::threading::OwnShared;
3+
use std::ffi::OsStr;
4+
use std::{borrow::Cow, path::PathBuf};
55

66
use super::{Error, Options};
77
use crate::{
@@ -86,7 +86,7 @@ impl ThreadSafeRepository {
8686
}
8787
};
8888

89-
// The be altered later based on `core.precomposeUnicode`.
89+
// To be altered later based on `core.precomposeUnicode`.
9090
let cwd = gix_fs::current_dir(false)?;
9191
let (git_dir, worktree_dir) = gix_discover::repository::Path::from_dot_git_dir(path, kind, &cwd)
9292
.expect("we have sanitized path with is_git()")
@@ -284,7 +284,7 @@ impl ThreadSafeRepository {
284284
}
285285

286286
match worktree_dir {
287-
None if !config.is_bare => {
287+
None if !config.is_bare && refs.git_dir().extension() == Some(OsStr::new(gix_discover::DOT_GIT_DIR)) => {
288288
worktree_dir = Some(git_dir.parent().expect("parent is always available").to_owned());
289289
}
290290
Some(_) => {
30 KB
Binary file not shown.

Diff for: gix/tests/fixtures/make_basic_repo.sh

+5
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ git init all-untracked
3636
git init empty-core-excludes
3737
(cd empty-core-excludes
3838
echo $'[core]\n\texcludesFile = ' >> .git/config
39+
)
40+
41+
git clone --bare . non-bare-without-worktree
42+
(cd non-bare-without-worktree
43+
git config core.bare false
3944
)

Diff for: gix/tests/gix/repository/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod dirwalk {
4242
("bare.git".into(), Directory),
4343
("empty-core-excludes".into(), Repository),
4444
("non-bare-repo-without-index".into(), Repository),
45+
("non-bare-without-worktree".into(), Directory),
4546
("some".into(), Directory),
4647
];
4748
assert_eq!(

Diff for: gix/tests/gix/repository/open.rs

+24
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,30 @@ fn bare_repo_with_index() -> crate::Result {
6262
repo.is_bare(),
6363
"it's properly classified as it reads the configuration (and has no worktree)"
6464
);
65+
assert_eq!(repo.work_dir(), None);
66+
Ok(())
67+
}
68+
69+
#[test]
70+
fn non_bare_non_git_repo_without_worktree() -> crate::Result {
71+
let repo = named_subrepo_opts(
72+
"make_basic_repo.sh",
73+
"non-bare-without-worktree",
74+
gix::open::Options::isolated(),
75+
)?;
76+
assert!(!repo.is_bare());
77+
assert_eq!(repo.work_dir(), None, "it doesn't assume that workdir exists");
78+
79+
let repo = gix::open_opts(
80+
repo.git_dir().join("objects").join(".."),
81+
gix::open::Options::isolated(),
82+
)?;
83+
assert!(!repo.is_bare());
84+
assert_eq!(
85+
repo.work_dir(),
86+
None,
87+
"it figures this out even if a non-normalized gitdir is used"
88+
);
6589
Ok(())
6690
}
6791

0 commit comments

Comments
 (0)