use std::env;
use std::fs;
use std::io;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FsyncComponents(u32);
impl FsyncComponents {
pub const NONE: Self = Self(0);
pub const LOOSE_OBJECT: Self = Self(1 << 0);
pub const PACK: Self = Self(1 << 1);
pub const PACK_METADATA: Self = Self(1 << 2);
pub const COMMIT_GRAPH: Self = Self(1 << 3);
pub const INDEX: Self = Self(1 << 4);
pub const REFERENCE: Self = Self(1 << 5);
pub const OBJECT_MAP: Self = Self(1 << 6);
pub const OBJECTS: Self = Self(Self::LOOSE_OBJECT.0 | Self::PACK.0);
pub const DERIVED_METADATA: Self = Self(Self::PACK_METADATA.0 | Self::COMMIT_GRAPH.0);
pub const DEFAULT: Self = Self(
(Self::OBJECTS.0 | Self::DERIVED_METADATA.0) & !Self::LOOSE_OBJECT.0,
);
pub const COMMITTED: Self = Self(Self::OBJECTS.0 | Self::REFERENCE.0);
pub const ADDED: Self = Self(Self::COMMITTED.0 | Self::INDEX.0);
pub const ALL: Self = Self(
Self::LOOSE_OBJECT.0
| Self::PACK.0
| Self::PACK_METADATA.0
| Self::COMMIT_GRAPH.0
| Self::INDEX.0
| Self::REFERENCE.0
| Self::OBJECT_MAP.0,
);
pub const PLATFORM_DEFAULT: Self = Self::DEFAULT;
const COMPONENT_TABLE: [(&str, Self); 11] = [
("loose-object", Self::LOOSE_OBJECT),
("pack", Self::PACK),
("pack-metadata", Self::PACK_METADATA),
("commit-graph", Self::COMMIT_GRAPH),
("index", Self::INDEX),
("objects", Self::OBJECTS),
("reference", Self::REFERENCE),
("derived-metadata", Self::DERIVED_METADATA),
("committed", Self::COMMITTED),
("added", Self::ADDED),
("all", Self::ALL),
];
pub fn parse(value: &str) -> Self {
let mut current = Self::PLATFORM_DEFAULT;
let mut positive = Self::NONE;
let mut negative = Self::NONE;
for raw_component in value.split(',') {
let component = raw_component.trim();
if component == "none" {
current = Self::NONE;
continue;
}
if component.is_empty() {
continue;
}
let Some(name) = component.strip_prefix('-') else {
for (table_name, bits) in Self::COMPONENT_TABLE {
if table_name.starts_with(component) {
positive = positive.union(bits);
}
}
continue;
};
if name.is_empty() {
break;
}
for (table_name, bits) in Self::COMPONENT_TABLE {
if table_name.starts_with(name) {
negative = negative.union(bits);
}
}
}
current.without(negative).union(positive)
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub const fn bits(self) -> u32 {
self.0
}
pub const fn includes_reference(self) -> bool {
self.contains(Self::REFERENCE)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FsyncMethod {
Fsync,
WriteoutOnly,
Batch,
}
impl FsyncMethod {
pub const fn platform_default() -> Self {
#[cfg(target_os = "windows")]
{
Self::Batch
}
#[cfg(all(not(target_os = "windows"), target_os = "macos"))]
{
Self::WriteoutOnly
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
Self::Fsync
}
}
pub fn from_config(value: Option<&str>) -> Self {
match value {
Some("fsync") => Self::Fsync,
Some("writeout-only") => Self::WriteoutOnly,
Some("batch") => Self::Batch,
_ => Self::platform_default(),
}
}
pub fn apply(self, file: &fs::File) -> io::Result<()> {
match self {
Self::WriteoutOnly => file.sync_data(),
Self::Fsync | Self::Batch => file.sync_all(),
}
}
}
pub fn test_fsync_enabled() -> bool {
let Ok(value) = env::var("GIT_TEST_FSYNC") else {
return true;
};
!matches!(
value.to_ascii_lowercase().as_str(),
"0" | "false" | "no" | "off" | ""
)
}
pub trait FsyncConfigSource {
fn fsync_lookup(&self, section: &str, subsection: Option<&str>, key: &str) -> Option<&str>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Policy {
components: FsyncComponents,
method: FsyncMethod,
use_fsync: bool,
}
impl Default for Policy {
fn default() -> Self {
Self::from_values(None, None)
}
}
impl Policy {
pub fn from_values(core_fsync: Option<&str>, core_fsync_method: Option<&str>) -> Self {
Self {
components: core_fsync.map_or(FsyncComponents::PLATFORM_DEFAULT, FsyncComponents::parse),
method: FsyncMethod::from_config(core_fsync_method),
use_fsync: test_fsync_enabled(),
}
}
pub fn resolve(config: &impl FsyncConfigSource) -> Self {
Self::from_values(
config.fsync_lookup("core", None, "fsync"),
config.fsync_lookup("core", None, "fsyncMethod"),
)
}
pub fn overridden(mut self, core_fsync: Option<&str>, core_fsync_method: Option<&str>) -> Self {
if let Some(value) = core_fsync {
self.components = FsyncComponents::parse(value);
}
if let Some(value) = core_fsync_method {
self.method = FsyncMethod::from_config(Some(value));
}
self.use_fsync = test_fsync_enabled();
self
}
pub const fn components(&self) -> FsyncComponents {
self.components
}
pub const fn method(&self) -> FsyncMethod {
self.method
}
pub const fn syncs(&self, component: FsyncComponents) -> bool {
self.use_fsync && self.components.contains(component)
}
pub const fn method_if_enabled(&self, component: FsyncComponents) -> Option<FsyncMethod> {
if self.syncs(component) {
Some(self.method)
} else {
None
}
}
pub fn apply(&self, file: &fs::File, component: FsyncComponents) -> io::Result<()> {
match self.method_if_enabled(component) {
Some(method) => method.apply(file),
None => Ok(()),
}
}
}
pub fn sync_file(path: &Path, policy: &Policy, component: FsyncComponents) -> io::Result<()> {
let file = fs::OpenOptions::new().write(true).open(path)?;
policy.apply(&file, component)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn component_bits_match_upstream_layout() {
assert_eq!(FsyncComponents::OBJECTS.bits(), 0b0000_0011);
assert_eq!(FsyncComponents::DERIVED_METADATA.bits(), 0b0000_1100);
assert_eq!(
FsyncComponents::DEFAULT.bits(),
FsyncComponents::PACK.bits()
| FsyncComponents::PACK_METADATA.bits()
| FsyncComponents::COMMIT_GRAPH.bits()
);
assert_eq!(
FsyncComponents::COMMITTED.bits(),
FsyncComponents::OBJECTS.union(FsyncComponents::REFERENCE).bits()
);
assert_eq!(
FsyncComponents::ADDED.bits(),
FsyncComponents::COMMITTED
.union(FsyncComponents::INDEX)
.bits()
);
assert_eq!(FsyncComponents::ALL.bits(), 0b0111_1111);
}
#[test]
fn parse_matches_upstream_groups_negation_and_prefixing() {
let reference = FsyncComponents::REFERENCE;
assert!(!FsyncComponents::parse("none").contains(reference));
assert!(FsyncComponents::parse("none,reference").contains(reference));
assert!(!FsyncComponents::parse("none,-reference").contains(reference));
assert!(!FsyncComponents::parse("objects,index").contains(reference));
assert!(!FsyncComponents::parse("-reference").contains(reference));
for value in ["reference", "ref", "committed", "added", "all"] {
assert!(
FsyncComponents::parse(value).contains(reference),
"{value} must include references"
);
}
assert!(FsyncComponents::parse("reference,-reference").contains(reference));
assert!(FsyncComponents::parse("-reference,reference").contains(reference));
assert!(FsyncComponents::parse("reference,none").contains(reference));
assert!(FsyncComponents::parse(
"committed,-loose-object"
)
.contains(reference));
assert!(FsyncComponents::parse("pack").contains(FsyncComponents::PACK_METADATA));
assert_eq!(
FsyncComponents::parse("nonsense").bits(),
FsyncComponents::PLATFORM_DEFAULT.bits()
);
}
#[test]
fn policy_gating_honors_components_and_test_switch() {
let enabled = Policy::from_values(Some("reference"), Some("writeout-only"));
assert!(enabled.syncs(FsyncComponents::REFERENCE) || !test_fsync_enabled());
if test_fsync_enabled() {
assert_eq!(
enabled.method_if_enabled(FsyncComponents::REFERENCE),
Some(FsyncMethod::WriteoutOnly)
);
assert_eq!(enabled.method_if_enabled(FsyncComponents::INDEX), None);
}
let disabled = Policy::from_values(Some("none"), Some("fsync"));
assert_eq!(disabled.method_if_enabled(FsyncComponents::REFERENCE), None);
let default = Policy::from_values(None, None);
assert!(!default.components().contains(FsyncComponents::REFERENCE));
let overridden = disabled.overridden(None, Some("batch"));
assert_eq!(overridden.method(), FsyncMethod::Batch);
let flipped = default.overridden(Some("all"), None);
if test_fsync_enabled() {
assert_eq!(
flipped.method_if_enabled(FsyncComponents::REFERENCE),
Some(FsyncMethod::platform_default())
);
}
}
}