use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use worktrunk::cache;
use worktrunk::git::Repository;
const KIND: &str = "picker-preview";
#[derive(Serialize, Deserialize, Default)]
pub(super) struct LogCacheEntry {
pub raw_log: String,
pub stats: HashMap<String, (usize, usize)>,
}
const MAX_ENTRIES: usize = 500;
fn log_key(sha: &str, w: usize, h: usize) -> String {
format!("log-{sha}-{w}-{h}.json")
}
fn branch_diff_key(base_sha: &str, branch_sha: &str, w: usize) -> String {
format!("branch-diff-{base_sha}-{branch_sha}-{w}.json")
}
fn upstream_diff_key(branch_sha: &str, upstream_sha: &str, w: usize) -> String {
format!("upstream-diff-{branch_sha}-{upstream_sha}-{w}.json")
}
pub(super) fn read_log(repo: &Repository, sha: &str, w: usize, h: usize) -> Option<LogCacheEntry> {
cache::read(repo, KIND, &log_key(sha, w, h))
}
pub(super) fn write_log(repo: &Repository, sha: &str, w: usize, h: usize, value: &LogCacheEntry) {
cache::write_with_lru(repo, KIND, &log_key(sha, w, h), value, MAX_ENTRIES);
}
pub(super) fn read_branch_diff(
repo: &Repository,
base_sha: &str,
branch_sha: &str,
w: usize,
) -> Option<String> {
cache::read(repo, KIND, &branch_diff_key(base_sha, branch_sha, w))
}
pub(super) fn write_branch_diff(
repo: &Repository,
base_sha: &str,
branch_sha: &str,
w: usize,
value: &str,
) {
cache::write_with_lru(
repo,
KIND,
&branch_diff_key(base_sha, branch_sha, w),
&value,
MAX_ENTRIES,
);
}
pub(super) fn read_upstream_diff(
repo: &Repository,
branch_sha: &str,
upstream_sha: &str,
w: usize,
) -> Option<String> {
cache::read(repo, KIND, &upstream_diff_key(branch_sha, upstream_sha, w))
}
pub(super) fn write_upstream_diff(
repo: &Repository,
branch_sha: &str,
upstream_sha: &str,
w: usize,
value: &str,
) {
cache::write_with_lru(
repo,
KIND,
&upstream_diff_key(branch_sha, upstream_sha, w),
&value,
MAX_ENTRIES,
);
}
pub(crate) fn clear_all(repo: &Repository) -> anyhow::Result<usize> {
cache::clear_json_files(&cache::cache_dir(repo, KIND))
}
pub(crate) fn count_all(repo: &Repository) -> usize {
cache::count_json_files(&cache::cache_dir(repo, KIND))
}
#[cfg(test)]
mod tests {
use super::*;
use worktrunk::testing::TestRepo;
fn sample_log_entry() -> LogCacheEntry {
let mut stats = HashMap::new();
stats.insert("abc123".to_string(), (5, 2));
LogCacheEntry {
raw_log: "raw log content".to_string(),
stats,
}
}
#[test]
fn log_roundtrip() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
assert!(read_log(&repo, "deadbeef", 80, 24).is_none());
write_log(&repo, "deadbeef", 80, 24, &sample_log_entry());
let read = read_log(&repo, "deadbeef", 80, 24).expect("entry exists");
assert_eq!(read.raw_log, "raw log content");
assert_eq!(read.stats.get("abc123"), Some(&(5, 2)));
}
#[test]
fn log_width_invalidates() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_log(&repo, "deadbeef", 80, 24, &sample_log_entry());
assert!(read_log(&repo, "deadbeef", 100, 24).is_none());
assert!(read_log(&repo, "deadbeef", 80, 30).is_none());
}
#[test]
fn log_sha_invalidates() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_log(&repo, "deadbeef", 80, 24, &sample_log_entry());
assert!(read_log(&repo, "cafe", 80, 24).is_none());
}
#[test]
fn branch_diff_roundtrip_and_asymmetric() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_branch_diff(&repo, "base", "tip", 80, "rendered diff");
assert_eq!(
read_branch_diff(&repo, "base", "tip", 80),
Some("rendered diff".to_string())
);
assert_eq!(read_branch_diff(&repo, "tip", "base", 80), None);
}
#[test]
fn upstream_diff_roundtrip() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_upstream_diff(&repo, "branch", "upstream", 80, "rendered upstream diff");
assert_eq!(
read_upstream_diff(&repo, "branch", "upstream", 80),
Some("rendered upstream diff".to_string())
);
}
#[test]
fn modes_share_kind_but_distinct_keys() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_log(&repo, "x", 80, 24, &sample_log_entry());
write_branch_diff(&repo, "x", "x", 80, "branch-diff-value");
write_upstream_diff(&repo, "x", "x", 80, "upstream-diff-value");
assert_eq!(
read_log(&repo, "x", 80, 24).unwrap().raw_log,
"raw log content"
);
assert_eq!(
read_branch_diff(&repo, "x", "x", 80).unwrap(),
"branch-diff-value"
);
assert_eq!(
read_upstream_diff(&repo, "x", "x", 80).unwrap(),
"upstream-diff-value"
);
assert_eq!(count_all(&repo), 3);
}
#[test]
fn clear_all_removes_entries() {
let test = TestRepo::with_initial_commit();
let repo = Repository::at(test.root_path()).unwrap();
write_log(&repo, "a", 80, 24, &sample_log_entry());
write_log(&repo, "b", 80, 24, &sample_log_entry());
write_branch_diff(&repo, "base", "tip", 80, "z");
assert_eq!(count_all(&repo), 3);
let removed = clear_all(&repo).unwrap();
assert_eq!(removed, 3);
assert_eq!(count_all(&repo), 0);
assert!(read_log(&repo, "a", 80, 24).is_none());
}
}