Support for self-hosted Gitea (#164)

deps.rs is now available for self-hosted Gitea at
`/repo/gitea/<DOMAIN>/owner/repo`, e. g.
`/repo/gitea/git.example.org/deps-rs/deps.rs`,
`/repo/gitea/git.example.org:1234/deps-rs/deps.rs`,
`/repo/gitea/http://unsafe-gitea.org/deps-rs/deps.rs`.

This _should_ also include support for Gitea hosted in subdirectories,
e. g. `www.example.org/gitea`, though I haven't tested this yet.

If no protocol (`https://`/`http://`) is specified, `https://` is
automatically added to the beginning of the gitea server's URL.
However I could also change this to only accept https. Another
option might be the use of URL-encoding.
I am open for feedback, feel free to suggest changes.

Implementation notes:

- The Router now matches `/repo/*site/:qual/:name` instead of
  `/repo/:site/:qual/:name` to allow for an arbitrary number of
  `/`s before qual and name.
- `RepoSite` now has a new variant `Gitea(GiteaDomain)`.
- `RepoSite` no longer implements `Copy`. However this should not
  be problematic because `Copy`ing was only used for `to_base_uri`,
  `to_usercontent_base_uri` and `to_usercontent_repo_suffix` which
  now accept `&self` references.
- `RepoSite` no longer implements `AsRef` and now uses `Display`
  instead.

- updated test `correct_raw_url_generation`
- updated readme

Related to #84, #141
This commit is contained in:
valentinleistner 2022-08-21 11:16:52 +02:00 committed by GitHub
parent a991fa8eb1
commit ba7647dcff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 28 deletions

View file

@ -13,7 +13,7 @@ We currently support projects and crates hosted on crates.io, Github, Gitlab, Bi
To analyze the state of your dependencies you can use the following URLs:
- for projects on crates.io: `https://deps.rs/crate/<NAME>`
- for projects on Github, Gitlab, Bitbucket, SourceHut, or Codeberg: `https://deps.rs/repo/<HOSTER>/<USER>/<REPO>` (where `<HOSTER>` is either `github`, `gitlab`, `bitbucket`, `sourcehut`, or `codeberg`)
- for projects on Github, Gitlab, Bitbucket, SourceHut, Codeberg, or Gitea: `https://deps.rs/repo/<HOSTER>/<USER>/<REPO>` (where `<HOSTER>` is either `github`, `gitlab`, `bitbucket`, `sourcehut`, `codeberg`, or `gitea/<DOMAIN>`)
## Badges

View file

@ -42,49 +42,52 @@ impl fmt::Display for RepoPath {
write!(
f,
"{} => {}/{}",
self.site.as_ref(),
self.site.to_string(),
self.qual.as_ref(),
self.name.as_ref()
)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum RepoSite {
Github,
Gitlab,
Bitbucket,
Sourcehut,
Codeberg,
Gitea(GiteaDomain),
}
impl RepoSite {
pub fn to_base_uri(self) -> &'static str {
pub fn to_base_uri(&self) -> &str {
match self {
RepoSite::Github => "https://github.com",
RepoSite::Gitlab => "https://gitlab.com",
RepoSite::Bitbucket => "https://bitbucket.org",
RepoSite::Sourcehut => "https://git.sr.ht",
RepoSite::Codeberg => "https://codeberg.org",
RepoSite::Gitea(domain) => domain.as_ref(),
}
}
pub fn to_usercontent_base_uri(self) -> &'static str {
pub fn to_usercontent_base_uri(&self) -> &str {
match self {
RepoSite::Github => "https://raw.githubusercontent.com",
RepoSite::Gitlab => "https://gitlab.com",
RepoSite::Bitbucket => "https://bitbucket.org",
RepoSite::Sourcehut => "https://git.sr.ht",
RepoSite::Codeberg => "https://codeberg.org",
RepoSite::Gitea(domain) => domain.as_ref(),
}
}
pub fn to_usercontent_repo_suffix(self) -> &'static str {
pub fn to_usercontent_repo_suffix(&self) -> &'static str {
match self {
RepoSite::Github => "HEAD",
RepoSite::Gitlab | RepoSite::Bitbucket => "raw/HEAD",
RepoSite::Sourcehut => "blob/HEAD",
RepoSite::Codeberg => "raw",
RepoSite::Codeberg | RepoSite::Gitea(_) => "raw",
}
}
}
@ -93,6 +96,12 @@ impl FromStr for RepoSite {
type Err = Error;
fn from_str(input: &str) -> Result<RepoSite, Error> {
if let Some((site, domain)) = input.split_once("/") {
match site {
"gitea" => Ok(RepoSite::Gitea(domain.parse()?)),
_ => Err(anyhow!("unknown repo site identifier")),
}
} else {
match input {
"github" => Ok(RepoSite::Github),
"gitlab" => Ok(RepoSite::Gitlab),
@ -102,16 +111,49 @@ impl FromStr for RepoSite {
_ => Err(anyhow!("unknown repo site identifier")),
}
}
}
}
impl AsRef<str> for RepoSite {
fn as_ref(&self) -> &str {
impl fmt::Display for RepoSite {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RepoSite::Github => "github",
RepoSite::Gitlab => "gitlab",
RepoSite::Bitbucket => "bitbucket",
RepoSite::Sourcehut => "sourcehut",
RepoSite::Codeberg => "codeberg",
RepoSite::Github => write!(f, "github"),
RepoSite::Gitlab => write!(f, "gitlab"),
RepoSite::Bitbucket => write!(f, "bitbucket"),
RepoSite::Sourcehut => write!(f, "sourcehut"),
RepoSite::Codeberg => write!(f, "codeberg"),
RepoSite::Gitea(s) => write!(f, "gitea/{s}"),
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct GiteaDomain(String);
impl FromStr for GiteaDomain {
type Err = Error;
fn from_str(input: &str) -> Result<GiteaDomain, Error> {
if input.starts_with("https://") || input.starts_with("http://") {
Ok(GiteaDomain(input.to_string()))
} else {
Ok(GiteaDomain(format!("https://{input}")))
}
}
}
impl AsRef<str> for GiteaDomain {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl fmt::Display for GiteaDomain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.starts_with("https://") {
f.write_str(&self.0["https://".len()..])
} else {
self.0.fmt(f)
}
}
}
@ -215,5 +257,21 @@ mod tests {
let exp = format!("https://codeberg.org/deps-rs/deps.rs/raw/{}", expected);
assert_eq!(out.to_string(), exp);
}
for (input, expected) in &paths {
let repo = RepoPath::from_parts("gitea/gitea.com", "deps-rs", "deps.rs").unwrap();
let out = repo.to_usercontent_file_url(RelativePath::new(input));
let exp = format!("https://gitea.com/deps-rs/deps.rs/raw/{}", expected);
assert_eq!(out.to_string(), exp);
}
for (input, expected) in &paths {
let repo = RepoPath::from_parts("gitea/example.com/git", "deps-rs", "deps.rs").unwrap();
let out = repo.to_usercontent_file_url(RelativePath::new(input));
let exp = format!("https://example.com/git/deps-rs/deps.rs/raw/{}", expected);
assert_eq!(out.to_string(), exp);
}
}
}

View file

@ -58,11 +58,11 @@ impl App {
router.add("/static/logo.svg", Route::Static(StaticFile::FaviconPng));
router.add(
"/repo/:site/:qual/:name",
"/repo/*site/:qual/:name",
Route::RepoStatus(StatusFormat::Html),
);
router.add(
"/repo/:site/:qual/:name/status.svg",
"/repo/*site/:qual/:name/status.svg",
Route::RepoStatus(StatusFormat::Svg),
);

View file

@ -21,12 +21,12 @@ fn popular_table(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>)
@for repo in popular_repos.into_iter().take(10) {
tr {
td {
a href=(format!("{}/repo/{}/{}/{}", &super::SELF_BASE_URL as &str, repo.path.site.as_ref(), repo.path.qual.as_ref(), repo.path.name.as_ref())) {
a href=(format!("{}/repo/{}/{}/{}", &super::SELF_BASE_URL as &str, repo.path.site, repo.path.qual.as_ref(), repo.path.name.as_ref())) {
(format!("{} / {}", repo.path.qual.as_ref(), repo.path.name.as_ref()))
}
}
td class="has-text-right" {
img src=(format!("{}/repo/{}/{}/{}/status.svg", &super::SELF_BASE_URL as &str, repo.path.site.as_ref(), repo.path.qual.as_ref(), repo.path.name.as_ref()));
img src=(format!("{}/repo/{}/{}/{}/status.svg", &super::SELF_BASE_URL as &str, repo.path.site, repo.path.qual.as_ref(), repo.path.name.as_ref()));
}
}
}

View file

@ -127,9 +127,11 @@ fn get_site_icon(site: &RepoSite) -> (FaType, &'static str) {
RepoSite::Github => (FaType::Brands, "github"),
RepoSite::Gitlab => (FaType::Brands, "gitlab"),
RepoSite::Bitbucket => (FaType::Brands, "bitbucket"),
// FIXME: There is no brands/{sourcehut, codeberg} icon, so just use a
// FIXME: There is no brands/{sourcehut, codeberg, gitea} icon, so just use a
// regular circle which looks close enough.
RepoSite::Sourcehut | RepoSite::Codeberg => (FaType::Regular, "circle"),
RepoSite::Sourcehut | RepoSite::Codeberg | RepoSite::Gitea(_) => {
(FaType::Regular, "circle")
}
}
}
@ -334,7 +336,7 @@ fn render_success(
let self_path = match subject_path {
SubjectPath::Repo(ref repo_path) => format!(
"repo/{}/{}/{}",
repo_path.site.as_ref(),
repo_path.site,
repo_path.qual.as_ref(),
repo_path.name.as_ref()
),