use std::sync::atomic::{AtomicBool, Ordering};
use super::{Error, State};
use crate::apply::{IndexLimits, TreeOptions, WorktreeLimits, WorktreeReport};
use crate::{OperationState, Repository, filter, pathspec, status};
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct ApplyLimits {
pub patch: crate::apply::Limits,
pub index: IndexLimits,
pub worktree: WorktreeLimits,
pub status: status::Limits,
}
impl ApplyLimits {
#[must_use]
pub fn with_patch(mut self, value: crate::apply::Limits) -> Self {
self.patch = value;
self
}
#[must_use]
pub fn with_index(mut self, value: IndexLimits) -> Self {
self.index = value;
self
}
#[must_use]
pub fn with_worktree(mut self, value: WorktreeLimits) -> Self {
self.worktree = value;
self
}
#[must_use]
pub fn with_status(mut self, value: status::Limits) -> Self {
self.status = value;
self
}
}
#[derive(Debug, thiserror::Error)]
#[error("staging the current am patch failed")]
#[non_exhaustive]
pub struct ApplyFailure {
#[source]
pub source: crate::Error,
pub worktree: WorktreeReport,
}
pub fn apply_current(
repo: &Repository,
snapshot: &OperationState,
limits: ApplyLimits,
cancel: &AtomicBool,
) -> Result<WorktreeReport, ApplyFailure> {
apply_with_hook(repo, snapshot, limits, cancel, || {})
}
pub(super) fn apply_with_hook(
repo: &Repository,
snapshot: &OperationState,
limits: ApplyLimits,
cancel: &AtomicBool,
before_index: impl FnOnce(),
) -> Result<WorktreeReport, ApplyFailure> {
let mut report = WorktreeReport::default();
let result = (|| -> crate::Result<()> {
check(cancel)?;
snapshot.verify_for(repo)?;
let state = State::from_operation(snapshot)?.ok_or(Error::NotReady("no am operation"))?;
if state.mail.is_none() {
return Err(Error::NotReady("completed mail range").into());
}
let bytes = state.patch.ok_or(Error::NotReady("patch not prepared"))?;
let patch = crate::apply::parse::parse_mail(bytes, limits.patch)?;
let mut edit = repo.edit_index(limits.index.index)?;
clean(repo, limits.status, cancel)?;
let applied = patch.apply_index_with(
&repo.objects()?,
edit.index(),
TreeOptions::default(),
limits.index,
cancel,
)?;
let mut filters = repo.filters(&Default::default(), filter::Commands::Refuse)?;
let working = patch.prepare_worktree_with(
repo,
&mut filters,
TreeOptions::default(),
limits.worktree,
cancel,
)?;
let (index, tree) = applied.into_parts();
edit.replace_index(index)?;
snapshot.verify_for(repo)?;
clean(repo, limits.status, cancel)?;
check(cancel)?;
tree.install_objects(&repo.loose_objects())?;
report = match working.publish_with(cancel) {
Ok(report) => report,
Err(failure) => {
report = failure.report;
return Err((*failure.cause).into());
}
};
before_index();
edit.commit()?;
Ok(())
})();
match result {
Ok(()) => Ok(report),
Err(source) => Err(ApplyFailure {
source,
worktree: report,
}),
}
}
fn check(cancel: &AtomicBool) -> Result<(), Error> {
if cancel.load(Ordering::Relaxed) {
Err(Error::Cancelled)
} else {
Ok(())
}
}
fn clean(repo: &Repository, limits: status::Limits, cancel: &AtomicBool) -> crate::Result<()> {
let report = repo.raw_status_with(
status::Baseline::Head,
status::Untracked::Omit,
&pathspec::Pathspec::all(),
limits,
cancel,
)?;
if report.index_missing
|| !report.staged.is_empty()
|| !report.unstaged.is_empty()
|| !report.unmerged.is_empty()
|| !report.unchecked_gitlinks.is_empty()
|| !report.boundaries.is_empty()
{
return Err(Error::NotReady("tracked state is not raw-clean").into());
}
Ok(())
}