1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::fs::{metadata, write};
use std::io::Error as IoError;
use std::path::{Path, PathBuf};

use git2::{Config as GitConfig, Error as GitError};

/// The default branch to use when initializing and creating commits, this
/// currently only effects LFS backed raw-file repositories.
pub const DEFAULT_GIT_BRANCH: &str = "main";

/// Default path used to setup Lorry's git configuration file.
/// TODO: Should support XDG style configuration and maybe others.
pub const DEFAULT_GIT_CONFIG_PATH: &str = "lorry.gitconfig";

/// Environment variable that specifies where the global git configuration is
/// located. This needs to be configured each time we shell out to Git.
pub const GIT_CONFIG_GLOBAL: &str = "GIT_CONFIG_GLOBAL";

/// Default username used for basic authentication during LFS operations
pub const GITLAB_OAUTH_USER: &str = "oauth2";

/// An error that occurred while access the global git configuration.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("Git Configuration Invalid: {0}")]
    Git(#[from] GitError),
    #[error("IO Failure: {0}")]
    Io(#[from] IoError),
}

/// Ensure that the global git configuration is initialized and has the correct
/// contents. Each time Lorry starts up this file will be setup with required
/// values but additional options can be added as desired.
pub struct Config(pub PathBuf);

impl Config {
    /// Setup the Lorry specific git configuration, admin_contact should be a
    /// valid e-mail address and will be used in automated commits made by
    /// Lorry.
    pub fn setup(
        &self,
        admin_email: &str,
        n_threads: i64,
        no_ssl_verify: bool,
        ask_pass_program: &Path,
    ) -> Result<(), Error> {
        if metadata(&self.0).is_err() {
            write(&self.0, [])?;
        }
        let mut cfg = GitConfig::open(&self.0)?;
        cfg.set_str("user.name", "Lorry")?;
        cfg.set_str("user.email", admin_email)?;
        // do not fork a background process for doing garbage collection
        cfg.set_bool("gc.autodetach", false)?;
        // number of threads used when pushing to a remote
        cfg.set_i64("pack.threads", n_threads)?;
        // if ssl cerficates from http sources should be verified
        cfg.set_bool("http.sslVerify", !no_ssl_verify)?;
        // Extra header exposed by Lorry
        cfg.set_str("http.extraHeader", crate::LORRY_VERSION_HEADER)?;
        // default branch when initializing repositories
        cfg.set_str("init.defaultBranch", DEFAULT_GIT_BRANCH)?;
        // global credential specifier used only for LFS operations with
        // downstream gitlab, defaults to reading LORRY_GITLAB_PRIVATE_TOKEN
        cfg.set_str("credential.username", GITLAB_OAUTH_USER)?;
        // program responsible for reading the downstream oauth password
        cfg.set_str("core.askPass", &ask_pass_program.to_string_lossy())?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::git_config::Config as GitConfig;
    use tempfile::tempdir;

    #[test]
    pub fn test_rawfile_init() {
        let test_dir = tempdir().unwrap();
        let git_config_path = test_dir.path().join("gitconfig");
        let git_config = GitConfig(git_config_path);
        git_config
            .setup("hello@example.org", 1, false, Path::new("/dev/null"))
            .unwrap();
    }
}