- Python 98.8%
- Jinja 1.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
A source repo with a unit disabled (e.g. Releases off) answers that unit's API with 404, which made Forgejo's migration abort with "The target couldn't be found". forgejo-migrate now requests issues/PRs from the repo flags and probes /releases, so it only asks for units that exist. Also fix api_request crashing on empty success bodies (HTTP 204 from a DELETE) via json.loads on an empty string. |
||
| .forgejo/workflows | ||
| src/codeberg_migrate | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| renovate.json | ||
| tox.ini | ||
| uv.lock | ||
codeberg-migrate
Migrate repositories between forges. Two CLIs ship in this package:
codeberg-migrate— GitHub → Codebergforgejo-migrate— Codeberg → a self-hosted Forgejo
Both share the same migrate / notice / archive / all subcommands and
per-repo flow. For each repo:
- Migrates to the destination via the Gitea/Forgejo API (code, issues, PRs, labels, milestones, releases, wiki)
- Prepends a migration notice to the source README (creates one if none exists)
- Archives the source repository
Install
pipx install -e .
cp .env.example .env
(Or pip install -e '.[dev]' for a development checkout.)
Edit .env with your details.
codeberg-migrate (GitHub → Codeberg)
| Variable | Description |
|---|---|
GITHUB_OWNER |
GitHub username to migrate from |
CODEBERG_OWNER |
Codeberg username to migrate to |
GITHUB_TOKEN |
GitHub personal access token (repo, read:org scopes) |
CODEBERG_TOKEN |
Codeberg API token — generate one here |
MIGRATION_NOTICE |
Notice template to use (optional, default: default) |
forgejo-migrate (Codeberg → Forgejo)
| Variable | Description |
|---|---|
CODEBERG_OWNER |
Codeberg username to migrate from |
CODEBERG_TOKEN |
Codeberg API token (needs write access for notice/archive) |
FORGEJO_URL |
Base URL of the target Forgejo, e.g. https://forgejo.damian.net |
FORGEJO_OWNER |
Destination username or org on Forgejo |
FORGEJO_TOKEN |
Forgejo API token |
MIGRATION_NOTICE |
Notice template to use (optional, default: default) |
The Forgejo token needs write:repository (and write:organization if the
destination owner is an org). forgejo-migrate copies each repo through
Forgejo's own migrate API with service: gitea, so full issue/PR history comes
across — this is the same thing the "New Migration" button does, but scripted
across every repo at once.
Usage
Both commands take the same subcommands and flags — examples use
codeberg-migrate; swap in forgejo-migrate for the Codeberg → Forgejo direction.
# Full migration (migrate + notice + archive), dry run first
codeberg-migrate all --dry-run
codeberg-migrate all
# Run steps independently
codeberg-migrate migrate
codeberg-migrate notice
codeberg-migrate archive
# Target a subset of repos
codeberg-migrate all --repos repo1 repo2 repo3
# Include forks and already-archived repos
codeberg-migrate all --include-forks --include-archived
Each subcommand accepts --dry-run, --repos, --include-forks, and --include-archived.
Migration notice templates
Templates ship inside the package (src/codeberg_migrate/templates/) and use
Jinja2 syntax.
Built-in templates:
| Name | Description |
|---|---|
default |
Multi-line notice with archive explanation |
short |
Single-line notice |
minimal |
Blockquote one-liner |
Variant suffixes (default-org, default-plain) are selected automatically
from the target README's extension (.org, .txt).
Available variables: {{ url }} (destination repo URL), {{ repo_name }},
{{ source }} (platform being left, e.g. GitHub or Codeberg), and
{{ target }} (the new home). {{ codeberg_url }} stays available as an alias
for {{ url }} so older custom templates keep working.
To use a custom template, set MIGRATION_NOTICE to a file path:
MIGRATION_NOTICE=/path/to/my-notice.j2
Development
tox -e lint # ruff check + format --check
tox -e typecheck # mypy (strict)
tox -e test # pytest
tox -e audit # pip-audit
License
GPLv3 — see LICENSE.