Skip to main content

cargo_unmaintained/github/
mod.rs

1use super::{RepoStatus, Url};
2use anyhow::Result;
3use regex::Regex;
4use std::{sync::LazyLock, time::SystemTime};
5
6#[allow(clippy::unwrap_used)]
7static GITHUB_RE: LazyLock<Regex> = LazyLock::new(|| {
8    Regex::new(r"^https://github\.com/([^/?#]+)/([^/?#]+?)(?:\.git)?(?:[/#?]|$)").unwrap()
9});
10
11/// Extracts `(owner, repo)` from a Github URL.
12/// Returns `None` if the URL does not match `https://github.com/{owner}/{repo}`.
13pub(crate) fn parse_github_url(url: &str) -> Option<(&str, &str)> {
14    let captures = GITHUB_RE.captures(url)?;
15    #[allow(clippy::unwrap_used)]
16    let owner = captures.get(1).unwrap().as_str();
17    #[allow(clippy::unwrap_used)]
18    let repo = captures.get(2).unwrap().as_str();
19    Some((owner, repo))
20}
21
22/// A Github repository extracted from a package URL.
23pub(crate) struct GithubRepo<'a> {
24    pub url: Url<'a>,
25    pub owner: &'a str,
26    pub repo: &'a str,
27}
28
29impl<'a> GithubRepo<'a> {
30    pub(crate) fn from_url(url: Url<'a>) -> Option<Self> {
31        let (owner, repo) = parse_github_url(url.as_str())?;
32        Some(Self {
33            url: canonical_github_url(url),
34            owner,
35            repo,
36        })
37    }
38}
39
40pub(crate) fn canonical_github_url(url: Url<'_>) -> Url<'_> {
41    let shortened = url.shorten().unwrap_or(url);
42    shortened
43        .as_str()
44        .strip_suffix(".git")
45        .map_or(shortened, Url::from)
46}
47
48pub(crate) trait Github {
49    fn load_token(f: impl FnOnce(&str) -> Result<()>) -> Result<bool>;
50    fn save_token() -> Result<()>;
51    fn archival_status(url: Url) -> Result<RepoStatus<()>>;
52    fn prefetch<'a>(repos: &'a [GithubRepo<'a>]) -> Result<Vec<RepoStatus<'a, SystemTime>>>;
53}
54
55// smoelius: If `__real_github` is enabled, we assume that `--all-features` was passed and therefore
56// disable `__mock_github`.
57
58#[cfg(all(feature = "__mock_github", not(feature = "__real_github")))]
59mod mock;
60#[cfg(all(feature = "__mock_github", not(feature = "__real_github")))]
61pub use mock::Impl;
62
63#[cfg(any(not(feature = "__mock_github"), feature = "__real_github"))]
64mod real;
65#[cfg(any(not(feature = "__mock_github"), feature = "__real_github"))]
66pub use real::Impl;
67#[cfg(any(not(feature = "__mock_github"), feature = "__real_github"))]
68pub use real::util;
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn parse_github_url_accepts_common_forms() {
76        assert_eq!(
77            parse_github_url("https://github.com/owner/repo"),
78            Some(("owner", "repo"))
79        );
80        assert_eq!(
81            parse_github_url("https://github.com/owner/repo.git"),
82            Some(("owner", "repo"))
83        );
84        assert_eq!(
85            parse_github_url("https://github.com/owner/repo/"),
86            Some(("owner", "repo"))
87        );
88    }
89
90    #[test]
91    fn parse_github_url_stops_at_query() {
92        assert_eq!(
93            parse_github_url("https://github.com/owner/repo?tab=readme"),
94            Some(("owner", "repo"))
95        );
96    }
97
98    #[test]
99    fn parse_github_url_stops_at_fragment() {
100        assert_eq!(
101            parse_github_url("https://github.com/owner/repo#readme"),
102            Some(("owner", "repo"))
103        );
104    }
105
106    #[test]
107    fn parse_github_url_rejects_incomplete_or_non_github_urls() {
108        assert_eq!(parse_github_url("https://github.com/"), None);
109        assert_eq!(parse_github_url("https://github.com/owner"), None);
110        assert_eq!(parse_github_url("https://example.com/owner/repo"), None);
111    }
112
113    #[test]
114    fn canonical_github_url_normalizes_aliases() {
115        assert_eq!(
116            canonical_github_url("https://github.com/owner/repo.git".into()).as_str(),
117            "https://github.com/owner/repo"
118        );
119        assert_eq!(
120            canonical_github_url("https://github.com/owner/repo/tree/main".into()).as_str(),
121            "https://github.com/owner/repo"
122        );
123        assert_eq!(
124            canonical_github_url("https://github.com/owner/repo.git/tree/main".into()).as_str(),
125            "https://github.com/owner/repo"
126        );
127    }
128
129    #[test]
130    fn github_repo_from_url_returns_parts_and_canonical_url() {
131        let github_repo =
132            GithubRepo::from_url("https://github.com/owner/repo.git/tree/main".into());
133        assert!(github_repo.is_some());
134        let Some(github_repo) = github_repo else {
135            return;
136        };
137
138        assert_eq!(github_repo.url.as_str(), "https://github.com/owner/repo");
139        assert_eq!(github_repo.owner, "owner");
140        assert_eq!(github_repo.repo, "repo");
141    }
142}