One-shot migration of a user or org (and all their repos) from git.0010.ca (Gitea) to git.gnu.fun (Forgejo)
Find a file
raphael 643bce3655 forge-people.sh: post-migration collaborators, org teams + members copy
Run after forge-migrator.sh. Per repo: re-adds collaborators with their
real permission (admin->owner mapping; per-collaborator lookup since the
listing carries no permission data). Per org: re-creates teams using
units_map (the team listing's permission field is unreliable), adds
members; Owners team untouched.

Graceful failover: users missing on the destination are skipped and
collected in a summary; one missing user never blocks the rest.
Re-run after creating them picks them up.

Live-tested both directions incl. idempotent re-run: added twotall
(admin->owner) + herman, skipped matt-daemon (missing on dest) as
collaborator AND team member, created chaos-monkeys with write units.
2026-10-09 09:43:14 -07:00
env.example env-file support: -E flag, ./.env auto-load, env.example template 2026-10-09 09:24:57 -07:00
forge-migrator.sh env-file support: -E flag, ./.env auto-load, env.example template 2026-10-09 09:24:57 -07:00
forge-people.sh forge-people.sh: post-migration collaborators, org teams + members copy 2026-10-09 09:43:14 -07:00
LICENSE license: WTFPL (replaces MIT stub; skipped AGPL per Matt) 2026-10-09 09:23:24 -07:00
README.md forge-people.sh: post-migration collaborators, org teams + members copy 2026-10-09 09:43:14 -07:00

forge-migrator

Migrates one user or org and all its repositories from one forge to another — any Gitea/Forgejo pair works, in either direction. Default: git.0010.ca → git.gnu.fun.

Tested live, both directions, on Gitea 1.27.3 and Forgejo 15.0.9.

Each repo arrives with all branches, tags, issues, labels, milestones, pull requests, releases, wiki, and LFS objects. Private stays private.

Requirements

You must own what you migrate. The script verifies everything below before touching anything.

Requirement Details
bash, curl, git, jq on the machine you run it from (apt install jq git curl)
SSH key registered on both forges under your own user. SSH users differ: gitea@git.0010.ca vs git@git.gnu.fun
Source token API token with your user's full permissions — git.0010.ca → Settings → Applications, select all read/write scopes
Destination token same, on git.gnu.fun — all 8 categories read+write, repo access: all
Org migration only org-creation permission on the destination (the script creates the org for you) — or the org already exists there with you as an owner
Same username both tokens and the SSH key must resolve to the same user on both forges

Put tokens in an env file (see env.example) — not on the command line.

Usage

cp env.example .env    # edit: fill in SRC_TOKEN and DST_TOKEN
./forge-migrator.sh --dry-run   # preview: what would move, name conflicts
./forge-migrator.sh             # the real run
./forge-people.sh               # after: collaborators, org teams + members

The script shows a numbered menu — 0 = your user, 1..N = orgs you fully own — you pick one. It lists the repos, you type migrate, it copies each repo. Already-migrated repos are skipped, so re-running after a partial failure is safe.

Env file can also set the servers (any Gitea/Forgejo pair, either direction): SRC_URL, DST_URL, SRC_SSH_HOST, DST_SSH_HOST.

What is migrated

  • full git history: all branches and tags
  • issues (open and closed), labels, milestones
  • pull requests
  • releases
  • wiki
  • Git LFS objects
  • repo visibility and description

What is NOT migrated

  • SSH deploy keys, webhooks, push mirrors
  • packages (container/debian/generic/helm — those have their own upload API)
  • stars, watches, notifications

Collaborators, org teams and members — run ./forge-people.sh after the migration. It copies collaborators per repo and re-creates org teams with their members. Users who don't exist on the destination are skipped gracefully and listed at the end; it never aborts on them. Create the missing users there and re-run to pick them up.

Troubleshooting

Symptom Fix
SSH key not accepted Register your public key on that forge (Settings → SSH / GPG Keys). Old forge SSH user is gitea@, not git@.
token usernames differ / Hi there, <someone-else>! Both tokens and the SSH key must belong to the same user on both forges.
could not create org '<org>' (HTTP 403) Your destination account lacks org-creation permission (some forges restrict it to admins). Have the org created there and yourself added to its Owners team, then re-run.
Repo fails with HTTP 422 Authentication failed: Clone Source token lacks read access to that repo — re-create it with full permissions.
Repo fails with HTTP 504 / timeout Big repo hit the server-side migrate timeout. Delete the partial copy on the destination and re-run.

Security notes

  • Tokens live in your env file or environment, never on the command line.
  • The source token is sent to the destination forge as the migrate auth_token — that's how it pulls your private repos. Only do this with a destination you trust.
  • Delete temporary tokens when done.

License

WTFPL — see LICENSE.