use crate::diagnostic_log::debug;
use crate::discard_audit::append_line;
use crate::iso_time::now_iso;
use crate::json_record::ObjectWriter;
use crate::lfs_output_mask::mask_userinfo_text;
use crate::service_instance::instance_of;
use crate::service_protocol::RELEASE;
use std::path::Path;
pub const TRIGGER_FILE: &str = "cli-git-push-trigger.jsonl";
pub const TRIGGER_SCHEMA_VERSION: i64 = 1;
const TAG: &str = "pushTrigger";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Outcome {
HandedOver,
Declined,
}
impl Outcome {
pub fn word(self) -> &'static str {
return match self {
Self::HandedOver => "handed-over",
Self::Declined => "declined",
};
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Origin<'place> {
pub worktree: &'place Path,
pub git_dir: &'place Path,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Decision<'record> {
pub common_dir: Option<&'record Path>,
pub origin: Option<Origin<'record>>,
pub command: &'record str,
pub outcome: Outcome,
pub reason: &'record str,
pub detail: &'record str,
pub branch: Option<&'record str>,
}
const WARN_REASONS: [&str; 9] = [
"layout-declined",
"head-unreadable",
"branch-has-no-tip",
"object-database-unopened",
"upstream-config-unreadable",
"walk-undecided-growth",
"walk-undecided-ahead",
"no-route",
"handover-failed",
];
pub fn level_of(outcome: Outcome, reason: &str) -> &'static str {
if outcome == Outcome::Declined && WARN_REASONS.contains(&reason) {
return "warn";
}
return "info";
}
pub fn trigger_line(decision: &Decision<'_>) -> String {
let mut record: ObjectWriter = ObjectWriter::new();
record
.integer("schemaVersion", TRIGGER_SCHEMA_VERSION)
.string("time", now_iso().as_str())
.string("level", level_of(decision.outcome, decision.reason))
.integer("pid", i64::from(std::process::id()))
.string("release", RELEASE);
if let Some(origin) = decision.origin {
record
.string("worktree", &origin.worktree.to_string_lossy())
.string("gitDir", &origin.git_dir.to_string_lossy())
.string("instance", instance_of(origin.git_dir).as_str());
}
record
.string("command", decision.command)
.string("outcome", decision.outcome.word())
.string("reason", decision.reason)
.string("detail", mask_userinfo_text(decision.detail).as_str());
if let Some(name) = decision.branch {
record.string("branch", name);
}
return format!("{}\n", record.finish());
}
pub fn record_decision(decision: &Decision<'_>) {
debug(TAG, &format!("{}: {} ({}) {}", decision.command, decision.outcome.word(), decision.reason, mask_userinfo_text(decision.detail)));
let Some(directory) = decision.common_dir else { return };
let target = directory.join(TRIGGER_FILE);
let line: String = trigger_line(decision);
if let Err(error) = append_line(target.as_path(), line.as_bytes()) {
debug(TAG, &format!("the trigger record could not be written to {}: {error}", target.display()));
}
}
#[cfg(test)]
#[path = "push_trigger_log_tests.rs"]
mod tests;