neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
Actions: on: schedule fires, from the default branch
ad37081 · Gabriel Morell · 2026-10-17 18:00
Message
{commit_body(@commit)}
Files changed
modified
README.md
+2
−0
| 91 | 91 | - **Triggers**: `push` (with `branches:` / `branches-ignore:` / `tags:` / `tags-ignore:`, GitHub's rule that naming one kind excludes the other kind of ref, and a list beating its `-ignore` if both are set), `pull_request` and `workflow_dispatch` |
| 92 | 92 | - **`on: pull_request`** for pull requests within one repository: `types:` `opened`, `synchronize` (the head moved) and `reopened` (the default; other activity types never fire). `branches:` / `branches-ignore:` filter the **target** branch, and `paths:` the PR's diff from its merge-base. Workflows are read from the PR's head commit, so a PR can change its own CI, and the job tests the head itself at `refs/pull/<N>/head` — not a merge with the target, as GitHub's `refs/pull/<N>/merge` does. The job gets `github.event_name`, `head_ref` and `base_ref` (the source and target branches), `ref_name` `<N>/head` and an empty `ref_type`. **Fork and federated pull requests don't run yet**: their head is code the target's maintainers haven't reviewed, and a job is handed the target repository's secrets and a token that can publish its packages; GitHub runs them without secrets and with a read-only token, and until that exists here they don't run at all. `pull_request_target` isn't supported |
| 93 | 93 | - **`on: workflow_dispatch`** from the Actions page ("Run workflow", for anyone who can write to the repository: pick a branch and fill in the inputs) or the Gitea/GitHub API (`POST /api/v1/repos/:owner/:name/actions/workflows/:file/dispatches`, a `ci:write` token or a job token). Declared `inputs:` are checked as GitHub checks them — an undeclared input, a missing `required` one, a `choice` outside its `options`, a `boolean` that isn't true/false or a `number` that isn't numeric answers 422 — and every declared input, defaults applied, reaches the job as `inputs.*` and `github.event.inputs.*` (strings) |
| 94 | + - **`on: schedule`** (`- cron:`, five-field cron in UTC): read from the **default branch's** latest commit and run there, whichever branch the workflow file was pushed to; `github.event.schedule` is the cron that fired. Crons that can fire more often than **every five minutes** (GitHub's minimum), `@reboot` and ones that don't parse are ignored with a warning in the log. Archived repositories don't run their schedules | |
| 95 | + - **`[skip ci]`**, `[ci skip]`, `[no ci]`, `[skip actions]` or `[actions skip]` in the head commit's message (any case), or a `skip-checks: true` trailer, starts no `push` or `pull_request` runs — the head commit only, as on GitHub; other events aren't skipped | |
| 94 | 96 | - **Filter patterns** in `branches:`, `tags:`, `paths:` and their `-ignore` lists follow GitHub's cheat sheet: `*` is any run of characters but `/`, `**` any run at all (`**/` also no directory), `?` zero or one and `+` one or more of the **preceding** character, `[a-z0-9]` one listed character (alphanumerics and `a-z`/`A-Z`/`0-9` ranges only), `\x` a literal `x`. In `branches:`, `tags:` and `paths:` the last matching pattern decides, so `!pattern` excludes and a later positive pattern includes again; a list with only `!` patterns matches nothing. `*` no longer crosses `/` in `branches:` / `tags:` (`branches: ['*']` doesn't run for `feature/x`), and `!` works there now. Branch protection and required-check patterns are separate: their `*` still crosses `/` |
| 95 | 97 | - **`paths:` / `paths-ignore:`**: a push runs if any changed file is selected by `paths:`, or if not every changed file is matched by `paths-ignore:`; both set → `paths:` wins, with a warning. Changed files are the ref's old → new diff (renames count as both paths); a new branch is diffed from where it left the default branch. Tag pushes and manual runs ignore path filters, and when the changed files can't be worked out (no default branch to compare with, an unknown base, a git error, more than 3000 files) the filter passes — a run is never skipped because the forge couldn't look |
| 96 | 98 | - **The job's context** follows Forgejo's: `github.event_name`, `ref`, `ref_name`, `ref_type`, `sha`, `repository` (`owner/name`), `repository_id`, `repository_owner(_id)`, `actor(_id)`, `run_id`, `run_number`, `run_attempt` (1, bumped by re-running the job; re-running the whole run makes a new run and a new `run_id`), `workflow`, `workflow_ref` (`owner/name/<path>@<ref>`), `job`, `server_url`, `api_url`, `token`. **`github.event`** is GitHub's webhook payload, trimmed: every event has `repository` and `sender`; `push` has `ref`, `before` (40 zeros for a new ref), `after`, `created`, `deleted`, `forced` (always `false`: not tracked), `compare`, `commits` (at most 20) and `head_commit`, `pusher`; `pull_request` has `action`, `number` and `pull_request` (`title`, `body`, `html_url`, `user`, `head`/`base` with `ref`, `sha`, `label`, `repo`); `workflow_dispatch` has `inputs`, `ref`, `workflow`. **`secrets.GITHUB_TOKEN`** (and `GITEA_TOKEN`, `FORGEJO_TOKEN`) is the job token. The workflow's top-level `env:`, `defaults:`, `permissions:`, `run-name:` and `on:` reach the runner with the job |
modified
config/config.exs
+3
−1
| 68 | 68 | # Requeue workflow_jobs stuck in `running` after a claim-then- |
| 69 | 69 | # can't-send (network blip, encoder bug, runner crash). Threshold |
| 70 | 70 | # is configurable via :stale_after_minutes; default 5 minutes. |
| 71 | − {"* * * * *", GitGud.Workflows.Workers.StaleJobReaper} | |
| 71 | + {"* * * * *", GitGud.Workflows.Workers.StaleJobReaper}, | |
| 72 | + # Fire the workflows' own `on: schedule` crons that are due. | |
| 73 | + {"* * * * *", GitGud.Workflows.Workers.ScheduleTick} | |
| 72 | 74 | ]} |
| 73 | 75 | ] |
| 74 | 76 |
modified
lib/git_gud/pull_requests.ex
+5
−0
| 705 | 705 | commit_sha |
| 706 | 706 | ) |
| 707 | 707 | |
| 708 | + # A merge moves the target without the post-receive hook, so | |
| 709 | + # nothing else re-reads the default branch's `on: schedule`. | |
| 710 | + if pr.target_ref == repo.default_branch, | |
| 711 | + do: {:ok, _count} = GitGud.Workflows.Schedules.sync(repo) | |
| 712 | + | |
| 708 | 713 | _ = GitGud.Federation.Publishing.publish_pr_merged(repo, updated) |
| 709 | 714 | {:ok, updated} |
| 710 | 715 | end |
modified
lib/git_gud/repositories.ex
+8
−0
| 1722 | 1722 | repo |
| 1723 | 1723 | |> Repository.update_changeset(attrs) |
| 1724 | 1724 | |> Repo.update() |
| 1725 | + |> tap(fn | |
| 1726 | + # `on: schedule` follows the default branch. | |
| 1727 | + {:ok, %Repository{default_branch: branch} = updated} when branch != repo.default_branch -> | |
| 1728 | + {:ok, _count} = GitGud.Workflows.Schedules.sync(updated) | |
| 1729 | + | |
| 1730 | + _ -> | |
| 1731 | + :ok | |
| 1732 | + end) | |
| 1725 | 1733 | |
| 1726 | 1734 | err -> |
| 1727 | 1735 | err |
modified
lib/git_gud/repositories/workers/commit_backfill.ex
+10
−0
| 43 | 43 | |
| 44 | 44 | if zero?(new_sha) do |
| 45 | 45 | Webhooks.notify(repo, "push", push_payload(repo, ref, old_sha, new_sha, :delete)) |
| 46 | + sync_schedules(repo, ref) | |
| 46 | 47 | :ok |
| 47 | 48 | else |
| 48 | 49 | :ok = walk_and_cache(repo, old_sha, new_sha) |
| 49 | 50 | Webhooks.notify(repo, "push", push_payload(repo, ref, old_sha, new_sha, :update)) |
| 51 | + sync_schedules(repo, ref) | |
| 50 | 52 | if dispatch?, do: dispatch_workflows(repo, ref, old_sha, new_sha, pusher_id) |
| 51 | 53 | publish_push(repo, ref, old_sha, new_sha) |
| 52 | 54 | refresh_cross_repo_prs(repo, ref, new_sha, pusher_id, dispatch?) |
| 56 | 58 | end |
| 57 | 59 | end |
| 58 | 60 | |
| 61 | + # `on: schedule` is read from the default branch, so any change to it — | |
| 62 | + # a job-token push and a `[skip ci]` one included, since neither changes | |
| 63 | + # what the file says — re-reads the crons. | |
| 64 | + defp sync_schedules(%Repository{default_branch: branch} = repo, "refs/heads/" <> branch), | |
| 65 | + do: {:ok, _count} = Workflows.Schedules.sync(repo) | |
| 66 | + | |
| 67 | + defp sync_schedules(_repo, _ref), do: :ok | |
| 68 | + | |
| 59 | 69 | defp enqueue_language_stats(repo, "refs/heads/" <> _branch, new_sha) do |
| 60 | 70 | _ = |
| 61 | 71 | GitGud.Repositories.Workers.LanguageStats.new_job(%{ |
modified
lib/git_gud/workflows.ex
+43
−0
Click to load diff…
modified
lib/git_gud/workflows/event_payload.ex
+9
−0
Click to load diff…
added
lib/git_gud/workflows/schedules.ex
+200
−0
Click to load diff…
added
lib/git_gud/workflows/workers/schedule_tick.ex
+30
−0
Click to load diff…
added
lib/git_gud/workflows/workflow_schedule.ex
+21
−0
Click to load diff…
added
priv/repo/migrations/20261017175632_create_workflow_schedules.exs
+16
−0
Click to load diff…
added
test/git_gud/workflows/schedule_test.exs
+159
−0
Click to load diff…
Parents: 9b97d32