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