Skip to main content

greplm_core/
meta.rs

1//! Index manifest persisted at `.greplm/meta.json`.
2
3use serde::{Deserialize, Serialize};
4use std::path::Path;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use crate::error::{Error, Result};
8
9/// On-disk format version. Bump when the segment layout changes.
10///
11/// v2 added the per-segment `seg-N.refs` reference/call-edge table.
12pub const SCHEMA_VERSION: u32 = 2;
13
14/// Index-wide manifest describing the set of live segments.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Meta {
17    pub schema_version: u32,
18    /// IDs of segments that make up the current index.
19    pub segments: Vec<u64>,
20    /// Monotonic counter for allocating new segment IDs.
21    pub next_segment_id: u64,
22    /// Unix timestamp (seconds) of the last successful index operation.
23    pub last_indexed: u64,
24    /// Total live documents across all segments.
25    pub doc_count: u64,
26    /// Total symbols across all segments.
27    pub symbol_count: u64,
28    /// Git commit sha at the time of indexing (empty if not a repo). Lets
29    /// callers detect that the working tree moved (e.g. a branch switch).
30    #[serde(default)]
31    pub indexed_git_head: String,
32    /// Git branch name at the time of indexing (empty if not a repo).
33    #[serde(default)]
34    pub indexed_branch: String,
35}
36
37impl Default for Meta {
38    fn default() -> Self {
39        Self {
40            schema_version: SCHEMA_VERSION,
41            segments: Vec::new(),
42            next_segment_id: 0,
43            last_indexed: 0,
44            doc_count: 0,
45            symbol_count: 0,
46            indexed_git_head: String::new(),
47            indexed_branch: String::new(),
48        }
49    }
50}
51
52impl Meta {
53    pub fn load(path: &Path) -> Result<Meta> {
54        match std::fs::read(path) {
55            Ok(bytes) => {
56                let meta: Meta = serde_json::from_slice(&bytes)?;
57                if meta.schema_version != SCHEMA_VERSION {
58                    return Err(Error::Corrupt(format!(
59                        "index schema version {} != supported {}; run `greplm index` to rebuild",
60                        meta.schema_version, SCHEMA_VERSION
61                    )));
62                }
63                Ok(meta)
64            }
65            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Meta::default()),
66            Err(e) => Err(Error::io(path, e)),
67        }
68    }
69
70    pub fn save(&self, path: &Path) -> Result<()> {
71        let bytes = serde_json::to_vec_pretty(self)?;
72        crate::fsutil::write_atomic(path, &bytes)
73    }
74
75    /// Record the current git HEAD/branch for the indexed tree (best-effort).
76    pub fn record_git_head(&mut self, root: &Path) {
77        if let Some((sha, branch)) = crate::git::head(root) {
78            self.indexed_git_head = sha;
79            self.indexed_branch = branch;
80        }
81    }
82
83    pub fn touch_now(&mut self) {
84        self.last_indexed = SystemTime::now()
85            .duration_since(UNIX_EPOCH)
86            .map(|d| d.as_secs())
87            .unwrap_or(0);
88    }
89
90    pub fn alloc_segment(&mut self) -> u64 {
91        let id = self.next_segment_id;
92        self.next_segment_id += 1;
93        id
94    }
95}