Skip to main content

just_shield/
lockfile.rs

1//! shield.lock — 신뢰를 결정한 시점의 태그→SHA 박제 (ADR-0003).
2//!
3//! 상태는 도구가 아니라 저장소의 이 파일에 산다. 정렬된 텍스트라 diff 친화적이고,
4//! 변경(신뢰 변경)이 PR 리뷰를 통과해야 하는 구조가 된다.
5
6use std::collections::BTreeMap;
7use std::io;
8use std::path::Path;
9
10pub const FILE_NAME: &str = "shield.lock";
11
12const HEADER: &str = "\
13# shield.lock — just-shield가 박제한 태그→SHA 대응. 직접 수정하지 말 것.
14# 갱신: just-shield lock
15";
16
17/// 박제본. 키는 `owner/repo@ref`, 값은 박제 시점의 커밋 SHA.
18#[derive(Default)]
19pub struct Lockfile {
20    pub entries: BTreeMap<String, String>,
21}
22
23impl Lockfile {
24    pub fn key(repo: &str, git_ref: &str) -> String {
25        format!("{repo}@{git_ref}")
26    }
27
28    pub fn get(&self, repo: &str, git_ref: &str) -> Option<&str> {
29        self.entries
30            .get(&Self::key(repo, git_ref))
31            .map(|s| s.as_str())
32    }
33}
34
35/// `<root>/shield.lock`을 읽는다. 없으면 `Ok(None)` — 오류가 아니다.
36pub fn load(root: &Path) -> io::Result<Option<Lockfile>> {
37    let path = root.join(FILE_NAME);
38    if !path.is_file() {
39        return Ok(None);
40    }
41    let content = std::fs::read_to_string(path)?;
42    let mut entries = BTreeMap::new();
43    for line in content.lines() {
44        let line = line.trim();
45        if line.is_empty() || line.starts_with('#') {
46            continue;
47        }
48        if let Some((key, sha)) = line.split_once(' ') {
49            entries.insert(key.to_string(), sha.trim().to_string());
50        }
51    }
52    Ok(Some(Lockfile { entries }))
53}
54
55/// 박제본을 기록한다. BTreeMap이라 같은 입력이면 항상 같은 바이트가 나온다.
56pub fn save(root: &Path, lockfile: &Lockfile) -> io::Result<()> {
57    let mut out = String::from(HEADER);
58    for (key, sha) in &lockfile.entries {
59        out.push_str(&format!("{key} {sha}\n"));
60    }
61    std::fs::write(root.join(FILE_NAME), out)
62}