Credo: complex functions split into tables and named helpers

f611475 · Gabe Morell · 2026-11-04 20:49

27 files +302 -262
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud/outbound_url.ex
+15 −11
@@ -254,18 +254,10 @@ defmodule GitGud.OutboundUrl do
254 254 Whether `ip` (an `:inet` tuple) is somewhere a user-chosen URL must
255 255 not reach.
256 256 """
257 − def blocked?({a, b, _c, _d} = ip) when tuple_size(ip) == 4 do
258 − a == 0 or a == 10 or a == 127 or a >= 224 or
259 − (a == 100 and (b &&& 0xC0) == 64) or
260 − (a == 169 and b == 254) or
261 − (a == 172 and (b &&& 0xF0) == 16) or
262 − (a == 192 and b == 168) or
263 − (a == 192 and b == 0 and elem(ip, 2) in [0, 2]) or
264 − (a == 198 and b in [18, 19]) or
265 − (a == 198 and b == 51 and elem(ip, 2) == 100) or
266 − (a == 203 and b == 0 and elem(ip, 2) == 113)
267 − end
257 + def blocked?({a, b, c, _d} = ip) when tuple_size(ip) == 4, do: blocked_v4?(a, b, c)
268 258
259 + # The v6 ranges, one per line, read best as a single table.
260 + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
269 261 def blocked?({a, b, c, d, e, f, g, h} = ip) when tuple_size(ip) == 8 do
270 262 cond do
271 263 # :: and ::1
@@ -288,6 +280,18 @@ defmodule GitGud.OutboundUrl do
288 280
289 281 def blocked?(_), do: true
290 282
283 + # By the first three octets.
284 + defp blocked_v4?(a, _b, _c) when a in [0, 10, 127] or a >= 224, do: true
285 + defp blocked_v4?(100, b, _c), do: (b &&& 0xC0) == 64
286 + defp blocked_v4?(169, 254, _c), do: true
287 + defp blocked_v4?(172, b, _c), do: (b &&& 0xF0) == 16
288 + defp blocked_v4?(192, 168, _c), do: true
289 + defp blocked_v4?(192, 0, c), do: c in [0, 2]
290 + defp blocked_v4?(198, b, _c) when b in [18, 19], do: true
291 + defp blocked_v4?(198, 51, 100), do: true
292 + defp blocked_v4?(203, 0, 113), do: true
293 + defp blocked_v4?(_a, _b, _c), do: false
294 +
291 295 defp v4(hi, lo), do: {hi >>> 8, hi &&& 0xFF, lo >>> 8, lo &&& 0xFF}
292 296
293 297 defp ip_string(ip), do: ip |> :inet.ntoa() |> to_string()
modified lib/git_gud/packages/formats/raw.ex
+13 −11
@@ -77,6 +77,17 @@ defmodule GitGud.Packages.Formats.Raw do
77 77 # The version slot that means "resolve it for me".
78 78 @latest "latest"
79 79
80 + @content_types %{
81 + ".json" => "application/json",
82 + ".txt" => "text/plain; charset=utf-8",
83 + ".md" => "text/markdown; charset=utf-8",
84 + ".sha256" => "text/plain; charset=utf-8",
85 + ".csv" => "text/csv; charset=utf-8",
86 + ".gz" => "application/gzip",
87 + ".zip" => "application/zip",
88 + ".tar" => "application/x-tar"
89 + }
90 +
80 91 @doc """
81 92 Media type for a stored artifact.
82 93
@@ -87,17 +98,8 @@ defmodule GitGud.Packages.Formats.Raw do
87 98 """
88 99 @impl true
89 100 def content_type(filename) when is_binary(filename) do
90 − case filename |> Path.extname() |> String.downcase() do
91 − ".json" -> "application/json"
92 − ".txt" -> "text/plain; charset=utf-8"
93 − ".md" -> "text/markdown; charset=utf-8"
94 − ".sha256" -> "text/plain; charset=utf-8"
95 − ".csv" -> "text/csv; charset=utf-8"
96 − ".gz" -> "application/gzip"
97 − ".zip" -> "application/zip"
98 − ".tar" -> "application/x-tar"
99 − _ -> "application/octet-stream"
100 − end
101 + ext = filename |> Path.extname() |> String.downcase()
102 + Map.get(@content_types, ext, "application/octet-stream")
101 103 end
102 104
103 105 def content_type(_filename), do: "application/octet-stream"
modified lib/git_gud/reports.ex
+23 −21
@@ -329,7 +329,7 @@ defmodule GitGud.Reports do
329 329 kind: :issue,
330 330 title: "##{issue.number} #{issue.title}",
331 331 excerpt: excerpt(issue.original_body || issue.body),
332 − url: repo && "/r/#{handle(repo)}/#{repo.name}/issues/#{issue.number}",
332 + url: item_url(repo, "issues", issue.number),
333 333 author: user_name(author),
334 334 author_id: author && author.id,
335 335 moderatable?: true,
@@ -351,10 +351,9 @@ defmodule GitGud.Reports do
351 351
352 352 %{
353 353 kind: :issue_comment,
354 − title:
355 − "comment on ##{issue && issue.number} #{(issue && issue.title) || "(deleted issue)"}",
354 + title: comment_title(issue, "(deleted issue)"),
356 355 excerpt: excerpt(comment.original_body || comment.body),
357 − url: repo && issue && "/r/#{handle(repo)}/#{repo.name}/issues/#{issue.number}",
356 + url: item_url(repo, "issues", issue && issue.number),
358 357 author: user_name(author),
359 358 author_id: author && author.id,
360 359 moderatable?: true,
@@ -378,17 +377,12 @@ defmodule GitGud.Reports do
378 377 kind: :pull_request,
379 378 title: "##{pr.number} #{pr.title}",
380 379 excerpt: excerpt(pr.original_body || pr.body),
381 − url: repo && "/r/#{handle(repo)}/#{repo.name}/pulls/#{pr.number}",
380 + url: item_url(repo, "pulls", pr.number),
382 381 author_id: author && author.id,
383 − author:
384 − cond do
385 − author -> user_name(author)
386 − source_actor -> actor_name(source_actor)
387 − true -> "(unknown)"
388 − end,
382 + author: author_label(author, source_actor),
389 383 moderatable?: true,
390 384 moderated?: GitGud.PullRequests.PullRequest.moderated?(pr),
391 − suspendable_actor_id: source_actor && source_actor.kind == "remote" && source_actor.id
385 + suspendable_actor_id: remote_actor_id(source_actor)
392 386 }
393 387 end
394 388 end
@@ -408,19 +402,14 @@ defmodule GitGud.Reports do
408 402
409 403 %{
410 404 kind: :pr_comment,
411 − title: "comment on ##{pr && pr.number} #{(pr && pr.title) || "(deleted PR)"}",
405 + title: comment_title(pr, "(deleted PR)"),
412 406 excerpt: excerpt(comment.original_body || comment.body),
413 − url: repo && pr && "/r/#{handle(repo)}/#{repo.name}/pulls/#{pr.number}",
407 + url: item_url(repo, "pulls", pr && pr.number),
414 408 author_id: author && author.id,
415 − author:
416 − cond do
417 − author -> user_name(author)
418 − source_actor -> actor_name(source_actor)
419 − true -> "(unknown)"
420 − end,
409 + author: author_label(author, source_actor),
421 410 moderatable?: true,
422 411 moderated?: GitGud.PullRequests.PrComment.moderated?(comment),
423 − suspendable_actor_id: source_actor && source_actor.kind == "remote" && source_actor.id
412 + suspendable_actor_id: remote_actor_id(source_actor)
424 413 }
425 414 end
426 415 end
@@ -465,6 +454,19 @@ defmodule GitGud.Reports do
465 454
466 455 defp build_preview(_, _), do: :missing
467 456
457 + defp comment_title(item, missing),
458 + do: "comment on ##{item && item.number} #{(item && item.title) || missing}"
459 +
460 + defp item_url(nil, _path, _number), do: nil
461 + defp item_url(repo, path, number), do: "/r/#{handle(repo)}/#{repo.name}/#{path}/#{number}"
462 +
463 + defp author_label(nil, nil), do: "(unknown)"
464 + defp author_label(nil, source_actor), do: actor_name(source_actor)
465 + defp author_label(author, _source_actor), do: user_name(author)
466 +
467 + defp remote_actor_id(source_actor),
468 + do: source_actor && source_actor.kind == "remote" && source_actor.id
469 +
468 470 defp repo_for_target_repo_id(rid) do
469 471 case Repo.get(GitGud.Repositories.Repository, rid) do
470 472 nil -> nil
modified lib/git_gud/workflows.ex
+85 −62
@@ -934,12 +934,7 @@ defmodule GitGud.Workflows do
934 934 # Cancelled once this run is committed, rather than from inside its
935 935 # transaction: `cancel/2` releases each one's group, and that
936 936 # should see this run.
937 − for id <- hold.cancel do
938 − case Repo.get(WorkflowRun, id) do
939 − %WorkflowRun{} = superseded -> cancel(superseded, reason: hold.cancel_reason)
940 − nil -> :ok
941 − end
942 − end
937 + cancel_superseded(hold)
943 938
944 939 {:ok, run}
945 940
@@ -948,6 +943,15 @@ defmodule GitGud.Workflows do
948 943 end
949 944 end
950 945
946 + defp cancel_superseded(hold) do
947 + for id <- hold.cancel do
948 + case Repo.get(WorkflowRun, id) do
949 + %WorkflowRun{} = superseded -> cancel(superseded, reason: hold.cancel_reason)
950 + nil -> :ok
951 + end
952 + end
953 + end
954 +
951 955 # The workflow's `concurrency:`, decided as the run is created (GitHub
952 956 # decides a workflow's group then, so it can't read `matrix` or `needs`).
953 957 # Returns the run, with its group recorded, and what to do about it:
@@ -2223,17 +2227,8 @@ defmodule GitGud.Workflows do
2223 2227
2224 2228 locked
2225 2229 |> WorkflowJob.status_changeset(concluded, opts)
2226 − |> then(fn cs ->
2227 − case reason || why do
2228 − nil -> cs
2229 − reason -> Ecto.Changeset.change(cs, status_reason: reason)
2230 − end
2231 − end)
2232 − |> then(fn cs ->
2233 − if is_nil(from) or locked.status in from,
2234 − do: cs,
2235 − else: Ecto.Changeset.add_error(cs, :status, "has moved on", from: locked.status)
2236 − end)
2230 + |> put_status_reason(reason || why)
2231 + |> require_status_from(from, locked.status)
2237 2232 |> Repo.update()
2238 2233 end)
2239 2234
@@ -2257,6 +2252,17 @@ defmodule GitGud.Workflows do
2257 2252 end
2258 2253 end
2259 2254
2255 + defp put_status_reason(cs, nil), do: cs
2256 + defp put_status_reason(cs, reason), do: Ecto.Changeset.change(cs, status_reason: reason)
2257 +
2258 + defp require_status_from(cs, nil, _current), do: cs
2259 +
2260 + defp require_status_from(cs, from, current) do
2261 + if current in from,
2262 + do: cs,
2263 + else: Ecto.Changeset.add_error(cs, :status, "has moved on", from: current)
2264 + end
2265 +
2260 2266 # ── concurrency: jobs ────────────────────────────────────────────────
2261 2267
2262 2268 # The `status_reason` of a job cancelled by a newer one in its group.
@@ -2358,40 +2364,43 @@ defmodule GitGud.Workflows do
2358 2364 )
2359 2365
2360 2366 {:ok, group, cancel?} ->
2361 − {:ok, row} =
2362 − Repo.transaction(fn ->
2363 − Concurrency.lock!(run.repository_id)
2364 − members = job_group_members(run.repository_id, group, job.id)
2365 − wait? = not cancel? and Enum.any?(members, &(&1.status in ["queued", "running"]))
2366 −
2367 − row =
2368 − put_job(
2369 − job,
2370 − changes ++
2371 − [
2372 − status: if(wait?, do: "waiting", else: "queued"),
2373 − concurrency_group: group,
2374 − cancel_in_progress: cancel?,
2375 − status_reason:
2376 − if(wait?, do: "Waiting for " <> group, else: next.status_reason)
2377 − ]
2378 − )
2367 + join_job_group(job, next, run, changes, group, cancel?)
2368 + end
2369 + end
2379 2370
2380 − superseded =
2381 − if cancel?, do: members, else: Enum.filter(members, &(&1.status == "waiting"))
2371 + defp join_job_group(job, next, run, changes, group, cancel?) do
2372 + {:ok, row} =
2373 + Repo.transaction(fn ->
2374 + Concurrency.lock!(run.repository_id)
2375 + members = job_group_members(run.repository_id, group, job.id)
2376 + wait? = not cancel? and Enum.any?(members, &(&1.status in ["queued", "running"]))
2377 +
2378 + row =
2379 + put_job(
2380 + job,
2381 + changes ++
2382 + [
2383 + status: if(wait?, do: "waiting", else: "queued"),
2384 + concurrency_group: group,
2385 + cancel_in_progress: cancel?,
2386 + status_reason: if(wait?, do: "Waiting for " <> group, else: next.status_reason)
2387 + ]
2388 + )
2382 2389
2383 − for member <- superseded do
2384 − update_job_status(member, "cancelled",
2385 − reason: @superseded <> group,
2386 − fail_fast: false
2387 − )
2388 − end
2390 + superseded =
2391 + if cancel?, do: members, else: Enum.filter(members, &(&1.status == "waiting"))
2389 2392
2390 − row
2391 − end)
2393 + for member <- superseded do
2394 + update_job_status(member, "cancelled",
2395 + reason: @superseded <> group,
2396 + fail_fast: false
2397 + )
2398 + end
2392 2399
2393 2400 row
2394 − end
2401 + end)
2402 +
2403 + row
2395 2404 end
2396 2405
2397 2406 defp put_job(%WorkflowJob{} = job, fields) do
@@ -3004,22 +3013,36 @@ defmodule GitGud.Workflows do
3004 3013 defp superseded_by_group?(%WorkflowJob{status_reason: @run_superseded <> _}), do: true
3005 3014 defp superseded_by_group?(job), do: superseded?(job)
3006 3015
3007 − defp job_status_description(%WorkflowJob{} = job) do
3008 − case job.status do
3009 − "queued" -> "Waiting to run"
3010 − "blocked" -> "Waiting for " <> Enum.join(job.needs, ", ")
3011 − "waiting" -> job.status_reason || "Waiting"
3012 − "awaiting_approval" -> "Waiting for approval"
3013 − "running" -> "Running"
3014 − "success" -> "Successful" <> took(job, " in ")
3015 − "failure" when is_nil(job.started_at) -> job.status_reason || "Failing"
3016 − "failure" -> "Failing" <> took(job, " after ")
3017 − "warning" -> warning_description(job)
3018 − "cancelled" -> job.status_reason || "Cancelled"
3019 − "skipped" -> "Skipped"
3020 − other -> other
3021 − end
3022 − end
3016 + defp job_status_description(%WorkflowJob{status: "queued"}), do: "Waiting to run"
3017 +
3018 + defp job_status_description(%WorkflowJob{status: "blocked"} = job),
3019 + do: "Waiting for " <> Enum.join(job.needs, ", ")
3020 +
3021 + defp job_status_description(%WorkflowJob{status: "waiting"} = job),
3022 + do: job.status_reason || "Waiting"
3023 +
3024 + defp job_status_description(%WorkflowJob{status: "awaiting_approval"}),
3025 + do: "Waiting for approval"
3026 +
3027 + defp job_status_description(%WorkflowJob{status: "running"}), do: "Running"
3028 +
3029 + defp job_status_description(%WorkflowJob{status: "success"} = job),
3030 + do: "Successful" <> took(job, " in ")
3031 +
3032 + defp job_status_description(%WorkflowJob{status: "failure", started_at: nil} = job),
3033 + do: job.status_reason || "Failing"
3034 +
3035 + defp job_status_description(%WorkflowJob{status: "failure"} = job),
3036 + do: "Failing" <> took(job, " after ")
3037 +
3038 + defp job_status_description(%WorkflowJob{status: "warning"} = job),
3039 + do: warning_description(job)
3040 +
3041 + defp job_status_description(%WorkflowJob{status: "cancelled"} = job),
3042 + do: job.status_reason || "Cancelled"
3043 +
3044 + defp job_status_description(%WorkflowJob{status: "skipped"}), do: "Skipped"
3045 + defp job_status_description(%WorkflowJob{status: other}), do: other
3023 3046
3024 3047 defp warning_description(%WorkflowJob{} = job) do
3025 3048 if continue_on_error?(job),
modified lib/git_gud/workflows/context.ex
+20 −8
@@ -71,11 +71,7 @@ defmodule GitGud.Workflows.Context do
71 71 # "Username required".
72 72 owner_id = repo.organization_id || repo.owner_id
73 73
74 − {actor, actor_id} =
75 − case run.triggered_by_id && Repo.get(User, run.triggered_by_id) do
76 − %User{handle: h, id: id} when is_binary(h) and h != "" -> {h, id}
77 − _ -> {owner, owner_id}
78 − end
74 + {actor, actor_id} = triggering_actor(run, owner, owner_id)
79 75
80 76 full_name = owner <> "/" <> repo.name
81 77
@@ -99,7 +95,7 @@ defmodule GitGud.Workflows.Context do
99 95 # expensive kind.
100 96 "ref_name" => Ref.name(run.head_ref),
101 97 "ref_type" => Ref.type(run.head_ref),
102 − "sha" => if(run.head_sha, do: Git.to_hex(run.head_sha), else: ""),
98 + "sha" => sha_hex(run.head_sha),
103 99 "repository" => full_name,
104 100 "repository_id" => Integer.to_string(repo.id),
105 101 "repository_owner" => owner,
@@ -111,14 +107,14 @@ defmodule GitGud.Workflows.Context do
111 107 # endpoints check against the token's run.
112 108 "run_id" => Integer.to_string(run.id),
113 109 "run_number" => maybe_int_to_string(run.number),
114 − "run_attempt" => maybe_int_to_string((job && job.attempt) || 1),
110 + "run_attempt" => maybe_int_to_string(job_attempt(job)),
115 111 "workflow_ref" => full_name <> "/" <> run.workflow_path <> "@" <> (run.head_ref || ""),
116 112 "server_url" => base,
117 113 "api_url" => base <> "/api/v1",
118 114 # Forgejo sends it empty too: artifacts follow the instance's policy.
119 115 "retention_days" => "",
120 116 "workflow" => run.workflow_name || run.workflow_path,
121 − "job" => (job && job.job_id) || "",
117 + "job" => job_key(job),
122 118 # `gitea_default_actions_url` tells the runner where to clone
123 119 # `uses: actions/<repo>@<ref>` from. If we point at our own
124 120 # instance, the runner expects `actions/checkout` etc. to be
@@ -134,6 +130,22 @@ defmodule GitGud.Workflows.Context do
134 130 |> Map.merge(pull_request_context(run))
135 131 end
136 132
133 + defp triggering_actor(run, owner, owner_id) do
134 + case run.triggered_by_id && Repo.get(User, run.triggered_by_id) do
135 + %User{handle: h, id: id} when is_binary(h) and h != "" -> {h, id}
136 + _ -> {owner, owner_id}
137 + end
138 + end
139 +
140 + defp sha_hex(nil), do: ""
141 + defp sha_hex(sha), do: Git.to_hex(sha)
142 +
143 + defp job_attempt(nil), do: 1
144 + defp job_attempt(job), do: job.attempt || 1
145 +
146 + defp job_key(nil), do: ""
147 + defp job_key(job), do: job.job_id || ""
148 +
137 149 # What GitHub gives a `pull_request` job. `ref` is already the run's
138 150 # `head_ref` (`refs/pull/<N>/head`); its short name is `<N>/head`, and
139 151 # `ref_type` is empty, as on GitHub — a PR ref is neither a branch nor a
modified lib/git_gud/workflows/runner_jwt.ex
+19 −15

Click to load diff…

modified lib/git_gud_web/components/audit_components.ex
+2 −0

Click to load diff…

modified lib/git_gud_web/controllers/actions_cache_controller.ex
+16 −22

Click to load diff…

modified lib/git_gud_web/controllers/ap_actor_controller.ex
+25 −23

Click to load diff…

modified lib/git_gud_web/controllers/api/mobile/ci_controller.ex
+36 −38

Click to load diff…

modified lib/git_gud_web/controllers/package_registry_controller.ex
+22 −27

Click to load diff…

modified lib/git_gud_web/controllers/packages_webhook_controller.ex
+3 −2

Click to load diff…

modified lib/git_gud_web/live/audit_live/filters.ex
+11 −8

Click to load diff…

modified lib/git_gud_web/live/repo_live/new.ex
+12 −14

Click to load diff…

Parents: 591e34a