Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions gix/src/config/cache/incubate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@ impl StageOne {
.map(|version| Core::REPOSITORY_FORMAT_VERSION.try_into_usize(version))
.transpose()?
.unwrap_or_default();
let object_hash = (repo_format_version != 1)
.then_some(Ok(gix_hash::Kind::Sha1))
.or_else(|| {
config
.string(Extensions::OBJECT_FORMAT)
.map(|format| Extensions::OBJECT_FORMAT.try_into_object_format(format))
})
.transpose()?
.unwrap_or(gix_hash::Kind::Sha1);
let object_hash = match (repo_format_version, config.string(Extensions::OBJECT_FORMAT)) {
// objectFormat is a repository format version 1 extension.
(1, Some(format)) => Extensions::OBJECT_FORMAT.try_into_object_format(format)?,
(0, Some(_)) => return Err(Error::ObjectFormatRequiresV1),
(0 | 1, None) => legacy_object_hash()?,
(version, _) => return Err(Error::UnsupportedRepositoryFormatVersion { version }),
};

let extension_worktree = util::config_bool(
&config,
Expand Down Expand Up @@ -104,6 +102,22 @@ impl StageOne {
}
}

/// Return the object hash for a repository that does not set `extensions.objectFormat`.
///
/// Git interprets a missing objectFormat as the original Sha1 layout, so we return
/// gix_hash::Kind::Sha1 whenever this build can handle it.
/// In Sha256-only builds we cannot open such a repository, so return an error instead.
fn legacy_object_hash() -> Result<gix_hash::Kind, Error> {
#[cfg(feature = "sha1")]
{
Ok(gix_hash::Kind::Sha1)
}
#[cfg(not(feature = "sha1"))]
{
Err(Error::UnsupportedObjectFormat { name: "sha1".into() })
}
}

fn load_config(
config_path: std::path::PathBuf,
buf: &mut Vec<u8>,
Expand Down
7 changes: 7 additions & 0 deletions gix/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ pub enum Error {
RefsNamespace(#[from] refs_namespace::Error),
#[error("Cannot handle objects formatted as {:?}", .name)]
UnsupportedObjectFormat { name: BString },
#[error(
"extensions.objectFormat is a v1-only extension, but the repository format version is 0; \
set core.repositoryFormatVersion=1 to use it, or remove extensions.objectFormat to fall back to the default Sha1 format (if supported by this build)"
)]
ObjectFormatRequiresV1,
#[error("Unsupported repository format version {version}; only versions 0 and 1 are supported")]
UnsupportedRepositoryFormatVersion { version: usize },
#[error(transparent)]
CoreAbbrev(#[from] abbrev::Error),
#[error("Could not read configuration file at \"{}\"", path.display())]
Expand Down
7 changes: 5 additions & 2 deletions gix/src/config/tree/sections/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,11 @@ mod validate {
pub struct Abbrev;
impl keys::Validate for Abbrev {
fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// TODO: when there is options, validate against all hashes and assure all fail to trigger a validation failure.
super::Core::ABBREV.try_into_abbreviation(value.into(), gix_hash::Kind::Sha1)?;
// The keys::Validate trait API doesn't take a hash kind, and passing one through
// would touch ~50 impl sites. The repo-aware check with the actual hash runs in
// config::cache::util::parse_core_abbrev, so here we just use Kind::longest()
// to allow the most permissive upper bound.
super::Core::ABBREV.try_into_abbreviation(value.into(), gix_hash::Kind::longest())?;
Ok(())
}
}
Expand Down
46 changes: 45 additions & 1 deletion gix/src/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ fn create_dir(p: &Path) -> Result<(), Error> {
}

/// Options for use in [`into()`];
#[derive(Copy, Default, Clone)]
#[derive(Copy, Clone)]
pub struct Options {
/// Control whether the destination directory must be empty when creating a repository with a worktree.
///
Expand All @@ -134,6 +134,31 @@ pub struct Options {
pub object_hash: Option<gix_hash::Kind>,
}

impl Default for Options {
fn default() -> Self {
Options {
destination_must_be_empty: None,
fs_capabilities: None,
object_hash: default_object_hash(),
}
}
}

fn default_object_hash() -> Option<gix_hash::Kind> {
#[cfg(feature = "sha1")]
{
None
}
#[cfg(all(not(feature = "sha1"), feature = "sha256"))]
{
Some(gix_hash::Kind::Sha256)
}
#[cfg(all(not(feature = "sha1"), not(feature = "sha256")))]
{
unreachable!("hash support features are validated by gix-hash")
}
}

/// Create a new `.git` repository of `kind` within the possibly non-existing `directory`
/// and return its path.
/// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections
Expand Down Expand Up @@ -286,3 +311,22 @@ fn bool(v: bool) -> &'static str {
false => "false",
}
}

#[cfg(test)]
mod tests {
#[test]
fn default_object_hash_matches_available_hash_support() {
let object_hash = super::Options::default().object_hash;
#[cfg(feature = "sha1")]
assert_eq!(
object_hash, None,
"SHA1-capable builds keep Git's implicit legacy object format"
);
#[cfg(all(not(feature = "sha1"), feature = "sha256"))]
assert_eq!(
object_hash,
Some(gix_hash::Kind::Sha256),
"SHA256-only builds must initialize repositories that can be reopened"
);
}
}
Binary file modified gix/tests/fixtures/generated-archives/make_config_repos.tar
Binary file not shown.
Binary file not shown.
25 changes: 25 additions & 0 deletions gix/tests/fixtures/make_config_repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,31 @@ mkdir not-a-repo-with-files;
touch this that
)

for format in sha1 sha256; do
git init "objectformat-$format-with-repository-format-v0"
(cd "objectformat-$format-with-repository-format-v0"
# use overrides to get a repository that Git refuses to open/edit.
cat <<EOF >>.git/config
[core]
repositoryFormatVersion = 0
[extensions]
objectFormat = $format
EOF
)
done

git init repository-format-v2-with-objectformat-sha1
(cd repository-format-v2-with-objectformat-sha1
# Future repository formats may change invariants beyond the object hash. Git
# refuses such repositories before interpreting extensions; gix should too.
cat <<EOF >>.git/config
[core]
repositoryFormatVersion = 2
[extensions]
objectFormat = sha1
EOF
)

git init ssl-verify-disabled
(cd ssl-verify-disabled
git config http.sslVerify false
Expand Down
44 changes: 44 additions & 0 deletions gix/tests/gix/repository/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,50 @@ mod not_a_repository {
}
}

mod object_format_extension {
use crate::util::named_subrepo_opts;

#[test]
fn rejects_object_format_on_v0_repo() -> crate::Result {
// objectFormat is a "v1-only" extension: git refuses to operate on a version-0 repo that
// sets it, even for sha1 (unlike grandfathered extensions like preciousObjects, which v0
// still honours). This rejection was introduced in git 2.29.0 (2020). We match it.
for name in [
"objectformat-sha256-with-repository-format-v0",
"objectformat-sha1-with-repository-format-v0",
] {
let err = named_subrepo_opts("make_config_repos.sh", name, gix::open::Options::isolated())
.expect_err("a v0 repository setting extensions.objectFormat must be rejected");
assert!(
matches!(
err,
gix::open::Error::Config(gix::config::Error::ObjectFormatRequiresV1)
),
"objectFormat on a v0 repository must be rejected, got {err:?} for {name}"
);
}
Ok(())
}

#[test]
fn rejects_future_repository_format_versions() -> crate::Result {
let err = named_subrepo_opts(
"make_config_repos.sh",
"repository-format-v2-with-objectformat-sha1",
gix::open::Options::isolated(),
)
.expect_err("future repository format versions must be rejected");
assert!(
matches!(
err,
gix::open::Error::Config(gix::config::Error::UnsupportedRepositoryFormatVersion { version: 2 })
),
"future repository format versions must be rejected before interpreting extensions, got {err:?}"
);
Ok(())
}
}

mod open_path_as_is {

use crate::util::{named_subrepo_opts, repo_opts};
Expand Down
2 changes: 2 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ check:
cargo check -p gix --no-default-features --features blame --tests
cargo check -p gix --no-default-features --features sha1
cargo check -p gix --no-default-features --features sha1,sha256
cargo check -p gix --no-default-features --features sha256
Comment thread
Byron marked this conversation as resolved.
cargo check -p gix --no-default-features 2>&1 >/dev/null | grep 'Please set either the `sha1` or the `sha256` feature flag'
cargo check -p gix-odb --features serde 2>&1 >/dev/null | grep 'Please set either the `sha1` or the `sha256` feature flag'
cargo check -p gix-odb --features sha1,serde
Expand Down Expand Up @@ -215,6 +216,7 @@ unit-tests:
cargo nextest run -p gix --features async-network-client --no-fail-fast
cargo nextest run -p gix --features blocking-network-client --no-fail-fast
env GIX_TEST_FIXTURE_HASH=sha256 cargo nextest run -p gix --no-fail-fast
cargo nextest run -p gix --no-default-features --features sha256 --lib --no-fail-fast
cargo nextest run -p gitoxide-core --lib --no-tests=warn --no-fail-fast

# Run all doctests
Expand Down
Loading