workerlib/
git_config.rs

1//!
2//! Configure the global settings of all git-binary based operations.
3//!
4
5use git2::{Config as GitConfig, Error as GitError};
6use std::fs::{metadata, write};
7use std::io::Error as IoError;
8use std::path::{Path, PathBuf};
9
10/// The default branch to use when initializing and creating commits, this
11/// currently only effects LFS backed raw-file repositories.
12pub const DEFAULT_GIT_BRANCH: &str = "main";
13
14/// Default path used to setup Lorry's git configuration file.
15/// TODO: Should support XDG style configuration and maybe others.
16pub const DEFAULT_GIT_CONFIG_PATH: &str = "lorry.gitconfig";
17
18/// Environment variable that specifies where the global git configuration is
19/// located. This needs to be configured each time we shell out to Git.
20pub const GIT_CONFIG_GLOBAL: &str = "GIT_CONFIG_GLOBAL";
21
22/// Default username used for basic authentication during LFS operations
23pub const GITLAB_OAUTH_USER: &str = "oauth2";
24
25/// An error that occurred while access the global git configuration.
26#[derive(thiserror::Error, Debug)]
27pub enum Error {
28    /// Could not serialize git configuration
29    #[error("Git Configuration Invalid: {0}")]
30    Git(#[from] GitError),
31
32    /// IO error
33    #[error("IO Failure: {0}")]
34    Io(#[from] IoError),
35}
36
37/// Ensure that the global git configuration is initialized and has the correct
38/// contents. Each time Lorry starts up this file will be setup with required
39/// values but additional options can be added as desired.
40pub struct Config(pub PathBuf);
41
42impl Config {
43    /// Setup the Lorry specific git configuration, admin_contact should be a
44    /// valid e-mail address and will be used in automated commits made by
45    /// Lorry.
46    #[tracing::instrument(skip_all, fields(git_cfg_path = self.0.to_str()))]
47    pub fn setup(
48        &self,
49        git_identity: (&str, &str),
50        n_threads: i64,
51        no_ssl_verify: bool,
52        http_version: Option<&str>,
53        ask_pass_program: &Path,
54        git_credentials_file: Option<&Path>,
55    ) -> Result<(), Error> {
56        if metadata(&self.0).is_err() {
57            write(&self.0, [])?;
58        }
59        let mut cfg = GitConfig::open(&self.0)?;
60        cfg.set_str("user.name", git_identity.0)?;
61        cfg.set_str("user.email", git_identity.1)?;
62
63        // Do not fork a background process for doing garbage collection
64        cfg.set_bool("gc.autodetach", false)?;
65
66        // Number of threads used when pushing to a remote
67        cfg.set_i64("pack.threads", n_threads)?;
68
69        // If ssl cerficates from http sources should be verified
70        cfg.set_bool("http.sslVerify", !no_ssl_verify)?;
71
72        // Specify the HTTP version
73        if let Some(version) = http_version {
74            cfg.set_str("http.version", version)?;
75        }
76
77        // Extra header exposed by Lorry
78        cfg.set_str("http.extraHeader", crate::LORRY_VERSION_HEADER)?;
79
80        // Default branch when initializing repositories
81        cfg.set_str("init.defaultBranch", DEFAULT_GIT_BRANCH)?;
82
83        // Global credential specifier used only for LFS operations with
84        // downstream gitlab, defaults to reading LORRY_GITLAB_PRIVATE_TOKEN
85        cfg.set_str("credential.username", GITLAB_OAUTH_USER)?;
86
87        // Program responsible for reading the downstream oauth password
88        cfg.set_str("core.askPass", &ask_pass_program.to_string_lossy())?;
89
90        // Workerlib access to git credentials
91        if let Some(gitcredentials_path) = git_credentials_file {
92            cfg.set_str(
93                "credential.helper",
94                &format!("store --file {}", gitcredentials_path.display()),
95            )?;
96        }
97
98        tracing::debug!("Initialized git config");
99        Ok(())
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::git_config::Config as GitConfig;
107    use tempfile::tempdir;
108
109    #[test]
110    pub fn test_rawfile_init() {
111        let test_dir = tempdir().unwrap();
112        let git_config_path = test_dir.path().join("gitconfig");
113        let git_config = GitConfig(git_config_path);
114        git_config
115            .setup(
116                ("Lorry", "hello@example.org"),
117                1,
118                false,
119                Some("HTTP/1.1"),
120                Path::new("/dev/null"),
121                None,
122            )
123            .unwrap();
124    }
125}