crev_lib/repo/
mod.rs

1use crate::{local::Local, util, verify_package_digest, Error, Result};
2use crev_data::{proof, Digest};
3use serde::{Deserialize, Serialize};
4
5use std::{
6    collections::HashSet,
7    fs,
8    path::{Path, PathBuf},
9};
10
11pub mod staging;
12
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct PackageConfig {
15    pub version: u64,
16    #[serde(rename = "trust-root")]
17    pub trust_root: String,
18}
19
20const CREV_DOT_NAME: &str = ".crev";
21
22#[derive(thiserror::Error, Debug)]
23#[error("Package config not-initialized. Use `crev package init` to generate it.")]
24pub struct PackageDirNotFound;
25
26fn find_package_root_dir() -> std::result::Result<PathBuf, PackageDirNotFound> {
27    let path = Path::new(".")
28        .canonicalize()
29        .map_err(|_| PackageDirNotFound)?;
30    let mut path = path.as_path();
31    loop {
32        if path.join(CREV_DOT_NAME).is_dir() {
33            return Ok(path.to_owned());
34        }
35        path = path.parent().ok_or(PackageDirNotFound)?;
36    }
37}
38
39/// `crev` repository dir inside a package dir
40///
41/// This represents the `.crev` directory and all
42/// the internals of it.
43pub struct Repo {
44    /// root dir, where `.crev` subdiretory resides
45    root_dir: PathBuf,
46    /// lazily loaded `Staging`
47    staging: Option<staging::Staging>,
48}
49
50impl Repo {
51    pub fn init(path: &Path, id_str: String) -> Result<Self> {
52        let repo = Self::new(path)?;
53
54        fs::create_dir_all(repo.dot_crev_path())?;
55
56        let config_path = repo.package_config_path();
57        if config_path.exists() {
58            return Err(Error::PathAlreadyExists(config_path.as_path().into()));
59        }
60        util::store_to_file_with(&config_path, move |w| {
61            serde_yaml::to_writer(
62                w,
63                &PackageConfig {
64                    version: 0,
65                    trust_root: id_str.clone(),
66                },
67            )
68        })??;
69
70        Ok(repo)
71    }
72
73    pub fn open(path: &Path) -> Result<Self> {
74        if !path.exists() {
75            return Err(
76                std::io::Error::new(std::io::ErrorKind::NotFound, "directory not found").into(),
77            );
78        }
79
80        Self::new(path)
81    }
82
83    pub fn auto_open() -> Result<Self> {
84        let root_path = find_package_root_dir()?;
85        Self::open(&root_path)
86    }
87
88    #[allow(clippy::new_ret_no_self)]
89    fn new(root_dir: &Path) -> Result<Self> {
90        let root_dir = root_dir.canonicalize()?;
91        Ok(Self {
92            root_dir,
93            staging: None,
94        })
95    }
96
97    fn package_config_path(&self) -> PathBuf {
98        self.dot_crev_path().join("config.yaml")
99    }
100
101    pub fn load_package_config(&self) -> Result<PackageConfig> {
102        let config = self.try_load_package_config()?;
103        config.ok_or(Error::PackageConfigNotInitialized)
104    }
105
106    pub fn try_load_package_config(&self) -> Result<Option<PackageConfig>> {
107        let path = self.package_config_path();
108
109        if !path.exists() {
110            return Ok(None);
111        }
112        let config_str = fs::read_to_string(&path)?;
113
114        Ok(Some(serde_yaml::from_str(&config_str)?))
115    }
116
117    #[must_use]
118    pub fn dot_crev_path(&self) -> PathBuf {
119        self.root_dir.join(CREV_DOT_NAME)
120    }
121
122    pub fn staging(&mut self) -> Result<&mut staging::Staging> {
123        if self.staging.is_none() {
124            self.staging = Some(staging::Staging::open(&self.root_dir)?);
125        }
126        Ok(self.staging.as_mut().unwrap())
127    }
128
129    #[must_use]
130    pub fn get_proof_rel_store_path(&self, _proof: &proof::Proof) -> PathBuf {
131        unimplemented!();
132    }
133
134    pub fn package_verify(
135        &mut self,
136        local: &Local,
137        allow_dirty: bool,
138        for_id: Option<String>,
139        params: &crate::TrustDistanceParams,
140        requirements: &crate::VerificationRequirements,
141    ) -> Result<crate::VerificationStatus> {
142        if !allow_dirty && self.is_unclean()? {
143            return Err(Error::GitRepositoryIsNotInACleanState);
144        }
145
146        let db = local.load_db()?;
147        let trust_set = local.trust_set_for_id(for_id.as_deref(), params, &db)?;
148        let ignore_list = fnv::FnvHashSet::default();
149        let digest = crate::get_recursive_digest_for_git_dir(&self.root_dir, &ignore_list)?;
150        Ok(verify_package_digest(
151            &digest,
152            &trust_set,
153            requirements,
154            &db,
155        ))
156    }
157
158    pub fn package_digest(&mut self, allow_dirty: bool) -> Result<Digest> {
159        if !allow_dirty && self.is_unclean()? {
160            return Err(Error::GitRepositoryIsNotInACleanState);
161        }
162
163        let ignore_list = HashSet::default();
164        crate::get_recursive_digest_for_git_dir(&self.root_dir, &ignore_list)
165    }
166
167    fn is_unclean(&self) -> Result<bool> {
168        let git_repo = git2::Repository::open(&self.root_dir)?;
169        if git_repo.state() != git2::RepositoryState::Clean {
170            return Err(Error::GitRepositoryIsNotInACleanState);
171        }
172        let mut status_opts = git2::StatusOptions::new();
173        status_opts.include_unmodified(true);
174        status_opts.include_untracked(false);
175        let mut unclean_found = false;
176        for entry in git_repo.statuses(Some(&mut status_opts))?.iter() {
177            if entry.status() != git2::Status::CURRENT {
178                unclean_found = true;
179            }
180        }
181
182        Ok(unclean_found)
183    }
184
185    /// Uses `log::info` to "print"
186    pub fn status(&mut self) -> Result<()> {
187        let staging = self.staging()?;
188        for (k, _v) in &staging.entries {
189            log::info!("{}", k.display());
190        }
191
192        Ok(())
193    }
194
195    pub fn add(&mut self, file_paths: Vec<PathBuf>) -> Result<()> {
196        let staging = self.staging()?;
197        for path in file_paths {
198            staging.insert(&path)?;
199        }
200        staging.save()?;
201
202        Ok(())
203    }
204
205    pub fn remove(&mut self, file_paths: Vec<PathBuf>) -> Result<()> {
206        let staging = self.staging()?;
207        for path in file_paths {
208            staging.remove(&path)?;
209        }
210        staging.save()?;
211
212        Ok(())
213    }
214}