Skip to content
Closed
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
52 changes: 43 additions & 9 deletions gix/src/config/cache/incubate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,12 @@ 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 config.string(Extensions::OBJECT_FORMAT) {
// objectFormat is honored from repository format version 1 onwards.
Some(format) if repo_format_version >= 1 => Extensions::OBJECT_FORMAT.try_into_object_format(format)?,
Some(_) => return Err(Error::ObjectFormatRequiresV1),
None => legacy_object_hash()?,
};
Comment thread
10ne1 marked this conversation as resolved.

let extension_worktree = util::config_bool(
&config,
Expand Down Expand Up @@ -104,6 +101,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() })
}
Comment on lines +114 to +117

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit stuck here and decided to go with something else.

Initially, instead of saying this isn't possible, I asked myself what would Git do if it would allow to not compile SHA-1 in. Then I think it should just be like SHA-1 never happened, hence SHA-256 is returned.

However, Git doesn't do that and it gix tooling that relies on SHA-256 only builds will simply have to declare it properly so both Git and the tool can open it.

An interesting journey, leading to nothing but this comment 😁.

}
Comment thread
10ne1 marked this conversation as resolved.

fn load_config(
config_path: std::path::PathBuf,
buf: &mut Vec<u8>,
Expand Down Expand Up @@ -157,3 +170,24 @@ fn load_config(

Ok(config)
}

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test felt redundant in the light of compile-time safety and docs of legacy_object_hash_reflects_build_features.

/// `legacy_object_hash` has a feature-gated split: Sha1 when this build supports it, an error
/// otherwise. Assert the branch matching the current build so the sha256-only path is covered
/// when the lib tests run under `--no-default-features --features sha256`.
#[test]
fn legacy_object_hash_reflects_build_features() {
let actual = super::legacy_object_hash();
#[cfg(feature = "sha1")]
assert!(
matches!(actual, Ok(gix_hash::Kind::Sha1)),
"with sha1 support a missing objectFormat resolves to the legacy Sha1 layout, got {actual:?}"
);
#[cfg(not(feature = "sha1"))]
assert!(
matches!(actual, Err(super::Error::UnsupportedObjectFormat { .. })),
"without sha1 support a missing objectFormat cannot be opened, got {actual:?}"
);
}
}
5 changes: 5 additions & 0 deletions gix/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ 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,
Comment thread
10ne1 marked this conversation as resolved.
Comment on lines +89 to +93

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A wonderful error message!

#[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
Binary file modified gix/tests/fixtures/generated-archives/make_config_repos.tar
Binary file not shown.
Binary file not shown.
13 changes: 13 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,19 @@ mkdir not-a-repo-with-files;
touch this that
)

# objectFormat is a v1-only extension; a version-0 repo that sets it is invalid (even for sha1).
# Reach that state with git config independently of the fixture's default hash: first make a valid
# v1+objectFormat repo, then flip the version to 0 as the last write. The flip must come last because
# git config refuses to run once the repo is already invalid (v0 with a v1-only extension).
for format in sha1 sha256; do
git init "objectformat-$format-with-repository-format-v0"
(cd "objectformat-$format-with-repository-format-v0"
git config core.repositoryFormatVersion 1
git config extensions.objectFormat "$format"
git config core.repositoryFormatVersion 0
)
done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the 'dance' in favor of something more direct, for better or worse.


git init ssl-verify-disabled
(cd ssl-verify-disabled
git config http.sslVerify false
Expand Down
26 changes: 26 additions & 0 deletions gix/tests/gix/repository/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,32 @@ 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.
Comment on lines +362 to +364

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the research that went into this.

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(())
}
}

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
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