use std::fs;
use std::path::{Path, PathBuf};
use super::error::ManifestError;
pub const EVENT_LOG_REL: &str = ".grex/events.jsonl";
pub const LEGACY_EVENT_LOG_REL: &str = "grex.jsonl";
pub fn event_log_path(workspace: &Path) -> PathBuf {
workspace.join(".grex").join("events.jsonl")
}
pub fn find_workspace_root(start: &Path) -> PathBuf {
let mut cur = start;
loop {
if cur.join(EVENT_LOG_REL).is_file()
|| cur.join(LEGACY_EVENT_LOG_REL).is_file()
|| cur.join(".grex").is_dir()
{
return cur.to_path_buf();
}
match cur.parent() {
Some(p) => cur = p,
None => return start.to_path_buf(),
}
}
}
pub fn ensure_event_log_migrated(workspace: &Path) -> Result<PathBuf, ManifestError> {
let new_path = event_log_path(workspace);
let legacy_path = workspace.join(LEGACY_EVENT_LOG_REL);
let new_exists = new_path.exists();
let legacy_exists = legacy_path.exists();
match (new_exists, legacy_exists) {
(true, true) => {
tracing::warn!(
legacy = %legacy_path.display(),
canonical = %new_path.display(),
"v1.x event log AND v2 event log both present; using v2 and leaving v1 in place \
— please review and delete the legacy file"
);
}
(false, true) => {
if let Some(parent) = new_path.parent() {
fs::create_dir_all(parent).map_err(ManifestError::Io)?;
}
fs::rename(&legacy_path, &new_path).map_err(ManifestError::Io)?;
tracing::info!(
from = %legacy_path.display(),
to = %new_path.display(),
"migrated v1.x event log to v2 canonical location"
);
}
_ => {}
}
Ok(new_path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn event_log_path_is_dotgrex_events_jsonl() {
let d = tempdir().unwrap();
let p = event_log_path(d.path());
assert_eq!(p, d.path().join(".grex").join("events.jsonl"));
}
#[test]
fn migrate_no_legacy_no_new_is_noop() {
let d = tempdir().unwrap();
let resolved = ensure_event_log_migrated(d.path()).unwrap();
assert_eq!(resolved, event_log_path(d.path()));
assert!(!resolved.exists());
assert!(!d.path().join(LEGACY_EVENT_LOG_REL).exists());
}
#[test]
fn migrate_legacy_only_renames_to_new() {
let d = tempdir().unwrap();
let legacy = d.path().join(LEGACY_EVENT_LOG_REL);
fs::write(&legacy, b"{\"k\":\"v\"}\n").unwrap();
let resolved = ensure_event_log_migrated(d.path()).unwrap();
assert_eq!(resolved, event_log_path(d.path()));
assert!(resolved.exists(), "new path must exist after migration");
assert!(!legacy.exists(), "legacy path must be gone after migration");
let body = fs::read_to_string(&resolved).unwrap();
assert_eq!(body, "{\"k\":\"v\"}\n");
}
#[test]
fn migrate_new_only_is_noop() {
let d = tempdir().unwrap();
let new_path = event_log_path(d.path());
fs::create_dir_all(new_path.parent().unwrap()).unwrap();
fs::write(&new_path, b"already-migrated\n").unwrap();
let resolved = ensure_event_log_migrated(d.path()).unwrap();
assert_eq!(resolved, new_path);
assert_eq!(fs::read_to_string(&resolved).unwrap(), "already-migrated\n");
assert!(!d.path().join(LEGACY_EVENT_LOG_REL).exists());
}
#[test]
fn migrate_both_present_prefers_new_keeps_legacy() {
let d = tempdir().unwrap();
let new_path = event_log_path(d.path());
fs::create_dir_all(new_path.parent().unwrap()).unwrap();
fs::write(&new_path, b"new\n").unwrap();
let legacy = d.path().join(LEGACY_EVENT_LOG_REL);
fs::write(&legacy, b"legacy\n").unwrap();
let resolved = ensure_event_log_migrated(d.path()).unwrap();
assert_eq!(resolved, new_path);
assert_eq!(fs::read_to_string(&resolved).unwrap(), "new\n");
assert!(legacy.exists(), "legacy must be left in place when both present");
}
#[test]
fn find_workspace_root_walks_up_to_dotgrex_dir() {
let d = tempdir().unwrap();
let ws = d.path();
fs::create_dir_all(ws.join(".grex")).unwrap();
let sub = ws.join("a").join("b");
fs::create_dir_all(&sub).unwrap();
let found = find_workspace_root(&sub);
assert_eq!(fs::canonicalize(&found).unwrap(), fs::canonicalize(ws).unwrap(),);
}
#[test]
fn find_workspace_root_walks_up_to_legacy_log() {
let d = tempdir().unwrap();
let ws = d.path();
fs::write(ws.join(LEGACY_EVENT_LOG_REL), b"").unwrap();
let sub = ws.join("a");
fs::create_dir_all(&sub).unwrap();
let found = find_workspace_root(&sub);
assert_eq!(fs::canonicalize(&found).unwrap(), fs::canonicalize(ws).unwrap(),);
}
#[test]
fn find_workspace_root_falls_back_to_start_when_no_marker() {
let d = tempdir().unwrap();
let found = find_workspace_root(d.path());
assert_eq!(found, d.path());
}
}