use crate::diagnostic_log::debug;
use crate::index_byte_search::has_nonzero_byte;
use crate::index_full::{FullIndex, parse_full_index};
use crate::index_full_write::write_full_index;
use crate::index_reset::reset_paths;
use crate::index_reset_publish::publish;
use crate::service_index_lock::IndexLock;
use crate::index_reset_tree::{changed_paths, root_tree, tree_entries_at};
use crate::index_settings::{IndexSettings, read_settings};
use crate::repo_read_config_list::list_config;
use crate::repo_read_config_scan::ScopeEnvironment;
use crate::repo_read_hook::{enabled, guarded};
use crate::repo_read_layout::{ObjectFormat, RepoLayout, read_layout_with};
use crate::repo_read_object::name_length;
use crate::repo_read_odb::ObjectDatabase;
use crate::repo_read_tree::FlatEntry;
use crate::service_land_follow::{IndexPaths, PrivateCopy};
use crate::service_protocol::is_zero_object;
use crate::subvolume_sha::{sha1, sha256};
use crate::transaction_git::GitContext;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
const INDEX_LIMIT: u64 = 1 << 30;
const POST_INDEX_HOOK: &str = "post-index-change";
fn checksum(format: ObjectFormat, bytes: &[u8]) -> Vec<u8> {
return if name_length(format) == 20 { sha1_bytes(bytes) } else { sha256_bytes(bytes) };
}
fn sha1_bytes(bytes: &[u8]) -> Vec<u8> {
return sha1(bytes).to_vec();
}
fn sha256_bytes(bytes: &[u8]) -> Vec<u8> {
return sha256(bytes).to_vec();
}
fn names_of(database: &ObjectDatabase, layout: &RepoLayout, new_tree: &[u8], paths: IndexPaths<'_>) -> Option<Vec<Vec<u8>>> {
return match paths {
IndexPaths::Listed(listed) => {
let mut names: Vec<Vec<u8>> = Vec::new();
for path in listed {
names.push(path.as_bytes().to_vec());
}
Some(names)
}
IndexPaths::Named(named) => Some(named.to_vec()),
IndexPaths::Derived { old } => {
let old_tree: Option<Vec<u8>> = if is_zero_object(old) { None } else { Some(root_tree(database, old, layout.object_format)?) };
changed_paths(database, old_tree.as_deref(), new_tree, layout.object_format)
}
};
}
struct Surroundings {
layout: RepoLayout,
settings: IndexSettings,
database: ObjectDatabase,
}
fn effective_environment(added: Vec<(OsString, OsString)>) -> Vec<(OsString, OsString)> {
let mut effective: Vec<(OsString, OsString)> = Vec::new();
for (name, value) in std::env::vars_os() {
let mut replaced: bool = false;
for (own, _) in &added {
if *own == name {
replaced = true;
break;
}
}
if !replaced {
effective.push((name, value));
}
}
effective.extend(added);
let mut kept: Vec<(OsString, OsString)> = Vec::new();
for (name, value) in effective {
if name != "GIT_INDEX_FILE" && name != "GIT_OBJECT_DIRECTORY" {
kept.push((name, value));
}
}
return kept;
}
fn turns_monitor_on(effective: &[(OsString, OsString)]) -> bool {
for (name, value) in effective {
if name == "GIT_TEST_FSMONITOR" && !value.is_empty() {
return true;
}
}
return false;
}
fn surroundings(git: &GitContext, repository: &Path) -> Option<Surroundings> {
let added: Vec<(OsString, OsString)> = crate::git_system_config::with_real_git(git.overlay.as_slice(), git.real_git.as_path());
if !enabled(added.as_slice()) {
return None;
}
let effective: Vec<(OsString, OsString)> = effective_environment(added);
if turns_monitor_on(effective.as_slice()) {
return None;
}
let scope: ScopeEnvironment = ScopeEnvironment::from_variables(effective.as_slice());
let layout: RepoLayout = read_layout_with(repository, effective.as_slice(), &scope)?;
let listed = list_config(layout.common_dir.as_path(), layout.git_dir.as_path(), &scope, effective.as_slice())?;
let settings: IndexSettings = read_settings(listed.as_slice())?;
if layout.common_dir.join("hooks").join(POST_INDEX_HOOK).symlink_metadata().is_ok() {
return None;
}
let database: ObjectDatabase = ObjectDatabase::open(layout.common_dir.as_path(), None, layout.object_format)?;
return Some(Surroundings { layout, settings, database });
}
fn read_index(file: &Path, format: ObjectFormat) -> Option<(FullIndex, u32)> {
let bytes: Vec<u8> = crate::private_storage::read_regular_file(file, INDEX_LIMIT).ok()?;
let hash_length: usize = name_length(format);
let (body, stored) = bytes.split_at(bytes.len().checked_sub(hash_length)?);
if stored != checksum(format, body).as_slice() && has_nonzero_byte(stored) {
return None;
}
let modified: std::time::SystemTime = std::fs::metadata(file).ok()?.modified().ok()?;
let seconds: u64 = modified.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs();
return Some((parse_full_index(bytes.as_slice(), hash_length)?, seconds as u32));
}
fn reset_file(git: &GitContext, repository: &Path, new: &str, paths: IndexPaths<'_>, built: &Path) -> Option<()> {
let around: Surroundings = surroundings(git, repository)?;
let (mut index, timestamp) = read_index(built, around.layout.object_format)?;
let new_tree: Vec<u8> = root_tree(&around.database, new, around.layout.object_format)?;
let names: Vec<Vec<u8>> = names_of(&around.database, &around.layout, new_tree.as_slice(), paths)?;
if names.is_empty() {
return Some(());
}
let tree: Vec<FlatEntry> = tree_entries_at(&around.database, new_tree.as_slice(), names.as_slice(), around.layout.object_format)?;
reset_paths(&mut index, names.as_slice(), tree.as_slice(), around.settings, timestamp)?;
let format: ObjectFormat = around.layout.object_format;
let hash: fn(&[u8]) -> Vec<u8> = if name_length(format) == 20 { sha1_bytes } else { sha256_bytes };
let bytes: Vec<u8> = write_full_index(&index, &hash)?;
return publish(built, bytes.as_slice(), around.settings.shared_repository).ok();
}
fn reset_file_guarded((git, repository, new, paths, built): (&GitContext, &Path, &str, IndexPaths<'_>, &Path)) -> Option<()> {
return reset_file(git, repository, new, paths, built);
}
pub fn reset_index_file(git: &GitContext, repository: &Path, new: &str, paths: IndexPaths<'_>, lock: &IndexLock, copy: &PrivateCopy) -> bool {
if copy.directory().parent() != lock.index().parent() {
return false;
}
let built: PathBuf = copy.path().to_path_buf();
let done: bool = guarded((git, repository, new, paths, built.as_path()), reset_file_guarded).is_some();
copy.set_flushed(done);
debug("indexResetFile", format!("in-process reset of {}: {}", built.display(), if done { "done" } else { "declined, Git answers" }).as_str());
return done;
}
#[cfg(test)]
#[path = "index_reset_support.rs"]
pub(crate) mod support;
#[cfg(test)]
#[path = "index_reset_file_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "index_reset_fallback_tests.rs"]
mod fallback_tests;