Skip to main content

gitstack/
index.rs

1use std::collections::hash_map::DefaultHasher;
2use std::fs;
3use std::hash::{Hash, Hasher};
4use std::path::PathBuf;
5
6use anyhow::Result;
7use git2::Repository;
8use serde::{Deserialize, Serialize};
9
10use crate::insights::ReviewPack;
11
12const INDEX_VERSION: u8 = 1;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15struct ReviewPackCacheEntry {
16    version: u8,
17    repo_id: String,
18    head: String,
19    selected_hash: Option<String>,
20    pack: ReviewPack,
21}
22
23fn repo_id() -> Option<String> {
24    let repo = Repository::discover(".").ok()?;
25    let root = repo
26        .workdir()
27        .or_else(|| repo.path().parent())
28        .unwrap_or(repo.path());
29    let mut hasher = DefaultHasher::new();
30    root.to_string_lossy().hash(&mut hasher);
31    Some(format!("{:016x}", hasher.finish()))
32}
33
34fn current_head() -> Option<String> {
35    let repo = Repository::discover(".").ok()?;
36    let oid = repo.head().ok()?.target()?;
37    Some(oid.to_string())
38}
39
40fn cache_dir() -> Option<PathBuf> {
41    dirs::cache_dir().map(|p| p.join("gitstack").join("index-v1"))
42}
43
44fn review_pack_cache_path(selected_hash: Option<&str>) -> Option<PathBuf> {
45    let rid = repo_id()?;
46    let suffix = selected_hash.unwrap_or("none");
47    let file_name = format!("review-pack-{}-{}.json", rid, suffix);
48    Some(cache_dir()?.join(file_name))
49}
50
51pub fn load_review_pack(selected_hash: Option<&str>) -> Option<ReviewPack> {
52    let path = review_pack_cache_path(selected_hash)?;
53    let content = fs::read_to_string(path).ok()?;
54    let entry: ReviewPackCacheEntry = serde_json::from_str(&content).ok()?;
55    if entry.version != INDEX_VERSION {
56        return None;
57    }
58    if entry.repo_id != repo_id()? {
59        return None;
60    }
61    if entry.head != current_head()? {
62        return None;
63    }
64    if entry.selected_hash.as_deref() != selected_hash {
65        return None;
66    }
67    Some(entry.pack)
68}
69
70pub fn save_review_pack(selected_hash: Option<&str>, pack: &ReviewPack) -> Result<()> {
71    let Some(path) = review_pack_cache_path(selected_hash) else {
72        return Ok(());
73    };
74
75    if let Some(parent) = path.parent() {
76        fs::create_dir_all(parent)?;
77    }
78
79    let Some(repo_id) = repo_id() else {
80        return Ok(());
81    };
82    let Some(head) = current_head() else {
83        return Ok(());
84    };
85
86    let entry = ReviewPackCacheEntry {
87        version: INDEX_VERSION,
88        repo_id,
89        head,
90        selected_hash: selected_hash.map(str::to_string),
91        pack: pack.clone(),
92    };
93    let json = serde_json::to_string_pretty(&entry)?;
94    fs::write(path, json)?;
95    Ok(())
96}