mod support;
use std::fs;
use tempfile::TempDir;
use thoughts_tool::git::sync::GitSync;
#[tokio::test]
async fn sync_jsonl_smart_merge_on_conflict() {
if std::env::var("THOUGHTS_INTEGRATION_TESTS").ok().as_deref() != Some("1") {
eprintln!("skipping; set THOUGHTS_INTEGRATION_TESTS=1");
return;
}
let remote = TempDir::new().unwrap();
support::git_ok(remote.path(), &["init", "--bare"]);
let local = TempDir::new().unwrap();
support::git_ok(local.path(), &["init"]);
let logs_dir = local.path().join("branch/logs");
fs::create_dir_all(&logs_dir).unwrap();
let jsonl_path = logs_dir.join("tool_logs_base.jsonl");
fs::write(
&jsonl_path,
r#"{"call_id":"base","started_at":"2025-01-01T10:00:00Z","tool":"init"}"#,
)
.unwrap();
fs::write(local.path().join("readme.txt"), "readme").unwrap();
support::git_ok(local.path(), &["add", "."]);
support::git_ok(
local.path(),
&[
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"commit",
"-m",
"initial",
],
);
support::git_ok(local.path(), &["branch", "-M", "main"]);
support::git_ok(
local.path(),
&["remote", "add", "origin", remote.path().to_str().unwrap()],
);
support::git_ok(local.path(), &["push", "-u", "origin", "main"]);
{
let bare_repo = git2::Repository::open(remote.path()).expect("open bare remote");
bare_repo
.set_head("refs/heads/main")
.expect("set bare HEAD to main");
}
let other = TempDir::new().unwrap();
support::git_ok(
other.path(),
&["clone", remote.path().to_str().unwrap(), "."],
);
let other_jsonl = other.path().join("branch/logs/tool_logs_base.jsonl");
let new_content = r#"{"call_id":"base","started_at":"2025-01-01T10:00:00Z","tool":"remote_modified"}
{"call_id":"remote_only","started_at":"2025-01-01T11:00:00Z","tool":"remote"}"#;
fs::write(&other_jsonl, new_content).unwrap();
support::git_ok(other.path(), &["add", "."]);
support::git_ok(
other.path(),
&[
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"commit",
"-m",
"remote change",
],
);
support::git_ok(other.path(), &["push"]);
let new_local_content = r#"{"call_id":"base","started_at":"2025-01-01T10:00:00Z","tool":"local_override"}
{"call_id":"local_only","started_at":"2025-01-01T12:00:00Z","tool":"local"}"#;
fs::write(&jsonl_path, new_local_content).unwrap();
let sync = GitSync::new(local.path(), None).unwrap();
sync.sync("test").await.unwrap();
let merged = fs::read_to_string(&jsonl_path).unwrap();
assert!(
merged.contains("remote_only"),
"should contain remote_only entry"
);
assert!(
merged.contains("local_only"),
"should contain local_only entry"
);
assert!(
merged.contains("local_override"),
"should contain local_override (local wins)"
);
assert!(
!merged.contains(r#""tool":"remote_modified""#),
"should NOT contain remote_modified (local wins on collision)"
);
let lines: Vec<&str> = merged.lines().collect();
assert!(
lines.len() >= 3,
"should have at least 3 lines, got {}",
lines.len()
);
assert!(lines[0].contains("base"), "first line should be base entry");
assert!(
lines[1].contains("remote_only"),
"second line should be remote_only"
);
assert!(
lines[2].contains("local_only"),
"third line should be local_only"
);
}