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

gix-discover/src/is.rs

Lines changed: 17 additions & 112 deletions
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
}

gix-discover/src/lib.rs

Lines changed: 2 additions & 0 deletions
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

gix-discover/src/repository.rs

Lines changed: 8 additions & 3 deletions
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 {

gix-discover/src/upwards/mod.rs

Lines changed: 3 additions & 3 deletions
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)? {

gix-discover/tests/is_git/mod.rs renamed to gix-discover/tests/discover/is_git/mod.rs

Lines changed: 24 additions & 0 deletions
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.

0 commit comments

Comments
 (0)