- Shell 100%
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. |
||
|---|---|---|
| env.example | ||
| forge-migrator.sh | ||
| forge-people.sh | ||
| LICENSE | ||
| README.md | ||
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.