diff --git a/crates/radicle-httpd/src/api.rs b/crates/radicle-httpd/src/api.rs index b2910531b..6e2cf4fd6 100644 --- a/crates/radicle-httpd/src/api.rs +++ b/crates/radicle-httpd/src/api.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use axum::response::{IntoResponse, Json}; @@ -75,6 +75,10 @@ pub struct Context { /// `RADICLE_SEARCH_URL`. When absent, listing and search fall back to /// the storage walk. search: Option, + /// The repo aliases configured via `--alias`, mapping each alias to its + /// [`RepoId`]. Used to resolve alias path segments to a repo and to + /// advertise a repo's short name in its info. + repo_aliases: Arc>, } impl Context { @@ -117,9 +121,21 @@ impl Context { cache: options.cache.map(Cache::new), web_config, search, + repo_aliases: Arc::new(options.aliases.clone()), }) } + /// Return the configured alias for a repo, if any. When several aliases + /// point at the same repo, the lexicographically smallest is returned so + /// the result is stable across requests. + fn repo_alias(&self, rid: &RepoId) -> Option { + self.repo_aliases + .iter() + .filter(|(_, id)| *id == rid) + .map(|(alias, _)| alias.clone()) + .min() + } + /// The search backend client, if one is configured and reachable at /// startup. `None` means listing and search use the storage walk. pub fn search(&self) -> Option<&SearchClient> { @@ -180,6 +196,7 @@ impl Context { rid, seeding, refs, + alias: self.repo_alias(&rid), }) } @@ -501,6 +518,9 @@ mod repo { pub rid: RepoId, pub seeding: usize, pub refs: CanonicalReferences, + /// The alias configured for this repo via `--alias`, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub alias: Option, } } diff --git a/http-client/lib/repo.ts b/http-client/lib/repo.ts index 82b818c72..2feaeb62b 100644 --- a/http-client/lib/repo.ts +++ b/http-client/lib/repo.ts @@ -84,6 +84,7 @@ const repoSchema = object({ tags: record(string(), tagInfoSchema), refs: record(string(), string()), }).optional(), + alias: string().optional(), }); const reposSchema = array(repoSchema); diff --git a/src/views/repos/Header/CloneButton.svelte b/src/views/repos/Header/CloneButton.svelte index 5d3da152a..cfb8e3391 100644 --- a/src/views/repos/Header/CloneButton.svelte +++ b/src/views/repos/Header/CloneButton.svelte @@ -22,6 +22,7 @@ export let id: string; export let name: string; export let currentRefname: string; + export let alias: string | undefined = undefined; let activeTab: "radicle" | "git" | "archive" = "radicle"; let archiveProbe: Promise | undefined; @@ -39,10 +40,10 @@ baseUrl.port === config.nodes.defaultHttpdPort ? "" : `:${baseUrl.port}`; - $: archiveUrl = `curl -OJ ${baseUrl.scheme}://${baseUrl.hostname}${portFragment}/raw/${id}/archive/${currentRefname}`; + $: archiveUrl = `curl -OJ ${baseUrl.scheme}://${baseUrl.hostname}${portFragment}/raw/${alias ?? id}/archive/${currentRefname}`; $: gitCloneUrl = `git clone ${baseUrl.scheme}://${ baseUrl.hostname - }${portFragment}/${parseRepositoryId(id)?.pubkey ?? id}.git ${name}`; + }${portFragment}/${alias ?? parseRepositoryId(id)?.pubkey ?? id}.git ${name}`;