diff --git a/gix-discover/src/is.rs b/gix-discover/src/is.rs index 6817a5fe8af..5c88db3a1f5 100644 --- a/gix-discover/src/is.rs +++ b/gix-discover/src/is.rs @@ -1,6 +1,5 @@ -use std::{borrow::Cow, ffi::OsStr, path::Path}; - use crate::{DOT_GIT_DIR, MODULES}; +use std::{borrow::Cow, ffi::OsStr, path::Path}; /// Returns true if the given `git_dir` seems to be a bare repository. /// @@ -67,21 +66,30 @@ pub(crate) fn git_with_metadata( { // Fast-path: avoid doing the complete search if HEAD is already not there. - // TODO(reftable): use a ref-store to lookup HEAD if ref-tables should be supported, or detect ref-tables beforehand. - // Actually ref-tables still keep a specially marked `HEAD` around, so nothing might be needed here - // Even though our head-check later would fail without supporting it. if !dot_git.join("HEAD").exists() { return Err(crate::is_git::Error::MissingHead); } // We expect to be able to parse any ref-hash, so we shouldn't have to know the repos hash here. - // With ref-table, the has is probably stored as part of the ref-db itself, so we can handle it from there. + // With ref-table, the hash is probably stored as part of the ref-db itself, so we can handle it from there. // In other words, it's important not to fail on detached heads here because we guessed the hash kind wrongly. let refs = gix_ref::file::Store::at(dot_git.as_ref().into(), Default::default()); - let head = refs.find_loose("HEAD")?; - if head.name.as_bstr() != "HEAD" { - return Err(crate::is_git::Error::MisplacedHead { - name: head.name.into_inner(), - }); + match refs.find_loose("HEAD") { + Ok(head) => { + if head.name.as_bstr() != "HEAD" { + return Err(crate::is_git::Error::MisplacedHead { + name: head.name.into_inner(), + }); + } + } + Err(gix_ref::file::find::existing::Error::Find(gix_ref::file::find::Error::ReferenceCreation { + source: _, + relative_path, + })) if relative_path == Path::new("HEAD") => { + // It's fine as long as the reference is found is `HEAD`. + } + Err(err) => { + return Err(err.into()); + } } } diff --git a/gix-discover/tests/discover/is_git/mod.rs b/gix-discover/tests/discover/is_git.rs similarity index 88% rename from gix-discover/tests/discover/is_git/mod.rs rename to gix-discover/tests/discover/is_git.rs index 09f8576a409..f7e0644c774 100644 --- a/gix-discover/tests/discover/is_git/mod.rs +++ b/gix-discover/tests/discover/is_git.rs @@ -130,3 +130,18 @@ fn split_worktree_using_configuration() -> crate::Result { } Ok(()) } + +#[test] +fn reftable() -> crate::Result { + let repo_path = match gix_testtools::scripted_fixture_read_only("make_reftable_repo.sh") { + Ok(root) => root.join("reftable-clone/.git"), + Err(_) if *gix_testtools::GIT_VERSION < (2, 44, 0) => { + eprintln!("Fixture script failure ignored as it looks like Git isn't recent enough."); + return Ok(()); + } + Err(err) => panic!("{err}"), + }; + let kind = gix_discover::is_git(&repo_path)?; + assert_eq!(kind, gix_discover::repository::Kind::WorkTree { linked_git_dir: None }); + Ok(()) +} diff --git a/gix-discover/tests/discover/parse/mod.rs b/gix-discover/tests/discover/parse.rs similarity index 100% rename from gix-discover/tests/discover/parse/mod.rs rename to gix-discover/tests/discover/parse.rs diff --git a/gix-discover/tests/discover/path/mod.rs b/gix-discover/tests/discover/path.rs similarity index 100% rename from gix-discover/tests/discover/path/mod.rs rename to gix-discover/tests/discover/path.rs diff --git a/gix-discover/tests/fixtures/generated-archives/make_reftable_repo.tar b/gix-discover/tests/fixtures/generated-archives/make_reftable_repo.tar new file mode 100644 index 00000000000..b57fe223574 Binary files /dev/null and b/gix-discover/tests/fixtures/generated-archives/make_reftable_repo.tar differ diff --git a/gix-discover/tests/fixtures/make_reftable_repo.sh b/gix-discover/tests/fixtures/make_reftable_repo.sh new file mode 100755 index 00000000000..11eac3a2f77 --- /dev/null +++ b/gix-discover/tests/fixtures/make_reftable_repo.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +git init -q + +git checkout -b main +touch this +git add this +git commit -q -m c1 +echo hello >> this +git commit -q -am c2 + +git clone --ref-format=reftable . reftable-clone diff --git a/gix-fs/src/lib.rs b/gix-fs/src/lib.rs index 9f77d8e8614..b04ba80eced 100644 --- a/gix-fs/src/lib.rs +++ b/gix-fs/src/lib.rs @@ -77,10 +77,10 @@ pub struct Stack { } #[cfg(unix)] -/// Returns whether a a file has the executable permission set. +/// Returns whether a file has the executable permission set. pub fn is_executable(metadata: &std::fs::Metadata) -> bool { use std::os::unix::fs::MetadataExt; - (metadata.mode() & 0o100) != 0 + (metadata.mode() & 0o111) != 0 } /// Classifiers for IO-errors. diff --git a/gix-validate/src/path.rs b/gix-validate/src/path.rs index 51997b9be1e..e924b5b452e 100644 --- a/gix-validate/src/path.rs +++ b/gix-validate/src/path.rs @@ -20,6 +20,8 @@ pub mod component { DotGitDir, #[error("The .gitmodules file must not be a symlink")] SymlinkedGitModules, + #[error("Relative components '.' and '..' are disallowed")] + Relative, } /// Further specify what to check for in [`component()`](super::component()) @@ -78,6 +80,9 @@ pub fn component( if input.is_empty() { return Err(component::Error::Empty); } + if input == ".." || input == "." { + return Err(component::Error::Relative); + } if protect_windows { if input.find_byteset(br"/\").is_some() { return Err(component::Error::PathSeparator); diff --git a/gix-validate/tests/path/mod.rs b/gix-validate/tests/path/mod.rs index 1330ff4a9c7..4c53601a932 100644 --- a/gix-validate/tests/path/mod.rs +++ b/gix-validate/tests/path/mod.rs @@ -253,6 +253,10 @@ mod component { mktest!(con_with_extension, b"CON.abc", Error::WindowsReservedName); mktest!(con_with_middle, b"CON.tar.xz", Error::WindowsReservedName); mktest!(con_mixed_with_middle, b"coN.tar.xz ", Error::WindowsReservedName); + mktest!(dot_dot, b"..", Error::Relative); + mktest!(dot_dot_no_opts, b"..", Error::Relative, NO_OPTS); + mktest!(single_dot, b".", Error::Relative); + mktest!(single_dot_no_opts, b".", Error::Relative, NO_OPTS); mktest!( conout_mixed_with_extension, b"ConOut$ .xyz", diff --git a/gix/tests/fixtures/generated-archives/make_reftable_repo.tar b/gix/tests/fixtures/generated-archives/make_reftable_repo.tar new file mode 100644 index 00000000000..b57fe223574 Binary files /dev/null and b/gix/tests/fixtures/generated-archives/make_reftable_repo.tar differ diff --git a/gix/tests/fixtures/make_reftable_repo.sh b/gix/tests/fixtures/make_reftable_repo.sh new file mode 100755 index 00000000000..11eac3a2f77 --- /dev/null +++ b/gix/tests/fixtures/make_reftable_repo.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +git init -q + +git checkout -b main +touch this +git add this +git commit -q -m c1 +echo hello >> this +git commit -q -am c2 + +git clone --ref-format=reftable . reftable-clone diff --git a/gix/tests/gix/repository/open.rs b/gix/tests/gix/repository/open.rs index f24edc68914..d60227414da 100644 --- a/gix/tests/gix/repository/open.rs +++ b/gix/tests/gix/repository/open.rs @@ -55,6 +55,33 @@ fn on_root_with_decomposed_unicode() -> crate::Result { Ok(()) } +#[test] +fn non_bare_reftable() -> crate::Result { + let repo = match named_subrepo_opts( + "make_reftable_repo.sh", + "reftable-clone", + gix::open::Options::isolated(), + ) { + Ok(r) => r, + Err(_) if *gix_testtools::GIT_VERSION < (2, 44, 0) => { + eprintln!("Fixture script failure ignored as it looks like Git isn't recent enough."); + return Ok(()); + } + Err(err) => panic!("{err}"), + }; + assert!( + repo.head_id().is_err(), + "Trying to do anything with head will fail as we don't support reftables yet" + ); + assert!(!repo.is_bare()); + assert_ne!( + repo.workdir(), + None, + "Otherwise it can be used, but it's hard to do without refs" + ); + Ok(()) +} + #[test] fn bare_repo_with_index() -> crate::Result { let repo = named_subrepo_opts( diff --git a/gix/tests/gix/util.rs b/gix/tests/gix/util.rs index 493abd9d36b..eab545ea34a 100644 --- a/gix/tests/gix/util.rs +++ b/gix/tests/gix/util.rs @@ -38,7 +38,9 @@ pub fn named_subrepo_opts( name: &str, opts: open::Options, ) -> std::result::Result { - let repo_path = gix_testtools::scripted_fixture_read_only(fixture).unwrap().join(name); + let repo_path = gix_testtools::scripted_fixture_read_only(fixture) + .map_err(|err| gix::open::Error::Io(std::io::Error::other(err)))? + .join(name); Ok(ThreadSafeRepository::open_opts(repo_path, opts)?.to_thread_local()) } diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index a9c2938a56f..882231c6f48 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -583,13 +583,10 @@ fn scripted_fixture_read_only_with_args_inner( }; if !output.status.success() { write_failure_marker(&failure_marker); + eprintln!("stdout: {}", output.stdout.as_bstr()); + eprintln!("stderr: {}", output.stderr.as_bstr()); + return Err(format!("fixture script of {cmd:?} failed").into()); } - assert!( - output.status.success(), - "fixture script of {cmd:?} failed: stdout: {}\nstderr: {}", - output.stdout.as_bstr(), - output.stderr.as_bstr() - ); create_archive_if_we_should( &script_result_directory, &archive_file_path,