use std::ops::ControlFlow;
use std::path::Path;
use std::sync::Arc;
use crate::error::Result;
use crate::hooks::RefHookContext;
use crate::lockfile::LockWait;
use crate::objects::ObjectId;
use crate::odb::Odb;
use crate::reflog::ReflogEntry;
type StoreResult<T> = std::result::Result<T, RefStoreError>;
type ObjectPartitionResult = (Vec<RefUpdate>, Vec<(String, RefStoreError)>);
use super::backend::{is_per_ref_rejectable, verify_direct_targets};
use super::reference_transaction::prepare_with_reference_transaction_hooks;
use super::{
FilesRefStore, PreparedRefTransaction, RawRef, RefEntry, RefStorageFormat, RefStore,
RefStoreError, RefTransaction, RefUpdate,
};
pub(crate) fn verify_update_targets(odb: &Odb, updates: &[RefUpdate]) -> StoreResult<()> {
odb.with_pack_read_context(|| verify_direct_targets(updates, |oid| odb.stored_object_kind(oid)))
}
fn partition_object_target_failures(
odb: &Odb,
updates: &[RefUpdate],
) -> StoreResult<ObjectPartitionResult> {
let mut accepted = Vec::with_capacity(updates.len());
let mut rejected = Vec::new();
for update in updates {
match verify_update_targets(odb, std::slice::from_ref(update)) {
Ok(()) => accepted.push(update.clone()),
Err(err) if is_per_ref_rejectable(&err) => rejected.push((update.name.clone(), err)),
Err(err) => return Err(err),
}
}
Ok((accepted, rejected))
}
fn txn_with_accepted_updates(
txn: &RefTransaction,
accepted: Vec<RefUpdate>,
) -> StoreResult<RefTransaction> {
let mut filtered = RefTransaction::from_updates(accepted)?;
if txn.is_initial() {
filtered = filtered.initial();
}
if txn.allows_failures() {
filtered = filtered.allowing_failures();
}
for rename in txn.reflog_renames() {
filtered = filtered.with_reflog_rename(rename.clone());
}
for rename in txn.case_only_renames() {
filtered = filtered.with_case_only_rename(rename.clone());
}
Ok(filtered)
}
struct ObjectCheckedPrepared<'a> {
rejections: Vec<(String, RefStoreError)>,
inner: Option<Box<dyn PreparedRefTransaction + Send + 'a>>,
}
impl PreparedRefTransaction for ObjectCheckedPrepared<'_> {
fn rejected_updates(&self) -> &[(String, RefStoreError)] {
&self.rejections
}
fn commit(mut self: Box<Self>) -> StoreResult<()> {
if let Some(inner) = self.inner.take() {
inner.commit()?;
}
Ok(())
}
fn abort(mut self: Box<Self>) -> StoreResult<()> {
if let Some(inner) = self.inner.take() {
inner.abort()?;
}
Ok(())
}
}
fn wrap_checked_prepared<'a>(
object_rejections: Vec<(String, RefStoreError)>,
inner: Box<dyn PreparedRefTransaction + Send + 'a>,
) -> Box<dyn PreparedRefTransaction + Send + 'a> {
let mut rejections = object_rejections;
rejections.extend(
inner
.rejected_updates()
.iter()
.map(|(name, err)| (name.clone(), err.clone())),
);
Box::new(ObjectCheckedPrepared {
rejections,
inner: Some(inner),
})
}
pub(crate) fn open_checked_ref_store(git_dir: &Path) -> Result<ObjectCheckedRefStore> {
let common_dir = crate::refs::common_dir(git_dir).unwrap_or_else(|| git_dir.to_path_buf());
let odb = Odb::new(&common_dir.join("objects")).with_config_git_dir(git_dir.to_path_buf());
Ok(ObjectCheckedRefStore::new(
super::open_ref_store(git_dir)?,
odb,
))
}
pub(crate) struct ObjectCheckedRefStore {
inner: Arc<dyn RefStore>,
odb: Odb,
hooks: Option<Arc<RefHookContext>>,
}
impl ObjectCheckedRefStore {
pub(crate) fn new(inner: Arc<dyn RefStore>, odb: Odb) -> Self {
Self {
inner,
odb,
hooks: None,
}
}
pub(crate) fn with_ref_hooks(mut self, hooks: Arc<RefHookContext>) -> Self {
self.hooks = Some(hooks);
self
}
pub(crate) fn inner(&self) -> &Arc<dyn RefStore> {
&self.inner
}
pub(crate) fn null_oid(&self) -> ObjectId {
self.hooks
.as_ref()
.map(|ctx| ctx.null_oid())
.unwrap_or_else(|| ObjectId::null(self.odb.hash_algo()))
}
}
impl std::fmt::Debug for ObjectCheckedRefStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ObjectCheckedRefStore")
.field("inner", &self.inner)
.finish_non_exhaustive()
}
}
impl RefStore for ObjectCheckedRefStore {
fn format(&self) -> RefStorageFormat {
self.inner.format()
}
fn read_raw(&self, name: &str) -> Result<Option<RawRef>> {
self.inner.read_raw(name)
}
fn resolve(&self, name: &str) -> Result<ObjectId> {
self.inner.resolve(name)
}
fn for_each_ref(
&self,
prefix: &str,
f: &mut dyn FnMut(&RefEntry) -> ControlFlow<()>,
) -> Result<()> {
self.inner.for_each_ref(prefix, f)
}
fn prepare(
&self,
txn: RefTransaction,
) -> StoreResult<Box<dyn PreparedRefTransaction + Send + '_>> {
txn.validate()?;
let (txn, object_rejections) = if txn.allows_failures() {
let (accepted, object_rejections) =
partition_object_target_failures(&self.odb, txn.updates())?;
let txn = txn_with_accepted_updates(&txn, accepted)?;
(txn, object_rejections)
} else {
self.verify_update_targets(txn.updates())?;
(txn, Vec::new())
};
if txn.allows_failures()
&& txn.updates().is_empty()
&& txn.reflog_renames().is_empty()
&& txn.case_only_renames().is_empty()
{
return Ok(Box::new(ObjectCheckedPrepared {
rejections: object_rejections,
inner: None,
}));
}
let prepared = prepare_with_reference_transaction_hooks(
self,
self.inner.as_ref(),
self.hooks.as_ref(),
self.null_oid(),
txn,
)?;
if object_rejections.is_empty() {
Ok(prepared)
} else {
Ok(wrap_checked_prepared(object_rejections, prepared))
}
}
fn verify_update_targets(&self, updates: &[RefUpdate]) -> StoreResult<()> {
verify_update_targets(&self.odb, updates)
}
fn reflog_exists(&self, name: &str) -> Result<bool> {
self.inner.reflog_exists(name)
}
fn for_each_reflog_entry(
&self,
name: &str,
reverse: bool,
f: &mut dyn FnMut(&ReflogEntry) -> ControlFlow<()>,
) -> Result<()> {
self.inner.for_each_reflog_entry(name, reverse, f)
}
fn create_reflog(&self, name: &str) -> Result<()> {
self.inner.create_reflog(name)
}
fn delete_reflog(&self, name: &str) -> Result<()> {
self.inner.delete_reflog(name)
}
fn replace_reflog(&self, name: &str, entries: Vec<ReflogEntry>) -> Result<()> {
self.inner.replace_reflog(name, entries)
}
fn rewrite_reflog(
&self,
name: &str,
transform: &mut dyn FnMut(Vec<ReflogEntry>) -> Result<Vec<ReflogEntry>>,
) -> Result<()> {
self.inner.rewrite_reflog(name, transform)
}
fn for_each_reflog_ref(&self, f: &mut dyn FnMut(&str) -> ControlFlow<()>) -> Result<()> {
self.inner.for_each_reflog_ref(f)
}
fn as_files(&self) -> Option<&FilesRefStore> {
self.inner.as_files()
}
fn ref_hook_context(&self) -> Option<&RefHookContext> {
self.hooks.as_deref()
}
fn reference_transaction_null_oid(&self) -> ObjectId {
self.null_oid()
}
fn ref_lock_wait(&self) -> LockWait {
if let Some(ctx) = self.hooks.as_ref() {
return LockWait::files_ref_lock_for_git_dir(ctx.git_dir());
}
if let Some(files) = self.inner.as_files() {
return LockWait::files_ref_lock_for_git_dir(files.git_dir());
}
if let Some(git_dir) = self.odb.config_git_dir() {
return LockWait::files_ref_lock_for_git_dir(git_dir);
}
LockWait::files_ref_lock(None)
}
}