Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
crates/model/src/entity.rs +11 −0
| @@ -94,6 +94,17 @@ impl Visibility { | |||
| 94 | pub fn is_private(self) -> bool { | 94 | pub fn is_private(self) -> bool { |
| 95 | matches!(self, Self::Private) | 95 | matches!(self, Self::Private) |
| 96 | } | 96 | } |
| 97 | |||
| 98 | /// A rank where higher is more visible (`private` < `internal` < `public`). | ||
| 99 | /// Used to cap a fork's visibility at its parent's. | ||
| 100 | #[must_use] | ||
| 101 | pub fn rank(self) -> u8 { | ||
| 102 | match self { | ||
| 103 | Self::Private => 0, | ||
| 104 | Self::Internal => 1, | ||
| 105 | Self::Public => 2, | ||
| 106 | } | ||
| 107 | } | ||
| 97 | } | 108 | } |
| 98 | 109 | ||
| 99 | impl std::fmt::Display for Visibility { | 110 | impl std::fmt::Display for Visibility { |
crates/web/src/icons.rs +5 −0
| @@ -40,6 +40,7 @@ pub enum Icon { | |||
| 40 | Globe, | 40 | Globe, |
| 41 | Download, | 41 | Download, |
| 42 | Mirror, | 42 | Mirror, |
| 43 | GitFork, | ||
| 43 | Github, | 44 | Github, |
| 44 | Mastodon, | 45 | Mastodon, |
| 45 | } | 46 | } |
| @@ -123,6 +124,10 @@ impl Icon { | |||
| 123 | Icon::Mirror => { | 124 | Icon::Mirror => { |
| 124 | r#"<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>"# | 125 | r#"<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>"# |
| 125 | } | 126 | } |
| 127 | // Lucide "git-fork": two branches diverging from a commit. | ||
| 128 | Icon::GitFork => { | ||
| 129 | r#"<circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"/><path d="M12 12v3"/>"# | ||
| 130 | } | ||
| 126 | Icon::Github => { | 131 | Icon::Github => { |
| 127 | r#"<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>"# | 132 | r#"<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>"# |
| 128 | } | 133 | } |
crates/web/src/repo.rs +20 −6
| @@ -1646,8 +1646,10 @@ pub async fn fork_confirm( | |||
| 1646 | form method="post" action=(format!("/repo-fork/{}", source.id)) { | 1646 | form method="post" action=(format!("/repo-fork/{}", source.id)) { |
| 1647 | input type="hidden" name="_csrf" value=(chrome.csrf); | 1647 | input type="hidden" name="_csrf" value=(chrome.csrf); |
| 1648 | label for="fk-name" { "Repository name" } | 1648 | label for="fk-name" { "Repository name" } |
| 1649 | input type="text" id="fk-name" name="name" value=(source.name) required; | 1649 | div class="admin-form-row admin-form-row-last" { |
| 1650 | button class="btn btn-primary inline-btn" type="submit" { "Fork repository" } | 1650 | input type="text" id="fk-name" name="name" value=(source.name) required; |
| 1651 | button class="btn btn-primary inline-btn" type="submit" { "Fork repository" } | ||
| 1652 | } | ||
| 1651 | } | 1653 | } |
| 1652 | } | 1654 | } |
| 1653 | } | 1655 | } |
| @@ -1839,7 +1841,15 @@ pub async fn settings_general( | |||
| 1839 | .store | 1841 | .store |
| 1840 | .rename_repo(&repo.id, new_name, &new_path) | 1842 | .rename_repo(&repo.id, new_name, &new_path) |
| 1841 | .await?; | 1843 | .await?; |
| 1842 | if let Some(vis) = Visibility::from_token(&form.visibility) { | 1844 | if let Some(mut vis) = Visibility::from_token(&form.visibility) { |
| 1845 | // A fork may never be more visible than its parent (no laundering a | ||
| 1846 | // private repo into a public one). | ||
| 1847 | if let Some(parent_id) = &repo.fork_parent_id | ||
| 1848 | && let Some(parent) = state.store.repo_by_id(parent_id).await? | ||
| 1849 | && vis.rank() > parent.visibility.rank() | ||
| 1850 | { | ||
| 1851 | vis = parent.visibility; | ||
| 1852 | } | ||
| 1843 | state.store.set_visibility(&repo.id, vis).await?; | 1853 | state.store.set_visibility(&repo.id, vis).await?; |
| 1844 | } | 1854 | } |
| 1845 | state | 1855 | state |
| @@ -2574,6 +2584,8 @@ pub(crate) struct RepoEntry { | |||
| 2574 | pub(crate) archived: bool, | 2584 | pub(crate) archived: bool, |
| 2575 | /// Whether the repo is a mirror (shows the mirror icon). | 2585 | /// Whether the repo is a mirror (shows the mirror icon). |
| 2576 | pub(crate) is_mirror: bool, | 2586 | pub(crate) is_mirror: bool, |
| 2587 | /// Whether the repo is a fork (shows the fork icon). | ||
| 2588 | pub(crate) is_fork: bool, | ||
| 2577 | /// Optional one-line description, shown under the name when present. | 2589 | /// Optional one-line description, shown under the name when present. |
| 2578 | pub(crate) description: Option<String>, | 2590 | pub(crate) description: Option<String>, |
| 2579 | } | 2591 | } |
| @@ -2589,6 +2601,7 @@ impl RepoEntry { | |||
| 2589 | updated: repo.pushed_at.unwrap_or(repo.updated_at), | 2601 | updated: repo.pushed_at.unwrap_or(repo.updated_at), |
| 2590 | archived: repo.archived_at.is_some(), | 2602 | archived: repo.archived_at.is_some(), |
| 2591 | is_mirror: repo.is_mirror, | 2603 | is_mirror: repo.is_mirror, |
| 2604 | is_fork: repo.fork_parent_id.is_some(), | ||
| 2592 | description: repo.description.clone(), | 2605 | description: repo.description.clone(), |
| 2593 | } | 2606 | } |
| 2594 | } | 2607 | } |
| @@ -2604,6 +2617,7 @@ impl RepoEntry { | |||
| 2604 | updated: repo.pushed_at.unwrap_or(repo.updated_at), | 2617 | updated: repo.pushed_at.unwrap_or(repo.updated_at), |
| 2605 | archived: repo.archived_at.is_some(), | 2618 | archived: repo.archived_at.is_some(), |
| 2606 | is_mirror: repo.is_mirror, | 2619 | is_mirror: repo.is_mirror, |
| 2620 | is_fork: repo.fork_parent_id.is_some(), | ||
| 2607 | description: repo.description.clone(), | 2621 | description: repo.description.clone(), |
| 2608 | } | 2622 | } |
| 2609 | } | 2623 | } |
| @@ -2643,7 +2657,7 @@ pub(crate) fn repo_card(title: &str, empty: &str, entries: &[RepoEntry]) -> Mark | |||
| 2643 | li { | 2657 | li { |
| 2644 | a class="repo-row" href=(e.href) { | 2658 | a class="repo-row" href=(e.href) { |
| 2645 | span class="repo-row-name" { | 2659 | span class="repo-row-name" { |
| 2646 | (icon(if e.is_mirror { Icon::Mirror } else { Icon::Box })) | 2660 | (icon(if e.is_mirror { Icon::Mirror } else if e.is_fork { Icon::GitFork } else { Icon::Box })) |
| 2647 | span { (e.label) } | 2661 | span { (e.label) } |
| 2648 | (visibility_badge(e.visibility)) | 2662 | (visibility_badge(e.visibility)) |
| 2649 | (archived_badge(e.archived)) | 2663 | (archived_badge(e.archived)) |
| @@ -2820,7 +2834,7 @@ pub(crate) fn repo_header( | |||
| 2820 | } | 2834 | } |
| 2821 | @if let Some((powner, ppath)) = &ctx.fork_parent { | 2835 | @if let Some((powner, ppath)) = &ctx.fork_parent { |
| 2822 | p class="muted fork-note" { | 2836 | p class="muted fork-note" { |
| 2823 | (icon(Icon::Mirror)) " forked from " | 2837 | (icon(Icon::GitFork)) " forked from " |
| 2824 | a href=(format!("/{powner}/{ppath}")) { (powner) "/" (ppath) } | 2838 | a href=(format!("/{powner}/{ppath}")) { (powner) "/" (ppath) } |
| 2825 | } | 2839 | } |
| 2826 | } | 2840 | } |
| @@ -2844,7 +2858,7 @@ pub(crate) fn repo_header( | |||
| 2844 | div class="repo-actions" { | 2858 | div class="repo-actions" { |
| 2845 | @if ctx.viewer_id.is_some() { | 2859 | @if ctx.viewer_id.is_some() { |
| 2846 | a class="btn" href=(format!("/repo-fork/{}", ctx.repo.id)) { | 2860 | a class="btn" href=(format!("/repo-fork/{}", ctx.repo.id)) { |
| 2847 | (icon(Icon::GitBranch)) span { "Fork" } | 2861 | (icon(Icon::GitFork)) span { "Fork" } |
| 2848 | } | 2862 | } |
| 2849 | } | 2863 | } |
| 2850 | (branch_menu(&base, rev, branches)) | 2864 | (branch_menu(&base, rev, branches)) |