On October 16, 2026, Roku posted a short notice on its developer forum: the developer docs, sample apps and tooling now live at gitlab.com/rokudev. Almost every repo under github.com/rokudev was archived the night before. No reason was given, and when someone asked in the thread, nobody from Roku answered.
If you've been wondering whether you could do the same, the answer is yes, and the git part is the easy bit. Below is the full GitHub to GitLab migration I'd run today: the importer and its token quirks, issues and PRs, rewriting GitHub Actions as .gitlab-ci.yml, and a push mirror so your old GitHub links keep working. At the end there's a list of the things that don't come across at all.
Roku isn't alone, either. Zig left for Codeberg in late November 2025 and blamed GitHub Actions directly, and the Hacker News thread on Roku's move hit 288 points and 203 comments.
Git was never the thing locking you in
Moving a git repo takes one command. git push --mirror has worked since forever. If git were what held you to GitHub, everyone would have left years ago.
What holds you is everything wrapped around the repo:
- Actions. Hundreds of lines of YAML that call
uses: some-org/some-action@v3, and none of it runs anywhere else. - Issues and PRs. Years of review comments, links and history that live in GitHub's database. Your clone doesn't have them.
- Trust in the defaults. On March 25, 2026, GitHub announced that starting April 24, Copilot interaction data from Free, Pro and Pro+ users would be used to train its models unless the user opts out. That covers code snippets, the code around your cursor, file names, repository structure and navigation patterns. Business and Enterprise plans are excluded. Private repos "at rest" are excluded too, but code Copilot processes during a session can be used.
One caveat on that last one: moving your repos to GitLab doesn't change Copilot's terms. If you keep using Copilot on a Free or Pro plan, your interaction data is covered wherever the code is hosted. The opt-out is what protects you. What the change did was show that GitHub will flip a default on your code and leave it to you to find the switch, and for some teams that's reason enough to stop treating GitHub as the only option.
The HN thread is mostly about the first point. One commenter called Actions "a god-awful mess" and said GitLab's CI/CD is "leaps and bounds over GitHub Actions". GitLab got plenty of criticism too: cross-group permissions, no group reviewers, a sidebar full of Secure/Deploy/Operate menus nobody asked for. What I didn't see anyone argue is that hosting the git data itself is hard to replace.
Whether you stay or go, if you use Copilot on a personal plan: open github.com/settings/copilot, go to the Privacy section and turn off training on your data.
How to migrate from GitHub to GitLab, step by step
For a normal-sized repo (a few thousand issues, a CI file or two), one hour is realistic. For a monster it isn't: GitLab's own docs say importing the Kubernetes repo, with about 80,000 PRs, 45,000 issues and 1.5 million comments, took 76 hours. Imports are also subject to GitHub's API rate limit, usually 5,000 requests per hour.
1. Create the right GitHub token
The GitHub importer supports classic personal access tokens only. Fine-grained tokens won't work. Scopes:
-
repo(required) -
read:orgif you want collaborators or LFS objects imported
2. Plan for placeholder users
Since GitLab 17.8 the importer maps contributions after the import. Every GitHub author lands on a placeholder user that can't sign in or be a member of anything. A group Owner then goes to Manage → Members → Placeholders in the top-level group and reassigns each placeholder to a real GitLab account, and that person has to accept. Once accepted, later imports from the same source into the same group map that user automatically.
So: get your team to create GitLab accounts before migration day, and budget time for the reassignment round afterwards. The older email-matching method (public GitHub email = GitLab email) only exists behind a feature flag on Self-Managed and Dedicated 18.7 and earlier. It isn't available on GitLab.com.
3. Run the import
In the UI: Create new (+) → New project/repository → Import project → GitHub, authorize, pick repos. For scripting it, or for GitHub Enterprise Server, use the import API:
curl --request POST \
--url "https://gitlab.com/api/v4/import/github" \
--header "content-type: application/json" \
--header "Authorization: Bearer <your_gitlab_token>" \
--data '{
"personal_access_token": "<github_classic_pat>",
"repo_id": "12345",
"target_namespace": "your-group",
"optional_stages": {
"attachments_import": true,
"collaborators_import": true
}
}'
repo_id is the numeric GitHub repository ID, not the name. Get it with gh api repos/OWNER/REPO --jq .id (for rokudev/hello-world that returns 50557099).
What comes across: all branches, LFS objects, issues, PRs with reviews and review comments, labels, milestones, release notes, wiki pages and branch protection rules. Markdown attachments only come across if you enable that option. It's off by default because it slows imports down, but if you skip it, the images in your issues stay on GitHub instead of moving with the project.
4. Rewrite GitHub Actions as .gitlab-ci.yml
Nothing converts this for you. The mapping from GitLab's migration guide is short:
| GitHub Actions | GitLab CI |
|---|---|
on: |
per-job rules:
|
runs-on: |
tags: (runner selection) |
steps: |
script: |
uses: |
include: / plain shell |
env: |
variables: |
strategy: matrix: |
parallel: matrix: |
${{ secrets.NAME }} |
$NAME (masked CI/CD variable) |
actions/cache |
cache: |
actions/upload-artifact |
artifacts: |
A typical Node workflow:
# .github/workflows/test.yml
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
becomes:
# .gitlab-ci.yml
test:
image: node:$NODE
parallel:
matrix:
- NODE: ["20", "22"]
cache:
key:
files: [package-lock.json]
paths: [.npm/]
script:
- npm ci --cache .npm --prefer-offline
- npm test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
Keep the when: never line. Without it, a push to a branch with an open merge request starts two pipelines, one for the branch and one for the MR.
Checkout is implicit, and the Docker image replaces setup-node. That's the pattern for most of your uses: lines: an action from the marketplace usually turns into a container image plus two shell lines. The painful cases are actions with real logic in them, like a deploy action written in TypeScript. Those you rewrite by hand.
Secrets go into Settings → CI/CD → Variables, marked masked and protected. GitLab's docs say outright: never put them in .gitlab-ci.yml.
5. Mirror back to GitHub so old links keep working
GitHub won't redirect to another host. Your stars, inbound links and go get paths all point at github.com. The cheap fix is a push mirror from GitLab to the old GitHub repo:
- On GitHub, create a fine-grained token with read/write on Contents (and Workflows if the repo still has a
.github/workflowsdirectory). - In GitLab: Settings → Repository → Mirroring repositories.
- URL
https://github.com/OWNER/REPO.git, direction Push, username = token owner, password = token. - Tick Only mirror protected branches. GitLab then pushes about every minute instead of about every five.
Then delete .github/workflows so the mirror doesn't run CI on GitHub too, disable Issues and PRs on GitHub, and put a "moved to GitLab" line at the top of the README.
Roku went the other way and archived its GitHub repos (a handful, like lt-sdk and roku-deploy, are still live). Archived repos are read-only, so they can't receive mirror pushes. Your old URLs keep working, but the code there is frozen. For a docs repo that third parties crawl, that's the worse choice: Roku's own notice tells anyone indexing their repos "for a knowledge base, assistant, or coding agent" to re-crawl, because raw file URLs changed.
6. Update every clone
git remote set-url origin https://gitlab.com/OWNER/REPO.git
# submodules: edit .gitmodules, then
git submodule sync
What doesn't survive the move
Straight from the importer docs and from what's missing in them:
- Organizations and groups. "Importing projects does not migrate or import any types of groups or organizations." You rebuild the group tree by hand.
- Outside collaborators. Not imported.
- "Require status checks" branch rule. Dropped, so you recreate it as an external status check.
- Custom repository roles. These cause partially completed imports.
- Actions, webhooks, GitHub Apps. Not mentioned in the importer docs, and none of it comes across.
- PRs opened after the import. Mirroring doesn't sync them, so freeze the GitHub side on migration day.
- Stars, forks, the social graph. Gone for good. For an open source project that's the real cost, and the reason many maintainers mirror instead of leaving outright.
My take
Leaving GitHub is no longer hard. The GitLab importer is good, and you'll lose an afternoon on Actions YAML, a round of placeholder reassignments and maybe a few stars. The reason to bother has changed too: after a year of Actions complaints and a training default flipped to opt-out, "we've always been on GitHub" stops being a neutral choice.
Roku won't say why it left. I'd bet the answer is boring, like standardizing on the GitLab they already run internally, as a couple of HN commenters guessed. Even a boring reason tells you something: a big public company decided GitHub's network effect wasn't worth keeping even for its public developer repos.
What would actually make you leave GitHub: pricing, Copilot training on your code, Actions outages, or nothing ever? And if you've already moved, what broke that the docs didn't warn you about?
Sources
- Roku developer forum: repositories have moved to GitLab
- Roku developer blog: the GitLab move
- Hacker News discussion
- GitHub Blog: Updates to GitHub Copilot interaction data usage policy
- GitLab docs: Import from GitHub
- GitLab docs: Import API
- GitLab docs: Post-migration user mapping
- GitLab docs: Migrating from GitHub Actions
- GitLab docs: Push mirroring
- The Register: Zig quits GitHub
Top comments (0)