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::error::Result;
use git2::{Commit, Repository};
use git_url_parse::GitUrl;

pub fn get_url(repo: &Repository, remote: &str, commit: Commit) -> Result<String> {
    let remote = repo.find_remote(remote)?;
    let remote_url = remote.url().unwrap();
    log::debug!("Found remote URL {} for 'origin'", remote_url);
    let remote_url = GitUrl::parse(remote_url).unwrap();
    let forge = Forge::from_url(&remote_url)?;
    let url = forge.get_url(&remote_url, commit)?;
    Ok(url)
}

trait Forger {
    fn get_url(&self, url: &GitUrl, commit: Commit) -> Result<String>;
}

enum Forge {
    #[cfg(feature = "sourcehut")]
    SourceHut(sourcehut::SourceHut),
    #[cfg(feature = "github")]
    GitHub(github::GitHub),
}

impl Forger for Forge {
    fn get_url(&self, url: &GitUrl, commit: Commit) -> Result<String> {
        match self {
            #[cfg(feature = "sourcehut")]
            Forge::SourceHut(s) => s.get_url(url, commit),
            #[cfg(feature = "github")]
            Forge::GitHub(g) => g.get_url(url, commit),
        }
    }
}

impl Forge {
    fn from_url(url: &GitUrl) -> Result<Self> {
        match &url.host {
            Some(u) => match &u.as_str()[..] {
                #[cfg(feature = "sourcehut")]
                "git.sr.ht" => Ok(Forge::SourceHut(sourcehut::SourceHut {})),
                #[cfg(feature = "github")]
                "github.com" => Ok(Forge::GitHub(github::GitHub {})),
                v => unimplemented!("Host {} not handled", v),
            },
            None => unimplemented!("URL {} has no domain", url),
        }
    }
}

#[cfg(feature = "sourcehut")]
mod sourcehut {
    use super::Forger;
    use crate::error::Result;
    use git2::Commit;
    use git_url_parse::GitUrl;

    pub struct SourceHut {}

    impl Forger for SourceHut {
        fn get_url(&self, url: &GitUrl, commit: Commit) -> Result<String> {
            Ok(format!(
                "https://git.sr.ht/{}/commit/{}",
                url.fullname,
                commit.id()
            ))
        }
    }
}

#[cfg(feature = "github")]
mod github {
    use super::Forger;
    use crate::error::Result;
    use git2::Commit;
    use git_url_parse::GitUrl;

    pub struct GitHub {}

    impl Forger for GitHub {
        fn get_url(&self, url: &GitUrl, commit: Commit) -> Result<String> {
            let repo = url.fullname.clone();
            let msg = commit.message().unwrap_or_default().to_string();
            let re = regex::Regex::new(r#"^Merge pull request #(\d+) from"#).unwrap();
            for line in msg.lines() {
                if let Some(captures) = re.captures(line) {
                    let pr_num = &captures[1];
                    return Ok(format!("https://github.com/{}/pull/{}", repo, pr_num));
                }
            }
            Ok(format!(
                "https://github.com/{}/commit/{}",
                repo,
                commit.id()
            ))
        }
    }
}