GitLab to GitHub migrations through gh gl2gh is not yet generally available (GA), so please set your expectations accordingly. It is being built and hardened as a first‑class, self‑serve migration path, but it is currently in private preview, so supported entities and limits may change. The tool supports non-EoL versions of GitLab products.
gh gl2gh is a thin, cross‑platform command‑line wrapper around the GitHub migration APIs. When you migrate a repository, the CLI performs the following sequence for you:
- Export - The CLI calls the GitLab project‑export API to produce a
.tar.gzarchive containing the Git bundle plus project metadata (issues, merge requests, labels, milestones, releases, and so on) as NDJSON. - Stage locally - The archive is downloaded to the machine where you run the command and will be deleted after a successful upload unless --
keep-archiveis specified. - Upload to blob storage - The archive is uploaded to storage that GitHub can read from - either GitHub‑owned storage or a storage account you own in AWS S3 or Azure Blob Storage.
- Import - GitHub imports the archive into the destination organization, transforming GitLab entities into their GitHub equivalents.
Understanding this flow explains most of the limits and prerequisites: you need credentials for both systems, a place to stage large archives, and enough local disk to hold the export.
The following limits come from GitHub's and Gitlab’s public documentation and apply to what can be imported and pushed into GitHub.com. Plan around them before you migrate, several failed migrations trace back to one of these.
As previously mentioned, only currently maintained GitLab versions are supported, older versions may work, but we will not prioritize any bug fixes on these versions as changes to GitLab are understandably out of our control.
NOTE: The units used below are listed as they appear in source documentation, so the mix of units is expected (GiB/GB).
| Limit | Value | Notes |
|---|---|---|
| Maximum Git source archive supported for import | 40 GiB | Use git-sizer to check total blob size. |
| GitLab export API archive ceiling | 40 GB | GitLab's own export limit on the source side (platform dependent) |
| Limit | Value | Behavior |
|---|---|---|
| Individual file during GEI import | 400 MiB | Elevated limits are only applied during import. Post-import, the individual file limit is 100MiB |
| Single push size | 2 GiB | Larger pushes fail (pack exceeds maximum allowed size). |
| Single commit size | 2 GiB | Platform limit. |
| Git reference name | 255 bytes | Platform limit. |
First install the official GitHub CLI. Then install the GitLab migration extension:
gh extension install github/gh-gl2gh
Keep it current - we ship updates frequently:
gh extension upgrade github/gh-gl2gh
Verify and explore:
gh gl2gh --help
gh gl2gh migrate-repo --help
You need one token for the source (GitLab) and one for the destination (GitHub).
GitLab PAT - requires the api and read_repository scopes. For self‑managed instances, the token generally must belong to an admin user so the export is complete and user attribution is preserved.
GitHub PAT (classic) - fine‑grained PATs are not supported for migrations. Scopes depend on your role in the destination organization. Refer to the official Managing access for GitHub Enterprise Importer guidance.
Provide the tokens through environment variables so they never appear in your shell history or in command arguments. The CLI reads these names:
# macOS / Linux
export GITLAB_PAT="your-gitlab-token"
export GH_PAT="your-github-token"
# Windows PowerShell
$env:GITLAB_PAT = "your-gitlab-token"
$env:GH_PAT = "your-github-token"
For migrations into GitHub Enterprise Cloud with data residency (a *.ghe.com subdomain) rather than github.com, you will also pass --target-api-url https://api.SUBDOMAIN.ghe.com on the migration commands.
| Option | How to select it | Notes |
|---|---|---|
| GitHub‑owned storage (recommended) | --use-github-storage |
No setup. Simplest path for most customers. Tune upload chunking with GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES (default 100 MiB, min 5 MiB) if you have a slow or proxied connection. |
| AWS S3 | --aws-bucket-name (+ AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional AWS_SESSION_TOKEN) |
You own the bucket and its lifecycle. |
| Azure Blob Storage | --azure-storage-connection-string (or AZURE_STORAGE_CONNECTION_STRING) |
Only storage‑account access‑key connection strings are supported (not SAS). |
Cost warning: GitHub does not delete archives from your AWS or Azure storage after a migration. Configure lifecycle rules on your bucket or container to control cost. (GitHub‑owned storage is cleaned up automatically after a successful migration or in seven days after a falied migration.)
The migrate-repo command exports the GitLab project, uploads it, starts the GitHub import.
gh gl2gh migrate-repo ``\
--gitlab-server-url https://gitlab.example.com ``\
--gitlab-group SOURCE-GROUP ``\
--gitlab-project SOURCE-PROJECT ``\
--github-org DESTINATION-ORG ``\
--github-repo NEW-REPO-NAME ``\
--use-github-storage
Key options (run gh gl2gh migrate-repo --help for the complete, version‑exact list):
| Option | Purpose |
|---|---|
--gitlab-server-url |
Full URL of the GitLab server. Accepts both https://gitlab.com and self‑managed instances (e.g. https://gitlab.mycompany.com). |
--gitlab-group |
The GitLab group / full namespace path containing the project. For nested subgroups use the full path, e.g. parent-group/subgroup. |
--gitlab-project |
The GitLab project to migrate. |
--github-org |
Destination GitHub organization. |
--github-repo |
Name for the repository on GitHub. |
--target-repo-visibility |
public, private, or internal. Defaults to private. |
--use-github-storage / --aws-bucket-name / --azure-storage-connection-string |
Selects the blob‑storage backend (see §3.4). |
--no-ssl-verify |
Disables SSL verification only when talking to your GitLab instance if you are using a self-signed cert. All other steps still verify SSL. |
--target-api-url |
Target API base URL when migrating to GHE.com rather than github.com. |
--archive-url |
URL location of a previously exported archive. |
--archive-path |
File location of a previously exported archive. |
--keep-archive |
Retain the archive instead of deleting after a successful upload. |
Every migration produces a log with any resource‑level warnings or errors. Download and review it:
gh gl2gh download-logs --migration-id <gql-id>
Then open the migrated repository on GitHub and spot‑check history, issues, pull requests, labels, milestones, and releases.
Contributions from GitLab users initially appear as mannequins - placeholder identities - because GitHub cannot know which GitHub account corresponds to each GitLab user. Map them to real accounts:
# 1. Export the mannequins for the org
gh gl2gh generate-mannequin-csv ``\
--github-org DESTINATION-ORG ``\
--output mannequins.csv
# 2. Fill in the target GitHub usernames in the CSV, then reclaim
gh gl2gh reclaim-mannequin ``\
--github-org DESTINATION-ORG ``\
--csv mannequins.csv
GitLab.com note: the quality of user attribution depends on what identity data GitLab includes in the export archive. Verify attribution on a sample repository before relying on it at scale.
The GitLab native project export defines the ceiling of what can possibly be migrated - if data isn't in the export archive, no tool can move it. Within that archive, the importer maps GitLab entities to their GitHub equivalents.
Migrated (when present in the export):
-
Git repository (code) and the wiki
-
Project configuration that maps cleanly (default branch, visibility, etc.)
-
Issues and issue comments; issue resource state / milestone events
-
Merge requests are converted to pull requests
-
Comments (only the latest diff is present in the export)
-
MR reviewers/approvers
-
MR state events
-
In Progress (Estimated Completion Date – July 10th, 2026):
-
Milestones
-
Timeline Events
-
Emoji reactions
Not migrated:
-
Git LFS objects (pointer files travel with history; the binary objects must be pushed separately as a follow‑up)
-
Repo policies includeing: merge-train, pipeline-gate, approvals-required, topics, avatar, and mirroring
-
Group
-
Group
-
Group membership
-
Snippets, issue boards, time‑tracking data, design‑management data
-
CI/CD pipelines and pipeline schedules (
.gitlab-ci.ymlhas no automatic GitHub Actions equivalent) -
Vulnerability reports
-
Items not included in the export at all: webhooks, CI/CD variables, job traces and artifacts, child‑pipeline history, pipeline triggers