https://github.com/paladini/backup-github-to-gitlab
Mirror all your GitHub repositories to GitLab automatically — private stays private, public stays public.
https://github.com/paladini/backup-github-to-gitlab
backup backup-script executable github github-to-gitlab gitlab migration package python repo-sync repository-migration sync user-backup user-repo
Last synced: 1 day ago
JSON representation
Mirror all your GitHub repositories to GitLab automatically — private stays private, public stays public.
- Host: GitHub
- URL: https://github.com/paladini/backup-github-to-gitlab
- Owner: paladini
- License: mit
- Created: 2026-06-12T10:04:54.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-06-19T20:20:06.000Z (19 days ago)
- Last Synced: 2026-06-19T21:25:25.822Z (19 days ago)
- Topics: backup, backup-script, executable, github, github-to-gitlab, gitlab, migration, package, python, repo-sync, repository-migration, sync, user-backup, user-repo
- Language: Python
- Homepage: https://paladini.github.io/backup-github-to-gitlab/
- Size: 50.8 KB
- Stars: 3
- Watchers: 0
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# backup-github-to-gitlab
> Mirror all your GitHub repositories to GitLab automatically — private stays private, public stays public.
[](https://python.org)
[](LICENSE)
---
## Why
Having all your code on a single platform is a risk. This tool clones every repository from your GitHub account and creates matching repositories on GitLab, preserving visibility exactly. Run it once for a full backup, run it again anytime to sync incrementally.
## Features
- **Visibility-safe** — private repos are *never* created as public; defaults to private when in doubt
- **Idempotent** — safe to re-run; existing GitLab repos receive incremental pushes, not duplicates
- **Full mirror** — clones all branches and tags via `git clone --mirror`
- **Dry-run mode** — preview every action before executing a single write
- **Selective backup** — filter by glob pattern (`--filter "myproject-*"`)
- **Works on Windows** — handles read-only `.git` files; tested on Windows 10 / PowerShell
- **Rich terminal output** — progress bar and per-repo status table
## Prerequisites
- Python 3.11+
- `git` in your PATH
- SSH key registered on both GitHub and GitLab
- [GitHub token](#github-token) with `repo` scope
- [GitLab token](#gitlab-token) with `api` scope
## Quick Start
```bash
git clone git@github.com:paladini/backup-github-to-gitlab.git
cd backup-github-to-gitlab
pip install -r requirements.txt
cp config.example.yaml config.yaml # then edit: set github.username and gitlab.username
cp .env.example .env # then edit: set GITHUB_TOKEN and GITLAB_TOKEN
python backup.py --dry-run # preview — no changes made
python backup.py # run the backup
```
## Token Setup
### GitHub token
1. Go to **Settings → Developer settings → Personal access tokens → Tokens (classic)**
2. Click **Generate new token (classic)**
3. Select scope: `repo` (required to list private repositories)
4. Paste the token into `.env` as `GITHUB_TOKEN`
### GitLab token
1. Go to **User Settings → Access Tokens → Add new token**
2. Select scope: `api` (required to create projects)
3. Paste the token into `.env` as `GITLAB_TOKEN`
## Configuration
**`config.yaml`** — copy from `config.example.yaml`:
```yaml
github:
username: your-github-username
gitlab:
username: your-gitlab-username # can differ from GitHub
url: https://gitlab.com # change only for self-hosted GitLab
backup:
include_forks: false # include forked repositories? (default: false)
include_archived: true # include archived repositories? (default: true)
temp_dir: ./tmp # temporary dir for clones — auto-cleaned after each repo
```
**`.env`** — copy from `.env.example`:
```
GITHUB_TOKEN=ghp_...
GITLAB_TOKEN=glpat-...
```
Tokens are loaded at runtime and never logged. `.env` is in `.gitignore`.
## Usage
```bash
# Back up all personal repositories
python backup.py
# Preview what would happen — no writes
python backup.py --dry-run
# Back up only repos matching a pattern
python backup.py --filter "myproject-*"
# Include forks (skipped by default)
python backup.py --include-forks
# Verbose: show raw git output (useful for debugging SSH issues)
python backup.py --verbose
# Use a different config file
python backup.py --config /path/to/config.yaml
```
### Example output
```
DRY RUN MODE — no changes will be made
my-private-repo ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3/10
Repository Status Details
my-private-repo ~ dry run would create as private
my-public-site ~ dry run would create as public
archived-experiment ~ dry run would create as private
10 dry run
[DRY RUN] Nenhuma operação foi executada.
```
## Security
Private repositories on GitHub are **always** created as private on GitLab. Visibility is derived exclusively from the GitHub API response — there is no code path that can promote a private repository to public. If visibility cannot be determined, the tool defaults to private and logs a warning.
SSH keys are used for all git operations. API tokens are read from environment variables (`.env`), never from the config file, and never written to logs.
## Known Limitations
| Limitation | Details |
|---|---|
| Git LFS | LFS objects are not transferred (`git clone --mirror` skips them) |
| Organization repos | Not included in v1; planned for v2 with `--include-orgs` |
| GitLab pull mirroring | Requires GitLab Premium for private repos; a GitHub Actions alternative is planned for v2 |
## Roadmap
- [x] **v1** — Full backup: clone + push via SSH, idempotent, dry-run, selective filter
- [ ] **v2** — Auto-sync: GitLab pull mirror (Premium) or GitHub Actions push mirror
- [ ] **v2** — Organization repos with explicit opt-in
- [ ] **v3** — Wiki mirroring
## Contributing
Issues and pull requests are welcome. Please open an issue first to discuss what you'd like to change.
## License
MIT — see [LICENSE](LICENSE).