use std::path::Path;
use super::event::EventContext;
#[derive(Debug, Clone, Default)]
pub struct GitContext {
pub branch: Option<String>,
pub worktree: Option<String>,
pub commit: Option<String>,
}
pub fn resolve_context(ito_path: &Path) -> EventContext {
let session_id = resolve_session_id(ito_path);
let harness_session_id = resolve_harness_session_id();
let git = resolve_git_context();
EventContext {
session_id,
harness_session_id,
branch: git.branch,
worktree: git.worktree,
commit: git.commit,
}
}
pub fn resolve_session_id(ito_path: &Path) -> String {
let session_dir = ito_path.join(".state").join("audit");
let session_file = session_dir.join(".session");
if let Ok(contents) = std::fs::read_to_string(&session_file) {
let contents = contents.trim().to_string();
if !contents.is_empty() {
return contents;
}
}
let id = uuid::Uuid::new_v4().to_string();
let _ = std::fs::create_dir_all(&session_dir);
let _ = std::fs::write(&session_file, &id);
id
}
pub fn resolve_harness_session_id() -> Option<String> {
ito_common::harness::resolve_harness_session_id()
}
pub fn resolve_git_context() -> GitContext {
let branch = run_git_command(&["symbolic-ref", "--short", "HEAD"]);
let commit = run_git_command(&["rev-parse", "--short=8", "HEAD"]);
let worktree = detect_worktree_name();
GitContext {
branch,
worktree,
commit,
}
}
pub fn resolve_user_identity() -> String {
let name = run_git_command(&["config", "user.name"])
.or_else(|| std::env::var("USER").ok())
.unwrap_or_else(|| "unknown".to_string());
format!("@{}", name.to_lowercase().replace(' ', "-"))
}
fn run_git_command(args: &[&str]) -> Option<String> {
let output = std::process::Command::new("git").args(args).output().ok()?;
if !output.status.success() {
return None;
}
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
if s.is_empty() { None } else { Some(s) }
}
fn detect_worktree_name() -> Option<String> {
let git_dir = run_git_command(&["rev-parse", "--git-dir"])?;
let common_dir = run_git_command(&["rev-parse", "--git-common-dir"])?;
let git_dir_path = std::path::Path::new(&git_dir);
let common_dir_path = std::path::Path::new(&common_dir);
if git_dir_path.canonicalize().ok()? != common_dir_path.canonicalize().ok()? {
let toplevel = run_git_command(&["rev-parse", "--show-toplevel"])?;
let path = std::path::Path::new(&toplevel);
Some(path.file_name()?.to_string_lossy().to_string())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_user_identity_returns_at_prefixed_string() {
let identity = resolve_user_identity();
assert!(identity.starts_with('@'));
assert!(!identity.contains(' '));
}
#[test]
fn resolve_harness_session_id_returns_none_without_env() {
let _result = resolve_harness_session_id();
}
#[test]
fn resolve_git_context_does_not_panic() {
let ctx = resolve_git_context();
let _ = ctx;
}
#[test]
fn resolve_session_id_generates_uuid() {
let tmp = tempfile::tempdir().expect("tempdir");
let ito_path = tmp.path().join(".ito");
std::fs::create_dir_all(&ito_path).expect("create ito dir");
let id = resolve_session_id(&ito_path);
assert!(!id.is_empty());
assert_eq!(id.len(), 36);
assert!(id.contains('-'));
}
#[test]
fn resolve_session_id_is_stable_across_calls() {
let tmp = tempfile::tempdir().expect("tempdir");
let ito_path = tmp.path().join(".ito");
std::fs::create_dir_all(&ito_path).expect("create ito dir");
let id1 = resolve_session_id(&ito_path);
let id2 = resolve_session_id(&ito_path);
assert_eq!(id1, id2);
}
#[test]
fn resolve_context_populates_session_id() {
let tmp = tempfile::tempdir().expect("tempdir");
let ito_path = tmp.path().join(".ito");
std::fs::create_dir_all(&ito_path).expect("create ito dir");
let ctx = resolve_context(&ito_path);
assert!(!ctx.session_id.is_empty());
}
}