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

use crate::error::{Error, Result};

pub fn get_url(
    repo: &Repository,
    remote: &str,
    commit: Commit,
) -> Result<String> {
    let remote =
        repo.find_remote(remote)
            .map_err(|e| Error::FindRemoteError {
                remote: remote.to_string(),
                source: e,
            })?;
    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[..] {
                #[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 git2::Commit;
    use git_url_parse::GitUrl;

    use super::Forger;
    use crate::error::Result;

    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 git2::Commit;
    use git_url_parse::GitUrl;

    use super::Forger;
    use crate::error::Result;

    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()
            ))
        }
    }
}