Bulk-migrate repositories from GitLab to GitHub: clone, create, and push, all driven by a simple CSV list instead of clicking through GitHub's importer one repo at a time.
It automatically installs git and GitHub CLI for you as long as you run it with elevated privileges. It just needs the usernames, tokens, and repository links/names.
Built originally for me and a few friends to move our projects from GitLab to GitHub, but usable by anyone with the same need. Includes a Bash script for Linux and a PowerShell script for Windows.
Create a repos.csv file in the project root listing every repository you want to migrate.
OLD_REPO,NEW_REPO
https://gitlab.com/your-username/project-one.git,project-one
https://gitlab.com/your-group/subgroup/project-two.git,new-project-two| Column | Meaning |
|---|---|
OLD_REPO |
Full HTTPS clone URL from GitLab, under Clone > Clone with HTTPS (e.g. https://gitlab.com/user/repo.git) |
NEW_REPO |
Name for the new GitHub repo. Created under the account tied to your GitHub token, so do not include a username or org prefix |
Rules:
- First line must be the header
OLD_REPO,NEW_REPO(the script skips it). - One repo per line, comma separated, no spaces around the comma or trailing whitespace.
NEW_REPOmust be a valid GitHub name using letters, numbers, hyphens, underscores, and periods.- GitLab allows nested subgroups (
kdg-ti/integration-2.1/24-25/team-12/scanner.git), but GitHub doesn't. That's whyNEW_REPOexists: always give a flat name. - Save with Unix (LF) line endings, not Windows (CRLF). See the fix below. This mainly affects the Linux/Bash script; PowerShell's CSV parser handles CRLF fine.
If repos.csv was created or edited on Windows (Notepad, Excel, Git Bash's default editor), it may carry CRLF endings, leaving a stray \r on the last field of each line, which then gets baked into the repo name.
Check for it:
file repos.csvIf it reports with CRLF line terminators, fix it with:
dos2unix repos.csv
# or, if dos2unix isn't installed:
sed -i 's/\r$//' repos.csvInstall dos2unix with sudo apt install dos2unix (Debian/Ubuntu) if you would rather use that.
Copy the template for your platform and fill in the values:
cp .bash-example.env .env # LinuxCopy-Item .ps1-example.env .env # Windows| Variable | Where to get it |
|---|---|
GITLAB_USERNAME |
Your GitLab username |
GITLAB_TOKEN |
Avatar > Edit profile > Access Tokens > New token, scopes read_repository and write_repository, then Create |
GITHUB_USERNAME |
Your GitHub username |
GITHUB_TOKEN |
GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic) > Generate new token, scope repo, then Generate |
Copy both tokens immediately, since neither platform shows them again. .env is gitignored; never commit it, and rotate the tokens if it ever leaks.
Format differs by platform, so do not mix them up:
- Bash (
.bash-example.env):export GITLAB_USERNAME=... - PowerShell (
.ps1-example.env): plainGITLAB_USERNAME=..., with noexport, no$env:prefix, and no quotes needed
Both scripts log into GitHub CLI automatically using GITHUB_TOKEN from .env. You don't need to run gh auth login yourself.
chmod +x gitlab2github.sh
sudo ./gitlab2github.sh- Open PowerShell as Administrator.
- Allow powershell scripts to run on your system:
set-executionpolicy remotesigned - Run it:
.\gitlab2github.ps1
While cloning and pushing, you'll see red, error-formatted lines like git : Cloning into 'repo'... or Everything up-to-date. These are git and gh writing normal progress output to stderr, not failures. The script checks each step's real exit code internally and only reports "Failed to clone/push, skipping" if something actually went wrong. As long as the loop continues to the next repo, or finishes cleanly, the migration succeeded.