GitHub → GitLab
Move repositories and collaboration history from GitHub to GitLab with explicit handling for identities, LFS, attachments, CI/CD, protections, integrations, secrets, package registries, cutover, and mirror-based rollback.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
GitHub
- The largest developer ecosystem combines collaboration, automation, and public discovery
- Repository-centered workflows keep code, review, and automation close together
- Enterprise planning and governance can require several overlapping GitHub products
- Advanced governance and automation can become configuration-heavy
GitLab
- A comprehensive DevSecOps platform offers deep CI/CD and self-hosting options
- Repository-centered workflows keep code, review, and automation close together
- Breadth, upgrades, and administration can be heavy for smaller teams
- Advanced governance and automation can become configuration-heavy
GitLab: A comprehensive DevSecOps platform offers deep CI/CD and self-hosting options. This removes a major source-side concern: Enterprise planning and governance can require several overlapping GitHub products.
What you lose: The largest developer ecosystem combines collaboration, automation, and public discovery. What you inherit: Breadth, upgrades, and administration can be heavy for smaller teams.
Jump to a section
Know the shape of the move.
This timeline assumes
- Editorial planning scale, not a GitHub or GitLab product limit: one GitHub.com organization with at most 100 repositories and 500 users; repositories near or above GitLab's documented approximate 30,000-comment threshold require the alternative comments option.
- You control organization settings, GitLab top-level groups, DNS, CI credentials, registries, OAuth applications, and production deployment targets.
- Every repository receives a mirror backup and its wiki, LFS objects, releases, packages, secrets, environments, and integrations are inventoried separately.
- GitLab groups and users are prepared before import; after import, GitLab's current post-migration contribution and membership mapping is completed, and any unmapped contribution is accepted only with an explicit attribution exception.
- Each repository is frozen before its production importer run and remains locked; production deploys are also frozen while the final scoped Git mirror, CI switch, and webhook cutover occur.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Approved user branches, tags, fork branches, commits, and revision history | clean | critical | Git parity is clean for approved user branches and tags plus fork branches required by imported pull requests when their tips and objects are explicitly reconciled. | Record the approved ref scope, run fsck, compare those ref tips and commit graphs, and handle submodules separately. |
| Provider-owned and synthetic refs | manual | medium | GitHub-owned pull-request, merge, notes, and other synthetic refs are not ordinary user branches and should not be treated as required one-for-one Git parity. | Classify refs by namespace, preserve a mirror backup, and verify corresponding imported pull-request metadata instead of publishing provider refs. |
| Issues and pull requests | partial | critical | The GitLab importer moves issues and pull requests into GitLab concepts, but state, review semantics, references, and edge cases are not identical. | Pilot repositories with complex reviews, cross-links, draft PRs, and merged branches. |
| Comments and review comments | partial | high | Large projects with roughly 30,000 or more comments can exceed the recommended importer path; attribution also depends on user mapping. | Use GitLab's documented alternate method for large projects and reconcile counts by type. |
| Markdown attachments | partial | high | Attachments require the relevant importer option; otherwise links in migrated Markdown can break. | Enable attachment import, scan migrated Markdown URLs, and preserve a source archive. |
| Git LFS objects | partial | critical | GitLab lists LFS objects as imported data, but its GitHub importer requires the documented token scope for repositories with LFS. A separate mirror backup also requires an explicit Git LFS fetch. | Use the required importer scope, fetch all LFS objects into the backup, compare object inventories, and download representative binaries. |
| Users and organization structure | manual | critical | The GitHub importer does not create GitLab groups from organizations and imports only supported direct collaborators; outside collaborators can be omitted. Current GitLab versions use post-migration contribution and membership mapping. | Create groups, subgroups, and users first; complete post-import contribution and membership mapping, then audit attribution and effective membership. |
| Branch protection and rulesets | partial | critical | Some branch protection can map, but GitHub rulesets, status-check requirements, bypass actors, and custom roles are not equivalent. | Rebuild protected branches, approval rules, push rules, CODEOWNERS, and merge checks from an approved policy matrix. |
| GitHub Actions workflows | manual | critical | Workflow YAML, runners, environments, approvals, OIDC trust, caches, and marketplace actions do not become GitLab CI behavior automatically. | Translate to .gitlab-ci.yml, replace actions, use GitLab variables and identities, and run release-path tests. |
| Secrets, variables, and environments | lost | critical | Secret values are intentionally not exportable and environment rules differ. | Create a secret inventory without values, rotate credentials, and enter them through approved GitLab secret controls. |
| Packages, releases, and artifacts | partial | high | Release metadata, binary assets, container images, packages, Actions artifacts, and caches have separate migration paths and retention rules. | Inventory each registry and artifact class, then copy and verify with native clients. |
| Wikis and GitHub Pages | partial | high | GitLab lists wikis as supported by the GitHub importer, but wiki content still requires count and content verification; Pages build and deployment settings are platform-specific. | Verify the imported wiki and keep a .wiki.git mirror as fallback; rebuild Pages publishing and domains in GitLab. |
| Webhooks, apps, bots, and integrations | lost | critical | Installation IDs, tokens, webhook secrets, checks, project boards, Dependabot, and external automation do not transfer as working integrations. | Reauthorize or replace each integration and test delivery before disabling GitHub. |
| Fork relationships | manual | high | GitLab documents importing fork branches associated with open pull requests, but the reviewed documentation does not promise recreation of the GitHub organization-wide fork network. | Document required fork topology and manually verify or rebuild upstream/downstream contribution flows. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| GitHub organization | GitLab top-level group | manual | Create group and subgroup hierarchy before project import. |
| Repository | GitLab project | transform | Run the GitHub importer and confirm visibility, default branch, namespace, path, optional stages, and final import status. |
| Approved user branches/tags and required fork branches | GitLab branches and tags | automatic | Compare the approved user-ref scope and classify provider-owned synthetic refs separately. |
| Pull request | Merge request | transform | GitLab imports pull requests and related review data, subject to documented mapping and known issues; reconcile states, references, reviewers, and discussions. |
| Issue and label | Issue and label | transform | GitLab lists these as imported, but identities, references, states, and partial imports still require reconciliation. |
| GitHub user and contribution | GitLab user, contribution, and membership | transform | Prepare destination users, then complete GitLab's current post-migration contribution and membership mapping and record every unresolved identity. |
| Branch protection/ruleset | Protected branch and approval/merge rules | manual | Recreate policy from intent; status checks are not imported as an equivalent rule. |
| .github/workflows/*.yml | .gitlab-ci.yml and CI includes | manual | Translate jobs, matrices, permissions, environments, caching, and release steps. |
| Actions secret | GitLab masked/protected CI/CD variable or external secret | manual | Rotate; secret values cannot be exported safely. |
| GitHub release and asset | GitLab release and package/generic asset | transform | Copy binaries and verify checksums. |
| GitHub wiki | GitLab wiki | transform | Use the importer-supported wiki path, verify content, and retain a .wiki.git mirror fallback. |
| GitHub App/webhook | No destination | unsupported | Install a GitLab-native integration or update the external service. |
Make the move recoverable.
Create the source-of-truth backup
Preserve every repository and the operational metadata required to reconstruct development and release workflows.
- Inventory repositories, visibility, archived/fork/template status, refs, LFS, submodules, wikis, Pages, issues, PRs, releases, packages, Actions, environments, rules, teams, apps, webhooks, deploy keys, and production dependencies.
- Create a --mirror clone of every repository and wiki, fetch all LFS objects, run git fsck, and record ref tips.
- Export organization/repository metadata and issue/PR counts through approved GitHub APIs or enterprise migration exports.
- Download required release assets and package/container artifacts with checksums.
- Record secret names and consumers without exporting values; plan rotation.
Proof to capture: A repository-by-repository manifest contains mirror hashes, ref and LFS counts, collaboration counts, assets, policies, integration owners, and restore instructions.
Identity and namespace map
Preserve attribution and least-privilege access.
- Map organization, teams, users, bots, externals, and emails.
- Create GitLab groups and users before import.
- After import, complete contribution and membership mapping and list every unresolved identity as a blocking exception.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
CI/CD translation
Rebuild tested pipelines rather than copying inert workflow files.
- Map triggers, jobs, services, permissions, caches, artifacts, environments, and approvals.
- Rotate variables and update OIDC/cloud trust.
- Run build, test, preview, production, and rollback paths.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Policy reconstruction
Recreate repository protection from its security intent.
- Inventory GitHub rulesets, protections, CODEOWNERS, status checks, and bypass actors.
- Map to protected branches, approvals, push rules, and merge checks.
- Test allowed and forbidden actions with role accounts.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Developers push to both hosts
New commits appear on GitHub after the final mirror starts.
- Consequence
- Histories diverge and releases use inconsistent source.
- Mitigation
- Freeze pushes, archive or lock GitHub, and advertise one canonical remote.
Stop if: Any uncaptured GitHub ref advances after the freeze.
CI reaches production with broader credentials
Translated jobs use long-lived or unprotected variables.
- Consequence
- Source or infrastructure compromise.
- Mitigation
- Rotate secrets, minimize job tokens, protect environments, and test untrusted merge requests.
Stop if: A non-protected pipeline can read a production credential.
Required merge controls disappear
A role account can merge without required approval or checks.
- Consequence
- Unreviewed code reaches protected branches.
- Mitigation
- Run explicit negative policy tests.
Stop if: Any forbidden push or merge succeeds.
LFS or release binaries are missing
Pointers resolve but binaries fail to download.
- Consequence
- Builds, releases, or historical checkouts break.
- Mitigation
- Compare object inventories and checksums.
Stop if: Any production-required binary is unavailable.
Importer rate limits or partial states are ignored
GitLab marks a project partial or reports failed relation types.
- Consequence
- The project looks available while metadata is incomplete.
- Mitigation
- Save status details, retry controlled failures, and reconcile all object counts.
Stop if: Any project remains partial without an accepted exception.
Do the work in this order.
- Days 1–4 · inventory
Inventory and target design
18–28 hours active4 days elapsedOwner and security review waiting- Inventory repositories, identities, collaboration data, policies, CI/CD, artifacts, and integrations.
- Create namespace, identity, retention, and freeze plans.
Depends on: GitHub organization owner and GitLab owner access
Stop / go checkpointApprove migration waves?
Go when: Every repository and operational dependency has an owner and method.
Stop when: A production pipeline, secret consumer, or compliance requirement is unknown.
- Days 5–7 · backup
Mirrors and evidence
12–20 hours active3 days elapsedClone, LFS, and artifact transfer waiting- Create and validate Git, wiki, LFS, asset, and metadata backups.
- Record source ref and object counts.
Depends on: Approved inventory
Stop / go checkpointCan every repository be restored?
Go when: Mirrors pass fsck and all critical binaries and metadata are accounted for.
Stop when: A required ref, LFS object, release asset, or wiki is absent.
- Days 8–11 · pilot
Pilot representative projects
14–22 hours active4 days elapsedImporter and CI runs waiting- Import simple, large-history, LFS, release, and complex-CI projects.
- Validate attribution, policies, pipeline permissions, artifacts, and integrations.
Depends on: GitLab groups and users, Backups
Stop / go checkpointApprove bulk import?
Go when: All critical project archetypes pass data, security, CI, and release checks.
Stop when: Attribution, protections, binaries, or production rollback cannot be proven.
- Days 12–20 · waves
Frozen production imports in waves
18–35 hours active5–9 days elapsedGitHub API limits and import queues waiting- Freeze each GitHub repository before its production importer run and keep it locked after the import starts.
- Import projects by dependency wave, complete post-import identity mapping, and reconcile metadata and importer status before advancing.
- Translate CI, policies, assets, and webhooks while the source repository remains locked.
Depends on: Approved pilot
Stop / go checkpointSchedule control-plane switch?
Go when: Every locked repository is complete or has an approved exception, its metadata ledger is reconciled, and release paths pass.
Stop when: Any source repository advances, importer partial state remains unexplained, or critical CI fails.
- Days 21–23 · cutover
Final reconciliation and control-plane switch
5–9 hours active1–3 days elapsedFinal CI and webhook runs waiting- Confirm every imported GitHub repository has remained locked since its production import.
- Reconcile any approved source metadata delta manually; do not rely on Git mirroring to update issues, pull requests, comments, reviews, releases, wiki content, or policy.
- Mirror the approved final user-ref scope and LFS, rotate secrets, switch webhooks/remotes, then run staged pipelines.
Depends on: Approved waves, Maintenance window
Stop / go checkpointMake GitLab canonical?
Go when: Approved refs match, the metadata-delta ledger is empty, policies reject forbidden actions, and production release/rollback pass.
Stop when: Any source repository advanced without reconciliation or production controls differ from policy.
- Days 24–30 · observe
Read-only observation
3–16 hours active5–7 days elapsedDeveloper feedback and scheduled jobs waiting- Keep GitHub read-only and monitor clones, webhooks, pipelines, releases, bots, and developer workflows.
- Complete verification and owner sign-off.
Depends on: Successful cutover
Stop / go checkpointClose rollback window?
Go when: Blocking checks pass across normal and scheduled workflows.
Stop when: A critical integration still writes to GitHub or a GitLab workflow is unsafe.
Cut over with a way back.
Cutover
Make GitLab the only writable source and CI/CD control plane while preserving a final verified GitHub snapshot.
- Confirm every GitHub repository was frozen before its production importer run and has remained locked.
- Reconcile any approved post-import GitHub metadata change manually; Git mirroring does not update pull requests or other imported metadata.
- Record the approved user-branch, tag, and required fork-branch scope; mirror those final Git refs and LFS and reconcile object counts while classifying provider-owned synthetic refs separately.
- Complete GitLab's post-migration contribution and membership mapping and resolve every blocking attribution exception.
- Rotate credentials and configure protected GitLab variables, deploy keys, runners, and environments.
- Switch remotes, webhooks, apps, registries, badges, documentation, and deployment integrations.
- Run protected-branch negative tests and staged build, test, release, deploy, and rollback pipelines.
- Keep GitHub repositories locked or archived and show the canonical GitLab URL.
Proof to capture: Final refs match, policy tests pass, all critical binaries resolve, and one complete production release and rollback simulation succeeds from GitLab.
Rollback
Restore GitHub as canonical without discarding GitLab commits or security changes.
- Freeze GitLab pushes, pipelines, releases, and package publication.
- Mirror new GitLab refs and LFS objects into a quarantine backup.
- Review and push approved new refs back to GitHub.
- Restore rotated GitHub secrets, integrations, branch rules, remotes, and deployment controls.
- Run GitHub policy and release-path tests before reopening writes.
Proof to capture: GitHub contains all approved refs, rejects forbidden actions, and completes the tested release path while GitLab is read-only.
- Missing refs, LFS objects, or critical assets
- Required policy bypass is possible
- Production pipeline or rollback fails
- Identity or permission mapping exposes restricted projects
- Critical integration cannot operate against GitLab
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Approved refs and objects | Compare approved user branch and tag tips plus required fork-branch tips, classify provider-owned synthetic refs separately, run fsck, and sample historical checkouts. | Exact parity for the approved ref scope and no corrupt or missing required object. | Scoped ref diff, namespace classification, and fsck logs. | |
V-02Blocking | Large-file availability | Compare LFS inventories and download production-critical objects. | Every required pointer resolves to the correct checksum. | LFS object report. | |
V-03Blocking | Issues, merge requests, comments, and attribution | Reconcile counts and inspect representative complex histories. | Every object is imported or listed as an approved exception; authorship is explained. | Object-count reconciliation. | |
V-04Blocking | Membership and visibility | Test internal, external, bot, and unauthorized role accounts. | Least-privilege access matches the approved matrix. | Role test matrix. | |
V-05Blocking | Protected workflow | Attempt forbidden push, merge, approval, and secret-access scenarios. | Every forbidden action fails and required checks block correctly. | Negative-test log. | |
V-06Blocking | Release path | Run build, test, artifact, preview, protected deploy, and rollback scenarios. | Outputs and approvals match the accepted pipeline specification. | Pipeline URLs and deployment evidence. | |
V-07Blocking | Releases, packages, containers, and wiki | Reconcile inventories and verify representative checksums and clones. | Every retained artifact class is usable from its destination. | Artifact manifest. | |
V-08Blocking | External events | Trigger each webhook, bot, scanner, notification, Pages build, and deployment integration. | One successful destination event and no production write to GitHub. | Integration delivery log. |
Remove the scaffolding safely.
Safe after: At least two normal release cycles, all scheduled workflows, and 30 days without a rollback trigger.
- Preserve mirrors, LFS, assets, manifests, importer logs, identity mappings, policy tests, and verification evidence.
- Revoke GitHub PATs, deploy keys, app installations, webhooks, runner tokens, and machine credentials no longer required.
- Delete temporary migration tokens and staging mirrors from unsecured machines.
- Retain GitHub repositories archived for the approved compliance window before any deletion decision.
- Update onboarding, incident response, backup, release, and ownership documentation.
- Schedule quarterly restore, access, policy, integration, and dormant-secret reviews.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Caio Andrade recounts Mercos moving its repositories and engineering workflow from GitHub to GitLab after evaluating price, stability, import tooling, CI, issue boards, dependency updates, and static analysis. The team treated source hosting as only one part of the move because GitHub-integrated services and GitHub Actions behavior also needed replacements. The author specifically recommends archiving source repositories to prevent writes during the cutover.
- Inventory CI, dependency automation, code analysis, issue tracking, and third-party integrations alongside the repositories themselves.
- Archive the GitHub repositories at cutover so no new commits or issues appear after the final import snapshot.
- The migration decision was driven by an integrated engineering platform rather than the mechanics of copying Git repositories.
- Pricing changed shortly after the move, reinforcing the value of documenting broader workflow reasons instead of relying on cost alone.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- GitLab Docs: migrate from GitHubAccessed 2026-07-27
- GitLab Docs: troubleshoot GitHub migrationAccessed 2026-07-27
- GitHub Docs: back up a repositoryAccessed 2026-07-27
- GitHub Docs: export organization migration dataAccessed 2026-07-27