crev_lib/repo/
staging.rs

1use crate::{Error, Result};
2use crev_data::proof;
3use serde::{Deserialize, Serialize};
4use std::{
5    collections::HashMap,
6    fs,
7    io::Write,
8    path::{Path, PathBuf},
9};
10
11#[derive(Serialize, Deserialize, Debug)]
12pub struct StagingPathInfo {
13    blake_hash: [u8; 32],
14}
15
16pub struct Staging {
17    root_path: PathBuf,
18    file_path: PathBuf,
19    pub entries: HashMap<PathBuf, StagingPathInfo>,
20}
21
22const STAGING_FILE_NAME: &str = "staging";
23
24impl Staging {
25    pub fn wipe(&mut self) -> Result<()> {
26        fs::remove_file(&self.file_path)?;
27        Ok(())
28    }
29
30    #[must_use]
31    pub fn is_empty(&self) -> bool {
32        self.entries.is_empty()
33    }
34
35    pub fn save(&mut self) -> Result<()> {
36        self.write_to_file(&self.file_path)
37    }
38
39    pub fn open(repo_path: &Path) -> Result<Self> {
40        let path = repo_path.join(super::CREV_DOT_NAME).join(STAGING_FILE_NAME);
41        if !path.exists() {
42            return Ok(Self {
43                root_path: repo_path.to_owned(),
44                file_path: path,
45                entries: Default::default(),
46            });
47        }
48
49        let file = fs::File::open(&path)?;
50
51        let path_info: HashMap<PathBuf, StagingPathInfo> = serde_cbor::from_reader(&file)?;
52
53        Ok(Self {
54            root_path: repo_path.to_owned(),
55            file_path: path,
56            entries: path_info,
57        })
58    }
59
60    fn write_to_file(&self, path: &Path) -> Result<()> {
61        let tmp_path = path.with_extension("tmp");
62        let mut file = fs::File::create(&tmp_path)?;
63        serde_cbor::to_writer(&mut file, &self.entries)?;
64        file.flush()?;
65        drop(file);
66        fs::rename(tmp_path, path)?;
67        Ok(())
68    }
69
70    pub fn insert(&mut self, path: &Path) -> Result<()> {
71        let full_path = path.canonicalize()?;
72
73        let path = full_path
74            .strip_prefix(&self.root_path)
75            .map_err(|_| Error::PathNotInStageRootPath)?
76            .to_owned();
77        log::info!("Adding {}", path.display());
78        self.entries.insert(
79            path,
80            StagingPathInfo {
81                blake_hash: crev_common::blake2b256sum_file(&full_path)?,
82            },
83        );
84
85        Ok(())
86    }
87
88    pub fn remove(&mut self, path: &Path) -> Result<()> {
89        let full_path = path.canonicalize()?;
90
91        let path = full_path
92            .strip_prefix(&self.root_path)
93            .map_err(|_| Error::PathNotInStageRootPath)?
94            .to_owned();
95        log::info!("Removing {}", path.display());
96
97        self.entries.remove(&path);
98
99        Ok(())
100    }
101
102    #[must_use]
103    pub fn to_review_files(&self) -> Vec<proof::review::code::File> {
104        self.entries
105            .iter()
106            .map(|(k, v)| proof::review::code::File {
107                path: k.clone(),
108                digest: v.blake_hash.to_vec(),
109                digest_type: "blake2b".into(),
110            })
111            .collect()
112    }
113
114    pub fn enforce_current(&self) -> Result<()> {
115        for (rel_path, info) in &self.entries {
116            let path = self.root_path.join(rel_path);
117            if crev_common::blake2b256sum_file(&path)? != info.blake_hash {
118                return Err(Error::FileNotCurrent(rel_path.as_path().into()));
119            }
120        }
121
122        Ok(())
123    }
124}