Skip to main content

radicle/storage/git/
temp.rs

1use std::io;
2use std::path::{Path, PathBuf};
3
4use crate::prelude::RepoId;
5
6use super::{Repository, RepositoryError, UserInfo};
7
8/// A [`Repository`] that is created for temporary operations, such as cloning.
9///
10/// When the `TempRepository` is no longer needed, then call one of destructors:
11///
12///   - [`TempRepository::cleanup`]: remove the repository directory
13///   - [`TempRepository::mv`]: move the repository directory to a final
14///     destination and remove the old directory
15///
16/// [`TempRepository`] implements [`AsRef`] so that the [`Repository`] can be
17/// used in places where a [`Repository`] is needed.
18pub struct TempRepository {
19    repo: Repository,
20    path: PathBuf,
21}
22
23impl TempRepository {
24    /// Extension used for the directory
25    pub(crate) const EXT: &str = "tmp";
26
27    const RANDOMNESS_LENGTH: usize = 6;
28
29    pub(super) fn new<P>(root: P, rid: RepoId, info: &UserInfo) -> Result<Self, RepositoryError>
30    where
31        P: AsRef<Path>,
32    {
33        let random: String = std::iter::repeat_with(fastrand::alphanumeric)
34            .take(Self::RANDOMNESS_LENGTH)
35            .collect();
36        let path = root
37            .as_ref()
38            .join(format!("{}.{random}", rid.canonical()))
39            .with_extension(Self::EXT);
40        let repo = Repository::create(&path, rid, info)?;
41        Ok(Self { repo, path })
42    }
43
44    /// Clean up the temporary directory of the repository.
45    ///
46    /// Note that the repository is dropped first to ensure that there are no
47    /// handles to the repository, before removing the directory.
48    pub fn cleanup(self) {
49        let path = self.path.clone();
50        drop(self.repo);
51        Self::remove(&path)
52    }
53
54    /// Move the temporary directory of the repository to the new path.
55    ///
56    /// If `to` already exists, then the temporary directory is removed, and the
57    /// repository is not moved.
58    ///
59    /// Note that the repository is dropped first to ensure that there are no
60    /// handles to the repository, before removing the directory.
61    pub fn mv<P>(self, to: P) -> io::Result<()>
62    where
63        P: AsRef<Path>,
64    {
65        let to = to.as_ref();
66        let rid = self.repo.id;
67        let path = self.path.clone();
68        drop(self.repo);
69        if to.exists() {
70            log::warn!(target: "radicle", "Refusing to move from temporary directory '{}' because destination {rid} already exists. Removing the temporary directory.", self.path.display());
71            Self::remove(&path);
72        }
73        std::fs::rename(path, to)
74    }
75
76    fn remove(path: &PathBuf) {
77        if let Err(err) = std::fs::remove_dir_all(path) {
78            let path = path.display();
79            log::error!(target: "worker", "Failed to remove temporary directory '{path}': {err}");
80        }
81    }
82}
83
84impl AsRef<Repository> for TempRepository {
85    fn as_ref(&self) -> &Repository {
86        &self.repo
87    }
88}