Skip to content

Instantly share code, notes, and snippets.

@jeviet
Last active July 20, 2026 15:31
Show Gist options
  • Select an option

  • Save jeviet/b8d57d2f9112f77accef220561be26cd to your computer and use it in GitHub Desktop.

Select an option

Save jeviet/b8d57d2f9112f77accef220561be26cd to your computer and use it in GitHub Desktop.
Migration from GitLab to GitHub

Self-Service Migration from GitLab to GitHub

Current Status

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.

How the migration works

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:

  1. Export - The CLI calls the GitLab project‑export API to produce a .tar.gz archive containing the Git bundle plus project metadata (issues, merge requests, labels, milestones, releases, and so on) as NDJSON.
  2. Stage locally - The archive is downloaded to the machine where you run the command and will be deleted after a successful upload unless --keep-archive is specified.
  3. Upload to blob storage - The archive is uploaded to storage that GitHub can read from - either GitHubowned storage or a storage account you own in AWS S3 or Azure Blob Storage.
  4. 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.

Limits

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).

Migration archive size

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)

File, commit, and push limits (GitHub platform)

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.

Prerequisites

Install the GitHub CLI and the gl2gh extension

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

Create the two personal access tokens (PATs)

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.

Set your environment variables

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.

Decide where archives will be staged (blob storage)

Option How to select it Notes
GitHubowned 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.)

Migrating a repository

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.

After the migration: validation and mannequins

Check the migration log

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.

Reclaim mannequins (restore authorship)

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.

What is and isn't migrated

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.yml has 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment