use crate::auto_push_single_flight::PushGit;
use crate::child_environment::environment_with_overlay;
use crate::candidate_prediction::CandidateRequest;
use crate::discard_audit::{AuditOutcome, record_audit};
use crate::manual_push_cause::ManualPushCause;
use crate::manual_push_lifecycle::manual_push_scope;
use crate::policy_checks::ShippedChecks;
use crate::policy_content::LifecycleContent;
use crate::policy_registry::PolicyId;
use crate::landed_summary::short_name;
use crate::transaction_git::GitContext;
use crate::push_failure_notice::{CommitNamer, ORPHAN_NOTICE_DIRECTORY, is_machine_read, notice_directory, take_orphaned_failures, take_push_failures_for};
use crate::worktree_removal_rescue::{settle_removals, snapshot_linked_worktrees};
use crate::rule_linked_worktree::{LinkedWorktreeDecision, decide_linked_worktree};
use crate::repository_facts::RepositoryFacts;
use crate::worktree_identity::worktree_root;
use crate::wrapper_controls::is_escaped;
use crate::wrapper_invocation::{StrippedInvocation, command_region, command_word};
use std::ffi::OsString;
use std::io::Write;
use std::path::{Path, PathBuf};
pub(crate) fn record_escape_use<F: RepositoryFacts>(stripped: &StrippedInvocation, checks: &mut ShippedChecks<F>) {
if !is_escaped(&stripped.controls, PolicyId::LinkedWorktreeOnly) {
return;
}
let command = match decide_linked_worktree(stripped.arguments.as_slice()) {
LinkedWorktreeDecision::Pass => return,
LinkedWorktreeDecision::NeedsEffectiveTarget(command) | LinkedWorktreeDecision::WhenDirty(command) => command,
};
if let Ok(location) = checks.facts.location() {
record_audit(&location.identity, command, AuditOutcome::Hatch, stripped.arguments.as_slice(), None);
}
}
pub(crate) fn show_push_failures<F: RepositoryFacts>(stripped: &StrippedInvocation, checks: &mut ShippedChecks<F>) {
if is_machine_read(command_word(stripped), command_region(stripped)) {
return;
}
let Some(git_dir) = checks.facts.known_git_dir() else { return };
let common_dir: PathBuf = match checks.facts.known_common_dir() {
Some(known) => known,
None => git_dir.clone(),
};
for problem in settle_removals(common_dir.as_path()) {
let _written = writeln!(std::io::stderr(), "{problem}");
}
let own: bool = notice_directory(git_dir.as_path()).is_dir();
let orphaned: bool = common_dir.join(ORPHAN_NOTICE_DIRECTORY).is_dir();
if !own && !orphaned {
return;
}
let context = checks.facts.transaction_context();
let cwd: PathBuf = match std::env::current_dir() {
Ok(directory) => directory,
Err(_unreadable) => git_dir.clone(),
};
let objects: PathBuf = common_dir.join("objects");
let abbreviate: GitShortName<'_> = GitShortName { context: &context, cwd: cwd.as_path(), objects: objects.as_path() };
let mut texts: Vec<String> = take_push_failures_for(git_dir.as_path(), None, &abbreviate);
texts.extend(take_orphaned_failures(common_dir.as_path(), &abbreviate));
for text in texts {
let _written = writeln!(std::io::stderr(), "{text}");
}
}
struct GitShortName<'a> {
context: &'a GitContext,
cwd: &'a Path,
objects: &'a Path,
}
impl CommitNamer for GitShortName<'_> {
fn name_of(&self, commit: &str) -> String {
return short_name(self.context, self.cwd, self.objects, None, commit);
}
}
pub(crate) fn snapshot_before_worktree_removal<F: RepositoryFacts>(stripped: &StrippedInvocation, checks: &mut ShippedChecks<F>) {
if command_word(stripped) != b"worktree" {
return;
}
let removes: bool = match command_region(stripped).first() {
Some(word) => matches!(word.as_encoded_bytes(), b"remove" | b"prune"),
None => false,
};
if !removes {
return;
}
if let Some(common_dir) = checks.facts.known_common_dir() {
for problem in snapshot_linked_worktrees(common_dir.as_path()) {
let _written = writeln!(std::io::stderr(), "{problem}");
}
}
}
pub(crate) fn prepare_manual_push<F: RepositoryFacts>(
checks: &mut ShippedChecks<F>,
environment: &[(OsString, OsString)],
post_subcommand: &[OsString],
) -> Result<(), crate::manual_push_lifecycle::ManualPushFailure> {
let worktree: Option<PathBuf> = match checks.facts.location() {
Ok(location) => worktree_root(&location.identity).map(Path::to_path_buf),
Err(message) => return Err(crate::manual_push_lifecycle::ManualPushFailure::step(ManualPushCause::SelfExplained, message)),
};
let context = checks.facts.transaction_context();
let cwd = match worktree {
Some(root) => root,
None => match std::env::current_dir() {
Ok(directory) => directory,
Err(error) => {
return Err(crate::manual_push_lifecycle::ManualPushFailure::step(ManualPushCause::SelfExplained, format!(
"the working directory could not be read: {error}"
)));
}
},
};
let git = PushGit {
real_git: context.real_git,
cwd,
environment: environment_with_overlay(environment, context.overlay.as_slice()),
lfs: false,
};
let scope = manual_push_scope(&git, context.global_prefix.as_slice(), post_subcommand)?;
checks.candidates = LifecycleContent::Requested(CandidateRequest::Pushed(scope));
return Ok(());
}