workerlib/
local.rs

1//!
2//! Initialize a local repository
3//!
4
5use git2::{Repository, RepositoryInitOptions};
6use std::path::{Path, PathBuf};
7
8use crate::git_config::DEFAULT_GIT_BRANCH;
9
10#[allow(dead_code)]
11#[derive(thiserror::Error, Debug)]
12pub(crate) enum PrepareLocalRepoError {
13    #[error("libgit2 Error {0}")]
14    Libgit2(#[from] git2::Error),
15}
16
17/// Helper struct for initializing a local repository
18///
19/// A repository on a local downstream is structured as:
20/// <base-dir>/group/subgroups/project/git-repository/<git-repo>
21///
22/// Raw files are not supported for local downstream/
23#[derive(Clone, Debug)]
24pub struct LocalRepositoryBuilder {
25    /// Path to the root directory of all mirrors stored locally
26    pub base_dir: PathBuf,
27    /// Default branch of the repository
28    pub head: String,
29    /// Description of the repository
30    pub description: Option<String>,
31}
32
33// Initialize a local git repository for local downstream
34impl LocalRepositoryBuilder {
35    /// Local repository defaults
36    pub fn new(base_dir: &Path) -> Self {
37        LocalRepositoryBuilder {
38            base_dir: base_dir.to_path_buf(),
39            head: DEFAULT_GIT_BRANCH.to_string(),
40            description: None,
41        }
42    }
43
44    /// Conditionally skips initialization if the repository already exists
45    /// on disk
46    pub fn git_directory_is_initialized(&self, dir: &Path) -> bool {
47        let test_paths = [
48            dir.join("git-repository/HEAD"),
49            dir.join("git-repository/config"),
50            dir.join("git-repository/objects"),
51        ];
52        test_paths
53            .iter()
54            .all(|file_path| std::fs::metadata(file_path.as_path()).is_ok())
55    }
56
57    /// Set the default branch of a local repository
58    pub fn head(mut self, head: Option<String>) -> Self {
59        self.head = if let Some(head) = head {
60            head
61        } else {
62            DEFAULT_GIT_BRANCH.to_string()
63        };
64        self
65    }
66
67    /// Set the description of a local repository
68    pub fn description(mut self, description: Option<String>) -> Self {
69        self.description = description;
70        self
71    }
72
73    /// Build the local repository and set the working directory
74    /// to the base directory specified in the configuration
75    ///
76    /// Tag the repository as managed by Lorry.
77    #[tracing::instrument(name = "local::build", skip_all)]
78    pub fn build(self, repo_path: &str) -> Result<(), git2::Error> {
79        let dir = PathBuf::from(format!(
80            "{}/{}/git-repository",
81            self.base_dir.display(),
82            repo_path
83        ));
84        if !self.git_directory_is_initialized(&dir) {
85            tracing::debug!("Creating repository at {:?}...", &dir);
86
87            let mut opts = RepositoryInitOptions::new();
88            opts.bare(true);
89            opts.workdir_path(&dir);
90            opts.initial_head(&self.head);
91            if let Some(description) = self.description {
92                opts.description(&description);
93            }
94
95            let repo = Repository::init_opts(dir, &opts)?;
96            repo.config()?.set_bool("lorry.managed", true)?;
97        }
98        Ok(())
99    }
100}
101
102#[cfg(test)]
103mod test {
104
105    use tempfile::tempdir;
106
107    use crate::local::LocalRepositoryBuilder;
108
109    #[tokio::test]
110    async fn test_initialize_local_repository() {
111        let temp_dir = tempdir().unwrap();
112        let path = temp_dir.path().join("a/asdf/mirrors");
113        let repo_path = "foo/bar";
114        LocalRepositoryBuilder::new(&path).build(repo_path).unwrap();
115
116        assert!(path.join("foo/bar/git-repository/HEAD").exists());
117        assert!(path.join("foo/bar/git-repository/description").exists());
118        assert!(path.join("foo/bar/git-repository/refs/heads").exists())
119    }
120}