use super::capture_invocation::InvocationCapture;
use super::commit_conclusion::events_with_summary;
use super::commit_landing_lock::acquire_landing_lock;
use super::commit_transaction::{CommitInvocation, TransactionResult, TransactionStop, failed};
use super::commit_workspace::CommitWorkspace;
use super::landing_attempt::{LandingOutcome, LandingPayload, LandingRequest, land_transaction};
use super::landing_findings::{LANDING_FINDING_EXIT_CODE, landing_finding};
use super::owner_lock::OwnerLock;
use super::policy_events::PolicyEvent;
use super::transaction_git::{GitFailure, GitRequest, run_git_checked};
pub const NO_CHANGE_CORE_ID: &str = "commit-normalization";
pub const NO_CHANGE_CODE: &str = "no-change";
use super::transaction_journal::{AddedPath, Base, TransactionMode};
use super::worktree_completion::{InstallResult, install_added_worktree_files};
pub struct NoChangeInput<'a> {
pub invocation: &'a CommitInvocation<'a>,
pub workspace: &'a mut CommitWorkspace,
pub capture: &'a InvocationCapture,
pub mode: TransactionMode,
pub eligible: bool,
pub intended_tree: &'a str,
pub committed_paths: &'a [String],
pub selected_worktree_paths: &'a [AddedPath],
pub events: &'a [PolicyEvent],
pub changed_passes: u64,
pub changed_paths: &'a [String],
pub index_lock_timeout_ms: u64,
}
fn base_tree(input: &NoChangeInput<'_>, base: &str) -> Result<String, GitFailure> {
let revision: String = format!("{base}^{{tree}}");
let mut request: GitRequest = GitRequest::new(input.invocation.cwd, &["rev-parse", "--verify", revision.as_str()]);
request.without_prefix = true;
let output = run_git_checked(input.invocation.context, &request)?;
return Ok(String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string());
}
fn no_change_message(conflicted: &[String]) -> String {
if conflicted.is_empty() {
return String::from(
"No commit created: normalization removed every selected change; matching worktree and index copies were reconciled.",
);
}
let listed: String = serde_json::to_string(conflicted).unwrap_or_default();
return format!(
"No commit created: normalization removed every selected change; index copies were reconciled, but worktree copies changed and were kept: {listed}. Compare them with HEAD before retrying."
);
}
pub fn complete_no_change_transaction(input: NoChangeInput<'_>) -> Result<Option<TransactionResult>, TransactionStop> {
let Base::Commit(base) = input.capture.base.clone() else {
return Ok(None);
};
if !input.eligible {
return Ok(None);
}
if base_tree(&input, base.as_str())? != input.intended_tree {
return Ok(None);
}
let summary: Vec<PolicyEvent> = events_with_summary(input.events.to_vec(), input.changed_passes, input.changed_paths);
let payload: LandingPayload = LandingPayload::NormalizeOnly {
landed_tree_oid: String::from(input.intended_tree),
};
let request: LandingRequest<'_> = LandingRequest {
capture: input.capture,
mode: input.mode,
payload: &payload,
committed_paths: input.committed_paths,
added_paths: &[],
selected_worktree_paths: input.selected_worktree_paths,
index_lock_timeout_ms: input.index_lock_timeout_ms,
attempt: 1,
};
let outcome: LandingOutcome = {
let lock: OwnerLock = acquire_landing_lock(
input.invocation.context,
input.invocation.cwd,
&input.capture.layout,
input.invocation.environment,
)
.map_err(|error| { return failed(error.0); })?;
land_transaction(
input.invocation.context,
input.invocation.cwd,
&lock,
&mut input.workspace.landing,
&request,
)?
};
if let Some(event) = landing_finding(&outcome, input.capture, None, false) {
let mut events: Vec<PolicyEvent> = summary;
events.push(event);
return Ok(Some(TransactionResult {
events,
exit_code: LANDING_FINDING_EXIT_CODE,
landed_oid: None,
}));
}
let completion: InstallResult = install_added_worktree_files(
input.invocation.context,
input.invocation.cwd,
input.capture.layout.repository_root.as_path(),
input.selected_worktree_paths,
Some(input.workspace.object_directory.as_path()),
)
.map_err(|error| { return TransactionStop::Message(error.0); })?;
input.workspace.finish_transaction();
let mut events: Vec<PolicyEvent> = summary;
events.push(PolicyEvent::CoreFinding {
core_id: NO_CHANGE_CORE_ID,
code: NO_CHANGE_CODE,
message: no_change_message(completion.conflicted.as_slice()),
});
return Ok(Some(TransactionResult {
events,
exit_code: 1,
landed_oid: None,
}));
}
#[cfg(test)]
#[path = "commit_no_change_tests.rs"]
mod tests;