Skip to content

Commit

Permalink
Merge branch 'discover-split-worktree'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Oct 17, 2023
2 parents 650461c + dd57957 commit 16170d9
Show file tree
Hide file tree
Showing 20 changed files with 294 additions and 55 deletions.
59 changes: 59 additions & 0 deletions gitoxide-core/src/discover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::path::Path;

pub fn discover(repo: &Path, mut out: impl std::io::Write) -> anyhow::Result<()> {
let mut has_err = false;
writeln!(out, "open (strict) {}:", repo.display())?;
has_err |= print_result(
&mut out,
gix::open_opts(repo, gix::open::Options::default().strict_config(true)),
)?;

if has_err {
writeln!(out, "open (lenient) {}:", repo.display())?;
has_err |= print_result(
&mut out,
gix::open_opts(repo, gix::open::Options::default().strict_config(false)),
)?;
}

writeln!(out)?;
writeln!(out, "discover from {}:", repo.display())?;
has_err |= print_result(&mut out, gix::discover(repo))?;

writeln!(out)?;
writeln!(out, "discover (plumbing) from {}:", repo.display())?;
has_err |= print_result(&mut out, gix::discover::upwards(repo))?;

if has_err {
writeln!(out)?;
anyhow::bail!("At least one operation failed")
}

Ok(())
}

fn print_result<T, E>(mut out: impl std::io::Write, res: Result<T, E>) -> std::io::Result<bool>
where
T: std::fmt::Debug,
E: std::error::Error + Send + Sync + 'static,
{
let mut has_err = false;
let to_print = match res {
Ok(good) => {
format!("{good:#?}")
}
Err(err) => {
has_err = true;
format!("{:?}", anyhow::Error::from(err))
}
};
indent(&mut out, to_print)?;
Ok(has_err)
}

fn indent(mut out: impl std::io::Write, msg: impl Into<String>) -> std::io::Result<()> {
for line in msg.into().lines() {
writeln!(out, "\t{line}")?;
}
Ok(())
}
3 changes: 3 additions & 0 deletions gitoxide-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,8 @@ pub mod pack;
pub mod query;
pub mod repository;

mod discover;
pub use discover::discover;

#[cfg(all(feature = "async-client", feature = "blocking-client"))]
compile_error!("Cannot set both 'blocking-client' and 'async-client' features as they are mutually exclusive");
41 changes: 28 additions & 13 deletions gix-discover/src/is.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,34 @@ fn bare_by_config(git_dir_candidate: &Path) -> std::io::Result<Option<bool>> {
mod config {
use bstr::{BStr, ByteSlice};

/// Note that we intentionally turn repositories that have a worktree configuration into bare repos,
/// as we don't actually parse the worktree from the config file and expect the caller to do the right
/// think when seemingly seeing bare repository.
/// The reason we do this is to not incorrectly pretend this is a worktree.
pub(crate) fn parse_bare(buf: &[u8]) -> Option<bool> {
buf.lines().find_map(|line| {
let line = line.trim().strip_prefix(b"bare")?;
match line.first() {
None => Some(true),
Some(c) if *c == b'=' => parse_bool(line.get(1..)?.trim_start().as_bstr()),
Some(c) if c.is_ascii_whitespace() => match line.split_once_str(b"=") {
Some((_left, right)) => parse_bool(right.trim_start().as_bstr()),
None => Some(true),
},
Some(_other_char_) => None,
let mut is_bare = None;
let mut has_worktree_configuration = false;
for line in buf.lines() {
if is_bare.is_none() {
if let Some(line) = line.trim().strip_prefix(b"bare") {
is_bare = match line.first() {
None => Some(true),
Some(c) if *c == b'=' => parse_bool(line.get(1..)?.trim_start().as_bstr()),
Some(c) if c.is_ascii_whitespace() => match line.split_once_str(b"=") {
Some((_left, right)) => parse_bool(right.trim_start().as_bstr()),
None => Some(true),
},
Some(_other_char_) => None,
};
continue;
}
}
})
if line.trim().strip_prefix(b"worktree").is_some() {
has_worktree_configuration = true;
break;
}
}
is_bare.map(|bare| bare || has_worktree_configuration)
}

fn parse_bool(value: &BStr) -> Option<bool> {
Expand Down Expand Up @@ -233,7 +248,7 @@ pub(crate) fn git_with_metadata(
Cow::Borrowed(git_dir)
};
if bare(conformed_git_dir.as_ref()) || conformed_git_dir.extension() == Some(OsStr::new("git")) {
crate::repository::Kind::Bare
crate::repository::Kind::PossiblyBare
} else if submodule_git_dir(conformed_git_dir.as_ref()) {
crate::repository::Kind::SubmoduleGitDir
} else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR))
Expand All @@ -246,7 +261,7 @@ pub(crate) fn git_with_metadata(
{
crate::repository::Kind::WorkTree { linked_git_dir: None }
} else {
crate::repository::Kind::Bare
crate::repository::Kind::PossiblyBare
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion gix-discover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub mod is_git {
GitFile(#[from] crate::path::from_gitdir_file::Error),
#[error("Could not retrieve metadata of \"{path}\"")]
Metadata { source: std::io::Error, path: PathBuf },
#[error("The repository's config file doesn't exist or didn't have a 'bare' configuration")]
#[error("The repository's config file doesn't exist or didn't have a 'bare' configuration or contained core.worktree without value")]
Inconclusive,
}
}
Expand Down
12 changes: 8 additions & 4 deletions gix-discover/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ mod path {
Path::WorkTree(work_dir)
}
},
Kind::Bare => Path::Repository(dir),
Kind::PossiblyBare => Path::Repository(dir),
}
.into()
}
Expand All @@ -89,7 +89,7 @@ mod path {
linked_git_dir: Some(git_dir.to_owned()),
},
Path::WorkTree(_) => Kind::WorkTree { linked_git_dir: None },
Path::Repository(_) => Kind::Bare,
Path::Repository(_) => Kind::PossiblyBare,
}
}

Expand All @@ -110,7 +110,11 @@ pub enum Kind {
/// A bare repository does not have a work tree, that is files on disk beyond the `git` repository itself.
///
/// Note that this is merely a guess at this point as we didn't read the configuration yet.
Bare,
///
/// Also note that due to optimizing for performance and *just* making an educated *guess in some situations*,
/// we may consider a non-bare repository bare if it it doesn't have an index yet due to be freshly initialized.
/// The caller is has to handle this, typically by reading the configuration.
PossiblyBare,
/// A `git` repository along with checked out files in a work tree.
WorkTree {
/// If set, this is the git dir associated with this _linked_ worktree.
Expand All @@ -135,6 +139,6 @@ pub enum Kind {
impl Kind {
/// Returns true if this is a bare repository, one without a work tree.
pub fn is_bare(&self) -> bool {
matches!(self, Kind::Bare)
matches!(self, Kind::PossiblyBare)
}
}
22 changes: 22 additions & 0 deletions gix-discover/tests/fixtures/make_basic_repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,25 @@ git init non-bare-without-index
git commit -m "init"
rm .git/index
)

git --git-dir=repo-with-worktree-in-config-unborn-no-worktreedir --work-tree=does-not-exist-yet init
worktree=repo-with-worktree-in-config-unborn-worktree
git --git-dir=repo-with-worktree-in-config-unborn --work-tree=$worktree init && mkdir $worktree

repo=repo-with-worktree-in-config-unborn-empty-worktreedir
git --git-dir=$repo --work-tree="." init
touch $repo/index
git -C $repo config core.worktree ''

repo=repo-with-worktree-in-config-unborn-worktreedir-missing-value
git --git-dir=$repo init
touch $repo/index
echo " worktree" >> $repo/config

worktree=repo-with-worktree-in-config-worktree
git --git-dir=repo-with-worktree-in-config --work-tree=$worktree init
mkdir $worktree && touch $worktree/file
(cd repo-with-worktree-in-config
git add file
git commit -m "make sure na index exists"
)
27 changes: 24 additions & 3 deletions gix-discover/tests/is_git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_bare_repo() -> crate::Resu
for name in ["bare-no-config-after-init.git", "bare-no-config.git"] {
let repo = repo_path()?.join(name);
let kind = gix_discover::is_git(&repo)?;
assert_eq!(kind, gix_discover::repository::Kind::Bare);
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
}
Ok(())
}
Expand All @@ -54,7 +54,7 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_bare_repo() -> crate::Resu
fn bare_repo_with_index_file_looks_still_looks_like_bare() -> crate::Result {
let repo = repo_path()?.join("bare-with-index.git");
let kind = gix_discover::is_git(&repo)?;
assert_eq!(kind, gix_discover::repository::Kind::Bare);
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
Ok(())
}

Expand All @@ -63,7 +63,7 @@ fn bare_repo_with_index_file_looks_still_looks_like_bare_if_it_was_renamed() ->
for repo_name in ["bare-with-index-bare", "bare-with-index-no-config-bare"] {
let repo = repo_path()?.join(repo_name);
let kind = gix_discover::is_git(&repo)?;
assert_eq!(kind, gix_discover::repository::Kind::Bare);
assert_eq!(kind, gix_discover::repository::Kind::PossiblyBare);
}
Ok(())
}
Expand All @@ -85,3 +85,24 @@ fn missing_configuration_file_is_not_a_dealbreaker_in_nonbare_repo() -> crate::R
}
Ok(())
}

#[test]
fn split_worktree_using_configuration() -> crate::Result {
for name in [
"repo-with-worktree-in-config",
"repo-with-worktree-in-config-unborn",
"repo-with-worktree-in-config-unborn-no-worktreedir",
"repo-with-worktree-in-config-unborn-empty-worktreedir",
"repo-with-worktree-in-config-unborn-worktreedir-missing-value",
] {
let repo = repo_path()?.join(name);
let kind = gix_discover::is_git(&repo)?;
assert_eq!(
kind,
gix_discover::repository::Kind::PossiblyBare,
"{name}: we think these are bare as we don't read the configuration in this case - \
a shortcoming to favor performance which still comes out correct in `gix`"
);
}
Ok(())
}
4 changes: 2 additions & 2 deletions gix-discover/tests/isolated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn upwards_bare_repo_with_index() -> gix_testtools::Result {
let (repo_path, _trust) = gix_discover::upwards(".".as_ref())?;
assert_eq!(
repo_path.kind(),
gix_discover::repository::Kind::Bare,
gix_discover::repository::Kind::PossiblyBare,
"bare stays bare, even with index, as it resolves the path as needed in this special case"
);
Ok(())
Expand All @@ -25,7 +25,7 @@ fn in_cwd_upwards_bare_repo_without_index() -> gix_testtools::Result {

let _keep = gix_testtools::set_current_dir(repo.join("bare.git"))?;
let (repo_path, _trust) = gix_discover::upwards(".".as_ref())?;
assert_eq!(repo_path.kind(), gix_discover::repository::Kind::Bare);
assert_eq!(repo_path.kind(), gix_discover::repository::Kind::PossiblyBare);
Ok(())
}

Expand Down
8 changes: 4 additions & 4 deletions gix-discover/tests/upwards/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn from_bare_git_dir() -> crate::Result {
let dir = repo_path()?.join("bare.git");
let (path, trust) = gix_discover::upwards(&dir)?;
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(path.kind(), Kind::PossiblyBare);
assert_eq!(trust, expected_trust());
Ok(())
}
Expand All @@ -27,7 +27,7 @@ fn from_bare_with_index() -> crate::Result {
let dir = repo_path()?.join("bare-with-index.git");
let (path, trust) = gix_discover::upwards(&dir)?;
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(path.kind(), Kind::PossiblyBare);
assert_eq!(trust, expected_trust());
Ok(())
}
Expand All @@ -48,7 +48,7 @@ fn from_bare_git_dir_without_config_file() -> crate::Result {
let dir = repo_path()?.join(name);
let (path, trust) = gix_discover::upwards(&dir)?;
assert_eq!(path.as_ref(), dir, "the bare .git dir is directly returned");
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(path.kind(), Kind::PossiblyBare);
assert_eq!(trust, expected_trust());
}
Ok(())
Expand All @@ -64,7 +64,7 @@ fn from_inside_bare_git_dir() -> crate::Result {
git_dir,
"the bare .git dir is found while traversing upwards"
);
assert_eq!(path.kind(), Kind::Bare);
assert_eq!(path.kind(), Kind::PossiblyBare);
assert_eq!(trust, expected_trust());
Ok(())
}
Expand Down
7 changes: 5 additions & 2 deletions gix/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ pub enum Error {
ResolveIncludes(#[from] gix_config::file::includes::Error),
#[error(transparent)]
FromEnv(#[from] gix_config::file::init::from_env::Error),
#[error(transparent)]
PathInterpolation(#[from] gix_config::path::interpolate::Error),
#[error("The path {path:?} at the 'core.worktree' configuration could not be interpolated")]
PathInterpolation {
path: BString,
source: gix_config::path::interpolate::Error,
},
#[error("{source:?} configuration overrides at open or init time could not be applied.")]
ConfigOverrides {
#[source]
Expand Down
2 changes: 1 addition & 1 deletion gix/src/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ pub fn into(
Ok(gix_discover::repository::Path::from_dot_git_dir(
dot_git,
if bare {
gix_discover::repository::Kind::Bare
gix_discover::repository::Kind::PossiblyBare
} else {
gix_discover::repository::Kind::WorkTree { linked_git_dir: None }
},
Expand Down
22 changes: 18 additions & 4 deletions gix/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,27 @@ impl ThreadSafeRepository {
.resolved
.path_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
{
let wt_clone = wt.clone();
let wt_path = wt
.interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref()))
.map_err(config::Error::PathInterpolation)?;
worktree_dir = {
gix_path::normalize(git_dir.join(wt_path).into(), current_dir)
.and_then(|wt| wt.as_ref().is_dir().then(|| wt.into_owned()))
.map_err(|err| config::Error::PathInterpolation {
path: wt_clone.value.into_owned(),
source: err,
})?;
worktree_dir = gix_path::normalize(git_dir.join(wt_path).into(), current_dir).map(Cow::into_owned);
#[allow(unused_variables)]
if let Some(worktree_path) = worktree_dir.as_deref().filter(|wtd| !wtd.is_dir()) {
gix_trace::warn!("The configured worktree path '{}' is not a directory or doesn't exist - `core.worktree` may be misleading", worktree_path.display());
}
} else if !config.lenient_config
&& config
.resolved
.boolean_filter("core", None, Core::WORKTREE.name, &mut filter_config_section)
.is_some()
{
return Err(Error::from(config::Error::ConfigTypedString(
config::key::GenericErrorWithValue::from(&Core::WORKTREE),
)));
}
}

Expand Down
2 changes: 1 addition & 1 deletion gix/src/repository/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl From<gix_discover::repository::Kind> for Kind {
gix_discover::repository::Kind::Submodule { .. } | gix_discover::repository::Kind::SubmoduleGitDir => {
Kind::WorkTree { is_linked: false }
}
gix_discover::repository::Kind::Bare => Kind::Bare,
gix_discover::repository::Kind::PossiblyBare => Kind::Bare,
gix_discover::repository::Kind::WorkTreeGitDir { .. } => Kind::WorkTree { is_linked: true },
gix_discover::repository::Kind::WorkTree { linked_git_dir } => Kind::WorkTree {
is_linked: linked_git_dir.is_some(),
Expand Down
Loading

0 comments on commit 16170d9

Please sign in to comment.