Migrate GitHub repositories to Codeberg
  • Python 98.8%
  • Jinja 1.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Christof Damian a8662bbfcd
All checks were successful
CI / lint (push) Successful in 7s
CI / typecheck (push) Successful in 12s
CI / test (push) Successful in 10s
CI / release (push) Has been skipped
CI / audit (push) Successful in 12s
Gate forgejo-migrate units on source; tolerate empty API responses
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.
2026-07-23 16:25:50 +02:00
.forgejo/workflows Update non-major updates 2026-07-21 18:19:45 +00:00
src/codeberg_migrate Gate forgejo-migrate units on source; tolerate empty API responses 2026-07-23 16:25:50 +02:00
tests Gate forgejo-migrate units on source; tolerate empty API responses 2026-07-23 16:25:50 +02:00
.env.example Add forgejo-migrate CLI for Codeberg-to-Forgejo migration 2026-07-23 15:58:19 +02:00
.gitignore Modernize project layout: src/ package, hatchling, tox, CI 2026-06-13 18:51:48 +02:00
CHANGELOG.md Gate forgejo-migrate units on source; tolerate empty API responses 2026-07-23 16:25:50 +02:00
LICENSE Modernize project layout: src/ package, hatchling, tox, CI 2026-06-13 18:51:48 +02:00
pyproject.toml Add forgejo-migrate CLI for Codeberg-to-Forgejo migration 2026-07-23 15:58:19 +02:00
README.md Add forgejo-migrate CLI for Codeberg-to-Forgejo migration 2026-07-23 15:58:19 +02:00
renovate.json Add renovate.json 2026-06-12 14:41:10 +00:00
tox.ini Switch tox to tox-uv runner with committed uv.lock 2026-06-15 11:53:18 +02:00
uv.lock Update non-major updates 2026-07-21 18:19:45 +00:00

codeberg-migrate

Migrate repositories between forges. Two CLIs ship in this package:

  • codeberg-migrate — GitHub → Codeberg
  • forgejo-migrate — Codeberg → a self-hosted Forgejo

Both share the same migrate / notice / archive / all subcommands and per-repo flow. For each repo:

  1. Migrates to the destination via the Gitea/Forgejo API (code, issues, PRs, labels, milestones, releases, wiki)
  2. Prepends a migration notice to the source README (creates one if none exists)
  3. 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.