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
89
90
91
92
93
94
95
96
97
98
99
use crate::errors::*;
use crate::Guidon;
use crate::TryNew;
use derive_builder::Builder;
use git2::build::RepoBuilder;
use git2::{AutotagOption, Cred, FetchOptions, ProxyOptions};
use std::env;
use std::fs::create_dir_all;
use std::fs::remove_dir_all;

/// GitOptions for git initialization.
/// cf [GitOptionsBuilder](struct.GitOptionsBuilder.html) documentation.
///
/// A repo URL must be given
/// Optional properties:
/// * `rev`: a revision. master by default, can be a tag.
/// * `credentials`: user password to connect the git repository
/// * `unsecure`: set to true to skip certificate check
/// * `auto_proxy`: set to true to let git discovers proxy configuration
#[derive(Debug, Clone, Builder, Default)]
#[builder(default)]
#[builder(field(private))]
#[builder(setter(into, strip_option))]
pub struct GitOptions {
    /// Repo url
    repo: String,
    /// The revision to retrieve (branch, tag...). master by default.
    /// A branch should be given as `origin/branch_name`
    /// A tag as `tag_name`, and a commit by id
    rev: Option<String>,
    /// Credentials if needed (user/password)
    credentials: Option<(String, String)>,
    /// if set to `true`, certificate validation will not be done. `false` by default
    unsecure: bool,
    /// if set to `true` will try to autodetect proxy configuration. `false` by default
    auto_proxy: bool,
}

impl GitOptions {
    /// Get a builder for GitOptions
    pub fn builder() -> GitOptionsBuilder {
        GitOptionsBuilder::default()
    }
}

impl<'a> TryNew<GitOptions> for Guidon<'a> {
    /// Initialization from a git repository
    /// The repo *MUST* contains at its root a `template.toml` file.
    fn try_new(git: GitOptions) -> Result<Self> {
        let mut cb = git2::RemoteCallbacks::new();

        if let Some(creds) = git.credentials {
            cb.credentials(move |_, _, _| {
                let credentials = Cred::userpass_plaintext(&creds.0, &creds.1)?;
                Ok(credentials)
            });
        }

        if git.unsecure {
            // The certificate check must return true if the certificate is accepted.
            cb.certificate_check(|_, _| true);
        }

        let mut fo = FetchOptions::new();
        fo.remote_callbacks(cb)
            .download_tags(AutotagOption::All)
            .update_fetchhead(true);

        if git.auto_proxy {
            let mut po = ProxyOptions::new();
            po.auto();
            fo.proxy_options(po);
        }

        let dest = env::temp_dir().join("guidon");
        let _ = remove_dir_all(&dest);
        create_dir_all(&dest)?;
        let repo = RepoBuilder::new()
            .fetch_options(fo)
            .clone(&git.repo, dest.as_path())?;
        let local =
            repo.revparse_single(&git.rev.unwrap_or_else(|| "refs/heads/master".to_owned()))?;
        let mut opts = git2::build::CheckoutBuilder::new();
        opts.force();
        opts.use_theirs(true);
        repo.checkout_tree(&local, Some(&mut opts))?;

        Guidon::try_new(dest)
    }
}

impl From<git2::Error> for GuidonError {
    fn from(error: git2::Error) -> Self {
        GuidonError {
            kind: ErrorKind::GitError,
            message: error.to_string(),
        }
    }
}