Skip to content

Commit 169228b

Browse files
committed
fix!: bare() can now make sure it detects bare repos, at an expense. (#980)
In order to be very sure it's the correct guess, we have to open and quick-parse a the bare line.
1 parent 353b1a7 commit 169228b

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-2
lines changed

gix-discover/src/is.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,99 @@ pub fn bare(git_dir_candidate: impl AsRef<Path>) -> bool {
1010
!(git_dir.join("index").exists() || (git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR))))
1111
}
1212

13+
/// Parse `<git_dir_candidate>/config` quickly to evaluate the value of the `bare` line, or return `true` if the file doesn't exist
14+
/// similar to what`guess_repository_type` seems to be doing.
15+
/// Return `None` if the `bare` line can't be found or the value of `bare` can't be determined.
16+
fn bare_by_config(git_dir_candidate: impl AsRef<Path>) -> std::io::Result<Option<bool>> {
17+
match std::fs::read(git_dir_candidate.as_ref().join("config")) {
18+
Ok(buf) => Ok(config::parse_bare(&buf)),
19+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Some(true)),
20+
Err(err) => Err(err),
21+
}
22+
}
23+
24+
// Copied and adapted from `gix-config-value::boolean`.
25+
mod config {
26+
use bstr::{BStr, ByteSlice};
27+
28+
pub(crate) fn parse_bare(buf: &[u8]) -> Option<bool> {
29+
buf.lines().find_map(|line| {
30+
let line = line.trim().strip_prefix(b"bare")?;
31+
match line.first() {
32+
None => Some(true),
33+
Some(c) if *c == b'=' => parse_bool(line.get(1..)?.trim_start().as_bstr()),
34+
Some(c) if c.is_ascii_whitespace() => match line.split_once_str(b"=") {
35+
Some((_left, right)) => parse_bool(right.trim_start().as_bstr()),
36+
None => Some(true),
37+
},
38+
Some(_other_char_) => None,
39+
}
40+
})
41+
}
42+
43+
fn parse_bool(value: &BStr) -> Option<bool> {
44+
Some(if parse_true(value) {
45+
true
46+
} else if parse_false(value) {
47+
false
48+
} else {
49+
use std::str::FromStr;
50+
if let Some(integer) = value.to_str().ok().and_then(|s| i64::from_str(s).ok()) {
51+
integer != 0
52+
} else {
53+
return None;
54+
}
55+
})
56+
}
57+
58+
fn parse_true(value: &BStr) -> bool {
59+
value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true")
60+
}
61+
62+
fn parse_false(value: &BStr) -> bool {
63+
value.eq_ignore_ascii_case(b"no")
64+
|| value.eq_ignore_ascii_case(b"off")
65+
|| value.eq_ignore_ascii_case(b"false")
66+
|| value.is_empty()
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
73+
#[test]
74+
fn various() {
75+
for (input, expected) in [
76+
("bare=true", Some(true)),
77+
("bare=1", Some(true)),
78+
("bare =1", Some(true)),
79+
("bare= yes", Some(true)),
80+
("bare=false", Some(false)),
81+
("bare=0", Some(false)),
82+
("bare=blah", None),
83+
("bare=", Some(false)),
84+
("bare= \n", Some(false)),
85+
("bare = true \n", Some(true)),
86+
("\t bare = false \n", Some(false)),
87+
("\n\tbare=true", Some(true)),
88+
("\n\tbare=true\n\tfoo", Some(true)),
89+
("\n\tbare ", Some(true)),
90+
("\n\tbare", Some(true)),
91+
("not found\nreally", None),
92+
] {
93+
assert_eq!(parse_bare(input.as_bytes()), expected, "{input:?}");
94+
}
95+
}
96+
}
97+
}
98+
1399
/// Returns true if `git_dir` is located within a `.git/modules` directory, indicating it's a submodule clone.
14100
pub fn submodule_git_dir(git_dir: impl AsRef<Path>) -> bool {
15101
let git_dir = git_dir.as_ref();
16102

17103
let mut last_comp = None;
18104
git_dir.file_name() != Some(OsStr::new(DOT_GIT_DIR))
19-
&& git_dir.components().rev().any(|c| {
105+
&& git_dir.components().rev().skip(1).any(|c| {
20106
if c.as_os_str() == OsStr::new(DOT_GIT_DIR) {
21107
true
22108
} else {
@@ -154,8 +240,17 @@ pub(crate) fn git_with_metadata(
154240
crate::repository::Kind::Bare
155241
} else if submodule_git_dir(conformed_git_dir.as_ref()) {
156242
crate::repository::Kind::SubmoduleGitDir
157-
} else {
243+
} else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR))
244+
|| !bare_by_config(conformed_git_dir.as_ref())
245+
.map_err(|err| crate::is_git::Error::Metadata {
246+
source: err,
247+
path: conformed_git_dir.join("config"),
248+
})?
249+
.ok_or(crate::is_git::Error::Inconclusive)?
250+
{
158251
crate::repository::Kind::WorkTree { linked_git_dir: None }
252+
} else {
253+
crate::repository::Kind::Bare
159254
}
160255
}
161256
})

gix-discover/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ 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")]
41+
Inconclusive,
4042
}
4143
}
4244

gix-discover/tests/fixtures/make_basic_repo.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ git init --bare bare-with-index.git
5252
touch index
5353
)
5454

55+
git init --bare bare-with-index-bare
56+
(cd bare-with-index-bare
57+
touch index
58+
)
59+
60+
git init --bare bare-with-index-no-config-bare
61+
(cd bare-with-index-no-config-bare
62+
touch index
63+
rm config
64+
)
65+
5566
git init non-bare-without-index
5667
(cd non-bare-without-index
5768
touch this

gix-discover/tests/is_git/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ fn bare_repo_with_index_file_looks_still_looks_like_bare() -> crate::Result {
5858
Ok(())
5959
}
6060

61+
#[test]
62+
fn bare_repo_with_index_file_looks_still_looks_like_bare_if_it_was_renamed() -> crate::Result {
63+
for repo_name in ["bare-with-index-bare", "bare-with-index-no-config-bare"] {
64+
let repo = repo_path()?.join(repo_name);
65+
let kind = gix_discover::is_git(repo)?;
66+
assert_eq!(kind, gix_discover::repository::Kind::Bare);
67+
}
68+
Ok(())
69+
}
70+
6171
#[test]
6272
fn no_bare_repo_without_index_file_looks_like_worktree() -> crate::Result {
6373
let repo = repo_path()?.join("non-bare-without-index").join(".git");

0 commit comments

Comments
 (0)