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}