use crate::diagnostic_log::debug;
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).to_vec() } else { 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) => Some(listed.iter().map(|path| { return path.as_bytes().to_vec(); }).collect()),
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 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 mut effective: Vec<(OsString, OsString)> = std::env::vars_os().filter(|(name, _)| { return !added.iter().any(|(own, _)| { return own == name; }); }).collect();
effective.extend(added);
effective.retain(|(name, _)| { return name != "GIT_INDEX_FILE" && name != "GIT_OBJECT_DIRECTORY"; });
if effective.iter().any(|(name, value)| { return name == "GIT_TEST_FSMONITOR" && !value.is_empty(); }) {
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() && stored.iter().any(|byte| { return *byte != 0; }) {
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 bytes: Vec<u8> = write_full_index(&index, &|data: &[u8]| { return checksum(format, data); })?;
return publish(built, bytes.as_slice(), around.settings.shared_repository).ok();
}
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(|| { return reset_file(git, repository, new, paths, built.as_path()); }).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;