feat(releases): mirror releases from Gitea and Forgejo sources (#446)

* feat(releases): mirror releases from Gitea and Forgejo sources (#440)

Release mirroring was GitHub only. The list-and-reconcile loop in gitea.ts
now works on a normalized release shape that a per-source lister fills, so
Gitea, Forgejo and Codeberg repositories mirror their releases and assets
the same way GitHub ones do.

- GiteaSourceProvider gains listReleases(owner, repo, limit), paginating
  with limit/page and stopping at x-total-count. listReleases is optional
  on the SourceProvider interface; GitLab does not implement it.
- mirrorGitHubReleasesToGitea keeps its signature and its behaviour, with
  the "Originally published on GitHub:" header line unchanged byte for
  byte so existing mirrored bodies are not PATCHed on the next sync.
  Gitea and Forgejo releases get the same shape with their own label.
- mirrorGiteaSourceReleasesToGitea is the new entry point. It honours the
  same release limit, asset limit, per-destination lock, tag-exists check,
  retention pruning and asset reconciliation.
- reconcileReleaseAssets takes the download Authorization from the caller
  instead of always sending the primary config's GitHub token: the GitHub
  path sends its token, a Gitea source sends that instance's token, and a
  tokenless public source sends no header at all. One host's token can no
  longer reach another host.
- mirrorReleases left GITHUB_ONLY_METADATA_KEYS and is gated on the new
  sourceKindSupportsReleases instead. Issues, pull requests, labels,
  milestones and the metadata master switch stay GitHub only, and GitLab
  still has releases forced off.
- The three call sites pick the path: a GitHub source with a client goes
  through Octokit, a Gitea source through its own API, anything else logs
  and is skipped.
- The release switch and its two limits follow the release-capable check
  in the settings UI, and the docs, the site and the capability tables say
  what is true now.

* test(releases): run the source aware release suite in an isolated child

Other test files replace the HTTP client and related modules with mock.module,
which is process-wide in bun and depends on file order. On CI the new suite
ran after one of them and every case failed on a mocked destination lookup.
Same harness as gitea-org-mirror-destination.test.ts.
This commit is contained in:
ARUNAVO RAY authored and GitHub committed 2026-09-23 01:00:20 +05:30
1 parent 6197352a8e
commit 7dd14dbb2e
20 files changed
+1314 -140

No files matched your search

+1 -1
View File
@@ -97,7 +97,7 @@ GITHUB_USERNAME=my-codeberg-user
GITHUB_TOKEN=...
```
Code, tags, wiki and LFS are mirrored from every source. Issues, pull requests, releases, labels, milestones and star lists need a GitHub source and are skipped for the others.
Code, tags, wiki and LFS are mirrored from every source, and releases with their assets from GitHub and Gitea/Forgejo sources. Issues, pull requests, labels, milestones and star lists need a GitHub source and are skipped for the others.
Once repositories have been imported the source is locked, and once anything has been mirrored the Gitea server URL is locked. A `SOURCE_PROVIDER`, `SOURCE_URL` or `GITEA_URL` value that disagrees with a locked host is ignored on boot with a warning; change the host on the Configuration page, where the change asks for confirmation.
+3 -3
View File
@@ -39,11 +39,11 @@ The mirror itself is done by Gitea's pull mirror, which treats every source as a
| Entire organizations (GitLab: top level groups) | yes | yes | yes |
| Add a single repository by URL | yes | yes | yes |
| Issues, pull requests, labels, milestones | yes | no | no |
| Releases with assets | yes | no | no |
| Releases with assets | yes | no | yes |
| Star lists | yes | no | no |
| Force push detection | yes | no | no |
The GitHub only rows read the GitHub API. For other sources the corresponding switches are disabled on the Configuration page, and the mirror step skips them.
The GitHub only rows read the GitHub API. For other sources the corresponding switches are disabled on the Configuration page, and the mirror step skips them. Releases are the exception: Gitea and Forgejo expose them at `GET /repos/{owner}/{repo}/releases`, so a Gitea, Forgejo or Codeberg source mirrors releases and their assets like GitHub does, with that instance's own token (public repositories need none).
Entire organizations also mirror without a token on every source; the next section covers public mode.
@@ -56,7 +56,7 @@ On the Organizations page, **Add organization** has a public mode, which is the
Public mode degrades in known ways:
- **GitHub metadata is best effort.** Anonymous requests run under GitHub's 60 requests per hour limit, so a large organization can end up with partial metadata. Releases, description and topics mirror anonymously with no account at all. Issues, pull requests, labels and milestones are also fetched anonymously once any source in the account carries a token; with no token anywhere they are skipped, and the mirror itself is unaffected either way.
- **GitLab and Gitea/Forgejo sources mirror code only.** Metadata remains GitHub only, as in the table above, and issues and merge requests from those sources stay a Not yet item with or without a token.
- **GitLab sources mirror code only; Gitea and Forgejo sources add releases.** The rest of the metadata remains GitHub only, as in the table above, and issues and merge requests from those sources stay a Not yet item with or without a token.
- **Personal auto discovery and starred repositories need a token.** Neither runs for a public only source.
- **Cleanup does not run.** Repositories deleted upstream are not detected for public only sources.