aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/hg.rs
blob: d5e867c0b7272c72d83d18d79fe06fe66da2e401 (plain)
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! ref: composer/src/Composer/Util/Hg.php

use std::sync::OnceLock;
use anyhow::Result;
use shirabe_php_shim::rawurlencode;
use shirabe_external_packages::composer::pcre::preg::Preg;
use crate::config::Config;
use crate::io::io_interface::IOInterface;
use crate::util::process_executor::ProcessExecutor;
use crate::util::url::Url;

static VERSION: OnceLock<Option<String>> = OnceLock::new();

#[derive(Debug)]
pub struct Hg {
    io: Box<dyn IOInterface>,
    config: Config,
    process: ProcessExecutor,
}

impl Hg {
    pub fn new(io: &dyn IOInterface, config: &Config, process: &ProcessExecutor) -> Self {
        todo!()
    }

    pub fn run_command(
        &self,
        command_callable: impl Fn(String) -> Vec<String>,
        url: String,
        cwd: Option<String>,
    ) -> Result<()> {
        self.config.prohibit_url_by_config(&url, &*self.io)?;

        // Try as is
        let command = command_callable(url.clone());
        let mut ignored_output = String::new();
        if self.process.execute(&command, &mut ignored_output, cwd.clone()) == 0 {
            return Ok(());
        }

        // Try with the authentication information available
        let matches = Preg::is_match_with_captures(
            r"(?i)^(?P<proto>ssh|https?)://(?:(?P<user>[^:@]+)(?::(?P<pass>[^:@]+))?@)?(?P<host>[^/]+)(?P<path>/.*)?",
            &url,
        )?;

        if let Some(matches) = matches {
            if self.io.has_authentication(matches.get("host").map(|s| s.as_str()).unwrap_or("")) {
                let authenticated_url = if matches.get("proto").map(|s| s.as_str()) == Some("ssh") {
                    let user = if let Some(u) = matches.get("user") {
                        format!("{}@", rawurlencode(u))
                    } else {
                        String::new()
                    };
                    format!(
                        "{}://{}{}{}",
                        matches.get("proto").unwrap_or(&String::new()),
                        user,
                        matches.get("host").unwrap_or(&String::new()),
                        matches.get("path").unwrap_or(&String::new()),
                    )
                } else {
                    let auth = self.io.get_authentication(matches.get("host").map(|s| s.as_str()).unwrap_or(""));
                    format!(
                        "{}://{}:{}@{}{}",
                        matches.get("proto").unwrap_or(&String::new()),
                        rawurlencode(auth.get("username").map(|s| s.as_str()).unwrap_or("")),
                        rawurlencode(auth.get("password").map(|s| s.as_str()).unwrap_or("")),
                        matches.get("host").unwrap_or(&String::new()),
                        matches.get("path").unwrap_or(&String::new()),
                    )
                };

                let command = command_callable(authenticated_url);
                let mut ignored_output = String::new();
                if self.process.execute(&command, &mut ignored_output, cwd) == 0 {
                    return Ok(());
                }

                let error = self.process.get_error_output();
                return self.throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url);
            }
        }

        let error = format!(
            "The given URL ({}) does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)",
            url
        );
        self.throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url)
    }

    fn throw_exception(&self, message: &str, url: &str) -> Result<()> {
        if Self::get_version(&self.process).is_none() {
            anyhow::bail!("{}", Url::sanitize(&format!(
                "Failed to clone {}, hg was not found, check that it is installed and in your PATH env.\n\n{}",
                url,
                self.process.get_error_output()
            )));
        }

        anyhow::bail!("{}", Url::sanitize(message));
    }

    pub fn get_version(process: &ProcessExecutor) -> Option<&'static str> {
        VERSION.get_or_init(|| {
            let mut output = String::new();
            if process.execute(
                &["hg".to_string(), "--version".to_string()],
                &mut output,
                None,
            ) == 0 {
                if let Ok(Some(matches)) = Preg::is_match_with_indexed_captures(
                    r"/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/",
                    &output,
                ) {
                    return matches.into_iter().nth(1);
                }
            }
            None
        }).as_deref()
    }
}