Badges: mirrors are what sync, and package alt text names the format

a0aa462 · gmorell · 2026-11-10 01:18

13 files +50 -10
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud/badges.ex
+22 −5
@@ -420,17 +420,34 @@ defmodule GitGud.Badges do
420 420 end)
421 421 end
422 422
423 + @doc """
424 + Whether `repo` follows an upstream: one it syncs from on a schedule,
425 + or has synced from, or tried to. A repository imported once from
426 + another forge also keeps its `upstream_url`, but having never synced
427 + and not being set to, it's developed here and isn't a mirror.
428 + """
429 + @spec mirror?(Repository.t()) :: boolean()
430 + def mirror?(%Repository{upstream_url: nil}), do: false
431 +
432 + def mirror?(%Repository{} = repo) do
433 + repo.upstream_auto_sync == true or not is_nil(repo.upstream_synced_at) or
434 + not is_nil(repo.upstream_sync_error)
435 + end
436 +
423 437 @doc "The `mirror` badge for `repo` as of `now`."
424 − def mirror(%Repository{upstream_url: nil}, _now),
425 − do: %{label: "mirror", value: "not a mirror", slot: "muted"}
438 + def mirror(%Repository{} = repo, now) do
439 + if mirror?(repo), do: mirror_state(repo, now), else: not_a_mirror()
440 + end
441 +
442 + defp not_a_mirror, do: %{label: "mirror", value: "not a mirror", slot: "muted"}
426 443
427 − def mirror(%Repository{upstream_sync_error: error}, _now) when is_binary(error),
444 + defp mirror_state(%Repository{upstream_sync_error: error}, _now) when is_binary(error),
428 445 do: %{label: "mirror", value: "sync failing", slot: "error"}
429 446
430 − def mirror(%Repository{upstream_synced_at: nil}, _now),
447 + defp mirror_state(%Repository{upstream_synced_at: nil}, _now),
431 448 do: %{label: "mirror", value: "never synced", slot: "warning"}
432 449
433 − def mirror(%Repository{upstream_synced_at: at}, now),
450 + defp mirror_state(%Repository{upstream_synced_at: at}, now),
434 451 do: mirror_age(max(DateTime.diff(now, at, :second), 0))
435 452
436 453 defp mirror_age(seconds) do
modified lib/git_gud_web/badge_catalog.ex
+9 −4
@@ -90,7 +90,7 @@ defmodule GitGudWeb.BadgeCatalog do
90 90 @doc """
91 91 Every badge `repo` has, grouped (status, activity, project, mark) and
92 92 in display order within each group. A mirror badge only for a
93 − repository that follows an upstream; a workflow badge for each
93 + repository that follows an upstream (`Badges.mirror?/1`); a workflow badge for each
94 94 workflow that has run on the default branch; a package badge for each
95 95 package.
96 96 """
@@ -126,8 +126,9 @@ defmodule GitGudWeb.BadgeCatalog do
126 126 |> Enum.sort_by(&group_rank(&1.group))
127 127 end
128 128
129 − defp plain_kinds(%Repository{upstream_url: nil}), do: @kinds -- ["mirror"]
130 − defp plain_kinds(_repo), do: @kinds
129 + # A mirror badge only for a repository that really follows an upstream,
130 + # not one imported once and developed here since (`Badges.mirror?/1`).
131 + defp plain_kinds(repo), do: if(Badges.mirror?(repo), do: @kinds, else: @kinds -- ["mirror"])
131 132
132 133 defp item(key, content, title, hint) do
133 134 {kind, _name, _registry} = split_key(key)
@@ -260,7 +261,11 @@ defmodule GitGudWeb.BadgeCatalog do
260 261 def alt(%Spec{kind: "last-commit"}), do: "last commit"
261 262 def alt(%Spec{kind: "coverage"}), do: "coverage"
262 263 def alt(%Spec{kind: "workflow", name: name}), do: "#{name} status"
263 − def alt(%Spec{kind: "package", name: name}), do: "install #{name}"
264 + # The format too: a package published as both deb and rpm has a badge
265 + # for each, and they shouldn't read the same.
266 + def alt(%Spec{kind: "package", name: name, registry: registry}),
267 + do: "install #{name} (#{registry})"
268 +
264 269 def alt(%Spec{kind: "issues"}), do: "open issues"
265 270 def alt(%Spec{kind: "pulls"}), do: "open pull requests"
266 271 def alt(%Spec{kind: "commits"}), do: "commits"
modified test/git_gud/badges_test.exs
+8 −1
@@ -447,7 +447,14 @@ defmodule GitGud.BadgesTest do
447 447 mirror = %{repo | upstream_url: "https://example.com/x.git"}
448 448
449 449 assert %{value: "not a mirror", slot: "muted"} = Badges.mirror(repo, now)
450 − assert %{value: "never synced", slot: "warning"} = Badges.mirror(mirror, now)
450 +
451 + # Imported once and never synced since: developed here, not a mirror.
452 + refute Badges.mirror?(mirror)
453 + assert %{value: "not a mirror"} = Badges.mirror(mirror, now)
454 +
455 + # Set to sync, but hasn't yet.
456 + assert %{value: "never synced", slot: "warning"} =
457 + Badges.mirror(%{mirror | upstream_auto_sync: true}, now)
451 458
452 459 assert %{value: "synced 3h ago", slot: "success"} =
453 460 Badges.mirror(%{mirror | upstream_synced_at: ~U[2026-11-09 09:00:00Z]}, now)
modified test/git_gud_web/controllers/api/mobile/mobile_badges_test.exs
+11 −0
@@ -87,4 +87,15 @@ defmodule GitGudWeb.Api.Mobile.MobileBadgesTest do
87 87 {stranger, _} = repository_fixture()
88 88 assert conn |> as(token_for(stranger, ["repo:read"])) |> get(path) |> json_response(404)
89 89 end
90 +
91 + test "a package's deb and rpm badges say which is which" do
92 + alias GitGud.Badges.Spec
93 + alias GitGudWeb.BadgeCatalog
94 +
95 + deb = BadgeCatalog.spec(%Spec{}, "package:deb:neiam-branding", "auto", "svg")
96 + rpm = BadgeCatalog.spec(%Spec{}, "package:rpm:neiam-branding", "auto", "svg")
97 +
98 + assert BadgeCatalog.alt(deb) == "install neiam-branding (deb)"
99 + assert BadgeCatalog.alt(rpm) == "install neiam-branding (rpm)"
100 + end
90 101 end

Parents: 308b64e