crev_lib/util/
git.rs

1use crate::Result;
2use git2::{ErrorClass, ErrorCode};
3use log::debug;
4use std::path::Path;
5
6#[derive(PartialEq, Debug, Default)]
7pub struct GitUrlComponents {
8    pub domain: String,
9    pub username: String,
10    pub repo: String,
11    pub suffix: String,
12}
13
14#[must_use]
15pub fn parse_git_url_https(http_url: &str) -> Option<GitUrlComponents> {
16    let mut split: Vec<_> = http_url.split('/').collect();
17
18    while let Some(&"") = split.last() {
19        split.pop();
20    }
21    if split.len() != 5 {
22        return None;
23    }
24    if split[0] != "https:" && split[0] != "http:" {
25        return None;
26    }
27    let domain = split[2];
28    let username = split[3];
29    let repo = split[4];
30    let suffix = match domain {
31        "git.sr.ht" => "",
32        "github.com" | "gitlab.com" => {
33            if repo.ends_with(".git") {
34                ""
35            } else {
36                ".git"
37            }
38        }
39        _ => return None,
40    };
41
42    Some(GitUrlComponents {
43        domain: domain.to_string(),
44        username: username.to_string(),
45        repo: repo.to_string(),
46        suffix: suffix.to_string(),
47    })
48}
49
50#[must_use]
51pub fn is_unrecoverable(err: &git2::Error) -> bool {
52    matches!(
53        (err.class(), err.code()),
54        // GitHub's way of saying 404
55        (ErrorClass::Http, ErrorCode::Auth) |
56        (ErrorClass::Repository, ErrorCode::NotFound) |
57        // corrupted loose reference
58        (ErrorClass::Reference, ErrorCode::GenericError)
59    )
60}
61
62pub fn fetch_and_checkout_git_repo(repo: &git2::Repository) -> Result<(), git2::Error> {
63    let mut fetch_options = default_fetch_options();
64    repo.find_remote("origin")?
65        .fetch::<String>(&[], Some(&mut fetch_options), None)?;
66    repo.set_head("FETCH_HEAD")?;
67    let mut opts = git2::build::CheckoutBuilder::new();
68    opts.force();
69    repo.checkout_head(Some(&mut opts))
70}
71
72/// Make a git clone with the default fetch options
73pub fn clone<P: AsRef<Path>>(
74    url: &str,
75    path: P,
76) -> std::result::Result<git2::Repository, git2::Error> {
77    debug!("Cloning {} to {}", url, path.as_ref().display());
78    let fetch_options = default_fetch_options();
79    git2::build::RepoBuilder::new()
80        .fetch_options(fetch_options)
81        .clone(url, path.as_ref())
82}
83
84/// Get the default fetch options to use when fetching or cloneing
85///
86/// Currently this just ensures that git's automatic proxy settings are used.
87#[must_use]
88pub fn default_fetch_options<'a>() -> git2::FetchOptions<'a> {
89    // Use automatic proxy configuration for the fetch
90    let mut proxy_options = git2::ProxyOptions::new();
91    proxy_options.auto();
92    let mut fetch_options = git2::FetchOptions::new();
93    fetch_options.proxy_options(proxy_options);
94
95    fetch_options
96}
97
98#[test]
99fn parse_git_url_https_test() {
100    assert_eq!(
101        parse_git_url_https("https://github.com/dpc/trust"),
102        Some(GitUrlComponents {
103            domain: "github.com".to_string(),
104            username: "dpc".to_string(),
105            repo: "trust".to_string(),
106            suffix: ".git".to_string()
107        })
108    );
109    assert_eq!(
110        parse_git_url_https("https://gitlab.com/hackeraudit/web.git"),
111        Some(GitUrlComponents {
112            domain: "gitlab.com".to_string(),
113            username: "hackeraudit".to_string(),
114            repo: "web.git".to_string(),
115            suffix: String::new()
116        })
117    );
118    assert_eq!(
119        parse_git_url_https("https://gitlab.com/hackeraudit/web.git/"),
120        Some(GitUrlComponents {
121            domain: "gitlab.com".to_string(),
122            username: "hackeraudit".to_string(),
123            repo: "web.git".to_string(),
124            suffix: String::new()
125        })
126    );
127    assert_eq!(
128        parse_git_url_https("https://gitlab.com/hackeraudit/web.git/////////"),
129        Some(GitUrlComponents {
130            domain: "gitlab.com".to_string(),
131            username: "hackeraudit".to_string(),
132            repo: "web.git".to_string(),
133            suffix: String::new()
134        })
135    );
136}
137
138#[must_use]
139pub fn https_to_git_url(http_url: &str) -> Option<String> {
140    parse_git_url_https(http_url).map(|components| {
141        format!(
142            "git@{}:{}/{}{}",
143            components.domain, components.username, components.repo, components.suffix
144        )
145    })
146}
147
148#[test]
149fn https_to_git_url_test() {
150    assert_eq!(
151        https_to_git_url("https://github.com/dpc/trust"),
152        Some("git@github.com:dpc/trust.git".into())
153    );
154    assert_eq!(
155        https_to_git_url("https://gitlab.com/hackeraudit/web.git"),
156        Some("git@gitlab.com:hackeraudit/web.git".into())
157    );
158    assert_eq!(
159        https_to_git_url("https://gitlab.com/hackeraudit/web.git/"),
160        Some("git@gitlab.com:hackeraudit/web.git".into())
161    );
162    assert_eq!(
163        https_to_git_url("https://gitlab.com/hackeraudit/web.git/////////"),
164        Some("git@gitlab.com:hackeraudit/web.git".into())
165    );
166    assert_eq!(
167        https_to_git_url("https://git.sr.ht/~ireas/crev-proofs"),
168        Some("git@git.sr.ht:~ireas/crev-proofs".into())
169    );
170    assert_eq!(https_to_git_url("https://example.com/foo/bar"), None);
171}