Skip to content

Commit f1f8b6f

Browse files
authored
Merge pull request #2651 from GitoxideLabs/allow-sha256-only-builds
allow sha256 only builds
2 parents 5dfb44d + 2a5b8d4 commit f1f8b6f

9 files changed

Lines changed: 151 additions & 12 deletions

File tree

gix/src/config/cache/incubate.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,13 @@ impl StageOne {
4545
.map(|version| Core::REPOSITORY_FORMAT_VERSION.try_into_usize(version))
4646
.transpose()?
4747
.unwrap_or_default();
48-
let object_hash = (repo_format_version != 1)
49-
.then_some(Ok(gix_hash::Kind::Sha1))
50-
.or_else(|| {
51-
config
52-
.string(Extensions::OBJECT_FORMAT)
53-
.map(|format| Extensions::OBJECT_FORMAT.try_into_object_format(format))
54-
})
55-
.transpose()?
56-
.unwrap_or(gix_hash::Kind::Sha1);
48+
let object_hash = match (repo_format_version, config.string(Extensions::OBJECT_FORMAT)) {
49+
// objectFormat is a repository format version 1 extension.
50+
(1, Some(format)) => Extensions::OBJECT_FORMAT.try_into_object_format(format)?,
51+
(0, Some(_)) => return Err(Error::ObjectFormatRequiresV1),
52+
(0 | 1, None) => legacy_object_hash()?,
53+
(version, _) => return Err(Error::UnsupportedRepositoryFormatVersion { version }),
54+
};
5755

5856
let extension_worktree = util::config_bool(
5957
&config,
@@ -104,6 +102,22 @@ impl StageOne {
104102
}
105103
}
106104

105+
/// Return the object hash for a repository that does not set `extensions.objectFormat`.
106+
///
107+
/// Git interprets a missing objectFormat as the original Sha1 layout, so we return
108+
/// gix_hash::Kind::Sha1 whenever this build can handle it.
109+
/// In Sha256-only builds we cannot open such a repository, so return an error instead.
110+
fn legacy_object_hash() -> Result<gix_hash::Kind, Error> {
111+
#[cfg(feature = "sha1")]
112+
{
113+
Ok(gix_hash::Kind::Sha1)
114+
}
115+
#[cfg(not(feature = "sha1"))]
116+
{
117+
Err(Error::UnsupportedObjectFormat { name: "sha1".into() })
118+
}
119+
}
120+
107121
fn load_config(
108122
config_path: std::path::PathBuf,
109123
buf: &mut Vec<u8>,

gix/src/config/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ pub enum Error {
8686
RefsNamespace(#[from] refs_namespace::Error),
8787
#[error("Cannot handle objects formatted as {:?}", .name)]
8888
UnsupportedObjectFormat { name: BString },
89+
#[error(
90+
"extensions.objectFormat is a v1-only extension, but the repository format version is 0; \
91+
set core.repositoryFormatVersion=1 to use it, or remove extensions.objectFormat to fall back to the default Sha1 format (if supported by this build)"
92+
)]
93+
ObjectFormatRequiresV1,
94+
#[error("Unsupported repository format version {version}; only versions 0 and 1 are supported")]
95+
UnsupportedRepositoryFormatVersion { version: usize },
8996
#[error(transparent)]
9097
CoreAbbrev(#[from] abbrev::Error),
9198
#[error("Could not read configuration file at \"{}\"", path.display())]

gix/src/config/tree/sections/core.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,11 @@ mod validate {
476476
pub struct Abbrev;
477477
impl keys::Validate for Abbrev {
478478
fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
479-
// TODO: when there is options, validate against all hashes and assure all fail to trigger a validation failure.
480-
super::Core::ABBREV.try_into_abbreviation(value.into(), gix_hash::Kind::Sha1)?;
479+
// The keys::Validate trait API doesn't take a hash kind, and passing one through
480+
// would touch ~50 impl sites. The repo-aware check with the actual hash runs in
481+
// config::cache::util::parse_core_abbrev, so here we just use Kind::longest()
482+
// to allow the most permissive upper bound.
483+
super::Core::ABBREV.try_into_abbreviation(value.into(), gix_hash::Kind::longest())?;
481484
Ok(())
482485
}
483486
}

gix/src/create.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn create_dir(p: &Path) -> Result<(), Error> {
108108
}
109109

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

137+
impl Default for Options {
138+
fn default() -> Self {
139+
Options {
140+
destination_must_be_empty: None,
141+
fs_capabilities: None,
142+
object_hash: default_object_hash(),
143+
}
144+
}
145+
}
146+
147+
fn default_object_hash() -> Option<gix_hash::Kind> {
148+
#[cfg(feature = "sha1")]
149+
{
150+
None
151+
}
152+
#[cfg(all(not(feature = "sha1"), feature = "sha256"))]
153+
{
154+
Some(gix_hash::Kind::Sha256)
155+
}
156+
#[cfg(all(not(feature = "sha1"), not(feature = "sha256")))]
157+
{
158+
unreachable!("hash support features are validated by gix-hash")
159+
}
160+
}
161+
137162
/// Create a new `.git` repository of `kind` within the possibly non-existing `directory`
138163
/// and return its path.
139164
/// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections
@@ -286,3 +311,22 @@ fn bool(v: bool) -> &'static str {
286311
false => "false",
287312
}
288313
}
314+
315+
#[cfg(test)]
316+
mod tests {
317+
#[test]
318+
fn default_object_hash_matches_available_hash_support() {
319+
let object_hash = super::Options::default().object_hash;
320+
#[cfg(feature = "sha1")]
321+
assert_eq!(
322+
object_hash, None,
323+
"SHA1-capable builds keep Git's implicit legacy object format"
324+
);
325+
#[cfg(all(not(feature = "sha1"), feature = "sha256"))]
326+
assert_eq!(
327+
object_hash,
328+
Some(gix_hash::Kind::Sha256),
329+
"SHA256-only builds must initialize repositories that can be reopened"
330+
);
331+
}
332+
}
134 KB
Binary file not shown.
Binary file not shown.

gix/tests/fixtures/make_config_repos.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,31 @@ mkdir not-a-repo-with-files;
165165
touch this that
166166
)
167167

168+
for format in sha1 sha256; do
169+
git init "objectformat-$format-with-repository-format-v0"
170+
(cd "objectformat-$format-with-repository-format-v0"
171+
# use overrides to get a repository that Git refuses to open/edit.
172+
cat <<EOF >>.git/config
173+
[core]
174+
repositoryFormatVersion = 0
175+
[extensions]
176+
objectFormat = $format
177+
EOF
178+
)
179+
done
180+
181+
git init repository-format-v2-with-objectformat-sha1
182+
(cd repository-format-v2-with-objectformat-sha1
183+
# Future repository formats may change invariants beyond the object hash. Git
184+
# refuses such repositories before interpreting extensions; gix should too.
185+
cat <<EOF >>.git/config
186+
[core]
187+
repositoryFormatVersion = 2
188+
[extensions]
189+
objectFormat = sha1
190+
EOF
191+
)
192+
168193
git init ssl-verify-disabled
169194
(cd ssl-verify-disabled
170195
git config http.sslVerify false

gix/tests/gix/repository/open.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,50 @@ mod not_a_repository {
380380
}
381381
}
382382

383+
mod object_format_extension {
384+
use crate::util::named_subrepo_opts;
385+
386+
#[test]
387+
fn rejects_object_format_on_v0_repo() -> crate::Result {
388+
// objectFormat is a "v1-only" extension: git refuses to operate on a version-0 repo that
389+
// sets it, even for sha1 (unlike grandfathered extensions like preciousObjects, which v0
390+
// still honours). This rejection was introduced in git 2.29.0 (2020). We match it.
391+
for name in [
392+
"objectformat-sha256-with-repository-format-v0",
393+
"objectformat-sha1-with-repository-format-v0",
394+
] {
395+
let err = named_subrepo_opts("make_config_repos.sh", name, gix::open::Options::isolated())
396+
.expect_err("a v0 repository setting extensions.objectFormat must be rejected");
397+
assert!(
398+
matches!(
399+
err,
400+
gix::open::Error::Config(gix::config::Error::ObjectFormatRequiresV1)
401+
),
402+
"objectFormat on a v0 repository must be rejected, got {err:?} for {name}"
403+
);
404+
}
405+
Ok(())
406+
}
407+
408+
#[test]
409+
fn rejects_future_repository_format_versions() -> crate::Result {
410+
let err = named_subrepo_opts(
411+
"make_config_repos.sh",
412+
"repository-format-v2-with-objectformat-sha1",
413+
gix::open::Options::isolated(),
414+
)
415+
.expect_err("future repository format versions must be rejected");
416+
assert!(
417+
matches!(
418+
err,
419+
gix::open::Error::Config(gix::config::Error::UnsupportedRepositoryFormatVersion { version: 2 })
420+
),
421+
"future repository format versions must be rejected before interpreting extensions, got {err:?}"
422+
);
423+
Ok(())
424+
}
425+
}
426+
383427
mod open_path_as_is {
384428

385429
use crate::util::{named_subrepo_opts, repo_opts};

justfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ check:
142142
cargo check -p gix --no-default-features --features blame --tests
143143
cargo check -p gix --no-default-features --features sha1
144144
cargo check -p gix --no-default-features --features sha1,sha256
145+
cargo check -p gix --no-default-features --features sha256
145146
cargo check -p gix --no-default-features 2>&1 >/dev/null | grep 'Please set either the `sha1` or the `sha256` feature flag'
146147
cargo check -p gix-odb --features serde 2>&1 >/dev/null | grep 'Please set either the `sha1` or the `sha256` feature flag'
147148
cargo check -p gix-odb --features sha1,serde
@@ -215,6 +216,7 @@ unit-tests:
215216
cargo nextest run -p gix --features async-network-client --no-fail-fast
216217
cargo nextest run -p gix --features blocking-network-client --no-fail-fast
217218
env GIX_TEST_FIXTURE_HASH=sha256 cargo nextest run -p gix --no-fail-fast
219+
cargo nextest run -p gix --no-default-features --features sha256 --lib --no-fail-fast
218220
cargo nextest run -p gitoxide-core --lib --no-tests=warn --no-fail-fast
219221

220222
# Run all doctests

0 commit comments

Comments
 (0)