GitLab から GitHub へ移行するには? リポジトリ / Issue / CI/CD をまとめて検証
CICD ツールや開発プラットフォームはいろいろありますが、「どれがよく使われているのか?」という観点でまずはざっくり人気感を押さえておきたいと思い、直近 1 年間の Google トレンドを比較してみました。
GitHub、Jira、GitLab、Azure DevOps の 4 つで相対的な検索関心度を並べると、おおむね GitHub が頭ひとつ抜けていて、その後ろに Jira、GitLab、Azure DevOps という順に続くイメージでした。

GitHub が強いとはいえ、GitLab も普通に使われていますし、「いま GitLab を使っているけど、今後は GitHub 側に寄せたい」という相談はよくあります。もちろん、その逆も然りです。
本記事では、GitLab から GitHub へ移行する際の手順と注意点 を、実際のリポジトリを使って検証した結果としてまとめます。
「コードはどうやって持っていくの?」「Issues は?」「パイプラインは?」といった、現場で一番困りがちな 3点
- リポジトリ(ソースコードと履歴)
- Issue(課題管理)
- CI/CD パイプライン
について、それぞれどこまで自動でいけるかを記録としてまとめます。
まずは、GitHub と GitLab で似ているようで違う用語をざっくり揃えておきます。
| GitHubの用語 | 役割イメージ | GitLabでいちばん近いもの | 補足 |
|---|---|---|---|
| Enterprise (アカウント) | 複数の Organization をまとめる最上位の管理単位。請求・ポリシー・監査ログなどをまとめて管理する枠。 | GitLab インスタンス全体(Self-Managedの場合)もしくはトップレベル Group(SaaS運用での実質的な最上位) | GitLab には GitHub Enterprise Account に相当する「SaaS上の親テナント」という概念がないので、目的によって近いものが変わります。 |
| Organization | チーム/部門ごとにリポジトリを束ねるスペース。メンバーや権限、シークレットのスコープなどを管理する単位。 | Group(グループ) | GitLab の Group はサブグループを階層化できるので、より柔軟です。 |
| Repository | ソースコード、Issue、Pull Request などの単位そのもの。 | Project(プロジェクト) | GitLab では「Project = リポジトリ + Issue + Merge Request + CI/CD」の一式です。 |
つまり本記事では、「GitLab の Project」を「GitHub の Repository」に移行する、という話になります。
GitHub 側にも “Project” という別機能があるので、この記事ではあえて GitLab 側も「リポジトリ」という言葉に統一します。
前置きはこのくらいにして、以降では リポジトリ / Issue / パイプライン の順で、実際の移行手順と結果を見ていきます。
0. 前提
今回検証した環境は次のとおりです。
| 要素 | 説明 | 備考 |
|---|---|---|
| GitLab | https://gitlab.com/union.dml-group/event-attendees-app | 移行元 (GitLab 側の Project) |
| GitHub | https://github.com/yutaka-art/event-attendees-app | 移行先 (GitHub 側の Repository) |
移行元となる GitLab プロジェクトは以下のような構成です。
データベースから取得したデータを返却する簡単なWebアプリケーションを例としています。

移行先となる GitHub のリポジトリは、あらかじめ「空の状態」で作成しておきます(README などを自動生成しない状態がやりやすいです)。

1. GitLab(プロジェクト) → GitHub(リポジトリ)
まずはコードと履歴(コミットログ、ブランチ、タグなど)をすべて GitHub に持っていきます。
1.1 GitLab 側リポジトリを mirror でクローンする
git clone --mirror は “bare リポジトリ” として全ての refs(branches, tags, notes, HEAD など)を丸ごと持ってきてくれます。
「そのまま引っ越したい」という用途ではこれが一番忠実です。
git clone --mirror https://gitlab.com/union.dml-group/event-attendees-app.git
これで event-attendees-app.git というディレクトリが作成されます。
通常のワーキングツリーはなく、中身はいわゆる bare repo です。
cd event-attendees-app.git
カレントディレクトリを合わせておきます。
1.2 現在のリモートを確認する(GitLab の URL が origin になっているはず)
git remote -v
期待される出力イメージはこんな感じです。
origin https://gitlab.com/union.dml-group/event-attendees-app.git (fetch)
origin https://gitlab.com/union.dml-group/event-attendees-app.git (push)
いまは origin が GitLab を向いていて、push も GitLab に行く状態だと確認できればOKです。
1.3 push 先を GitHub リポジトリに差し替える
GitHub 側にはすでに空のリポジトリ
https://github.com/yutaka-art/event-attendees-app
がある前提とします。この URL を push 先に設定し直します。
git remote set-url は2通りの使い方があります。状況に合わせて選びます。
パターンA: fetch は GitLab のまま残し、push だけ GitHub に向ける
移行検証中など、GitLab 側もまだ触りたいときに便利です。
git remote set-url --push origin https://github.com/yutaka-art/event-attendees-app.git
このあと git remote -v を見ると、こうなっているはずです。
origin https://gitlab.com/union.dml-group/event-attendees-app.git (fetch)
origin https://github.com/yutaka-art/event-attendees-app.git (push)
パターンB: origin 自体をまるごと GitHub に置き換える
「もう GitLab は参照しない。本番で切り替える」という場合はこちらがシンプルです。
git remote set-url origin https://github.com/yutaka-art/event-attendees-app.git
この場合の git remote -v はこうなります。
origin https://github.com/yutaka-art/event-attendees-app.git (fetch)
origin https://github.com/yutaka-art/event-attendees-app.git (push)
1.4 GitHub 側へ全履歴を一括プッシュ(mirror)
最後に、GitHub 側へ全ての refs を push します。
mirror で clone しているので、単純に git push --mirror でOKです。
git push --mirror
1.5 まとめ(コマンドだけざっと並べると)
# 1. GitLabから全refsをmirror clone
git clone --mirror https://gitlab.com/union.dml-group/event-attendees-app.git
cd event-attendees-app.git
# 2. 今のリモート確認
git remote -v
# 3. push先をGitHubに変更(検証的にpushだけ変える場合)
git remote set-url --push origin https://github.com/yutaka-art/event-attendees-app.git
# 完全移行なら
# git remote set-url origin https://github.com/yutaka-art/event-attendees-app.git
# 4. GitHubへ全refsをpush
git push --mirror
# または
# git push --mirror origin

GitLab 側で使っていたブランチなども、GitHub 側にそのまま移っていることが確認できます。

2. GitLab(Issue) → GitHub(Issues)
次は Issue(課題管理)です。
ここは GitHub が公式で用意している「GitHub Enterprise Importer」ではカバーできない範囲がまだ多く、サードパーティ/OSS ツールに頼ることになります。
今回使ったのは node-gitlab-2-github という OSS です
「GitLab の Issue や Merge Request の内容を GitHub Issues に持っていく」ことを目的にしたツールです。
2.1 ツールの取得とセットアップ
リポジトリを任意のディレクトリに clone し、npm で依存パッケージを入れます。
git clone https://github.com/piceaTech/node-gitlab-2-github.git
cd node-gitlab-2-github
npm install
2.2 Personal Access Token (PAT) の発行
GitLab 側・GitHub 側それぞれで PAT を作っておき、移行時に使います。
必要なスコープは最低限に絞って発行してください。
-
GitLab
ユーザー設定 > プロファイルを編集 > パーソナルアクセストークン から発行
スコープはapiとread_repositoryを付与します。
-
GitHub
Settings > Developer settings > Personal access tokens > Tokens (classic) から発行
スコープはrepoを付与します。
2.3 設定ファイルの作成
ツールは settings.ts を読み込みます。
サンプル設定ファイルがあるので、それをコピーして編集します。
cp sample_settings.ts settings.ts
settings.ts の例はこんな感じです(必要な値は自分の環境に合わせて置き換えてください)
import Settings from './src/settings';
export default {
gitlab: {
token: '<your-gitlab-token>',
projectId: null, // 後で置き換える
listArchivedProjects: true,
sessionCookie: "",
},
github: {
owner: '<your-organization-name or your-personal-account-name>',
ownerIsOrg: false, // Organizationに移したい場合はtrue
token: '<your-github-token>',
token_owner: '<your-account-name>',
repo: '<migration-repository-name>',
recreateRepo: false,
},
usermap: {
'<gitlab-user-name>': '<github-account-name>'
},
projectmap: {
'event-attendees-app': 'event-attendees-app'
},
// ...省略...
} as Settings;
2.4 ツール実行(対象プロジェクトの確認)
一度ツールを実行して、まずは「GitLab 側でどのプロジェクトが対象になるか」を確認します。
この段階では実データの移行はまだ行いません(ドライランのイメージです)。
npm run start
問題なく動けば、対象となる GitLab プロジェクトの ID が列挙されます。
これは後の手順で指定する必要があります。

2.5 ツール実行(実際の移行)
settings.ts の gitlab.projectId をいま取得したプロジェクト ID に書き換えます。
export default {
gitlab: {
// ...
projectId: <2.4で出力されたプロジェクトID>,
// ...
},
// ...
}
その上で、もう一度実行します。
npm run start

GitHub 側の Issues を見ると、GitLab の Issue がいい感じにインポートされています。
Milestone や Label もある程度マッピングされていました。

3. GitLab(CI/CD パイプライン) → GitHub(Actions)
最後はパイプラインです。
ここが一番「期待しすぎるとつらい」領域で、GitLab CI/CD の実行環境そのものを GitHub Actions にそのまま持っていくことはできません。
ただし、「.gitlab-ci.yml をベースに、GitHub Actions っぽいワークフロー(.github/workflows/*.yml)を自動生成する」ための支援ツールは存在します。それが gh-actions-importer です。
このツールはいくつかのモードを持っていて、
- 監査(audit)
- コスト/実行量の見積もり(forecast)
- ドライラン変換(dry-run)
- 実際にPRを作る移行(migrate)
といったステップで、段階的に移行を手伝ってくれます。
※ Docker が動く環境が必要なので、事前に用意しておきます。
3.0 ツールの初期設定
まず CLI 拡張を GitHub CLI (gh) にインストールします。
gh extension install github/gh-actions-importer

次に資格情報を設定します。
gh actions-importer configure


設定時の入力イメージはこんな感じです。
| 項目 | 値の例 | 補足 |
|---|---|---|
| Which CI providers are you configuring? | GitLab CI | 今回は GitLab → GitHub の移行なのでこれ |
| Personal access token for GitHub | GitHubで発行したPAT |
repo, workflow など必要な権限を付与 |
| Base url of the GitHub instance | https://github.com | GitHub.com以外(GHES等)の場合は適宜変更 |
| Private token for GitLab | GitLabで発行したPAT |
read_api が必要 |
| Base url of the GitLab instance | https://gitlab.com | Self-Managedの場合はそのURLに変更 |
最後にツール本体をアップデートしておきます。
gh actions-importer update

3.1 audit: GitLab 全体のパイプラインを棚卸しする
audit コマンドでは、指定した GitLab グループ以下のプロジェクトを走査して、
- どんなパイプラインがあるのか
- それらを GitHub Actions にどの程度マッピングできそうか
をレポートとしてまとめてくれます。
gh actions-importer audit gitlab `
--output-dir tmp/audit `
--namespace <your-gitlab-group>

出力されるディレクトリ構成の例はこんな感じです。
.\tmp\audit
│ audit_summary.md
│ workflow_usage.csv
│
├─log
│ valet-20251028-025836.log
│ valet-20251028-030938.log
│
└─union.dml-group
├─event-attendees-app
│ │ config.json
│ │ source.yml
│ │
│ └─.github
│ └─workflows
│ event-attendees-app.yml
│
└─migration-lab-to-hub-deletion_scheduled-75614825
│ config.json
│ source.yml
│
└─.github
└─workflows
migration-lab-to-hub-deletion_scheduled-75614825.yml
3.2 forecast: GitHub Actions の利用量を見積もる
forecast コマンドは、GitLab 上のパイプライン実行履歴からメトリックを集計し、
「GitHub Actions に移したらどれくらい実行時間 / ランナー利用量になりそうか」を試算します。
コスト見積もりやライセンス検討の材料になるフェーズです。
gh actions-importer forecast gitlab `
--output-dir tmp/forecast `
--namespace <your-gitlab-group>

出力イメージ
.tmp\forecast
│ forecast_report.md
│
├─jobs
│ 10-28-2025-03-14_jobs_0.json
│
└─log
valet-20251028-031442.log
3.3 dry-run: GitLab パイプラインを Actions 形式に変換してみる
dry-run コマンドは、GitLab のパイプライン定義(.gitlab-ci.yml)を解析し、
相当する GitHub Actions ワークフロー(.github/workflows/*.yml)をローカルに生成してくれます。
この時点では、まだ GitHub 側には反映されません。
gh actions-importer dry-run gitlab `
--output-dir tmp/dry-run `
--namespace <your-gitlab-group> `
--project <your-gitlab-project>

出力イメージ
.\tmp\dry-run
├─log
│ valet-20251028-030513.log
│ valet-20251028-030853.log
│
└─union.dml-group
└─event-attendees-app
└─.github
└─workflows
event-attendees-app.yml
3.4 migrate: 実際に GitHub リポジトリへ PR を作る
最後に migrate。
これは、GitLab のパイプラインを変換したうえで、GitHub 側のターゲットリポジトリに Pull Request を作ってくれます。
つまり「本番環境への持ち込み」の第一歩です。
gh actions-importer migrate gitlab `
--target-url https://github.com/yutaka-art/event-attendees-app `
--output-dir tmp/migrate `
--namespace <your-gitlab-group> `
--project <your-gitlab-project>

出力例
.\tmp\migrate
└─log
valet-20251028-032918.log
GitHub 側を見ると、実際にワークフローを追加する Pull Request が作成されています。

3.5 どこまで「そのまま」使えるのか?
ここが重要なポイントです。
今回の GitLab パイプラインは、.NET のユニットテストとカバレッジ計測、そしてコンテナイメージのビルド・ACR (Azure Container Registry) へのプッシュ、という比較的よくある内容です。
まず元の .gitlab-ci.yml はこんな感じでした(一部抜粋)
workflow:
rules:
- if: '$CI_COMMIT_BRANCH == "main""
changes:
- src/**/*
- database/**/*
- when: never
variables:
BUILD_CONFIGURATION: "Release"
stages:
- test
- deploy_dev
# (ユニットテスト & カバレッジ)
unit_test_job:
stage: test
image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- echo "Discovering test projects..."
- TEST_PROJECTS_LIST=$(find . -type f -name '*.Tests.csproj')
- ...
- dotnet restore ...
- dotnet build ...
- dotnet test ... --collect:"XPlat Code Coverage"
- カバレッジXMLを coverage/ にコピー
artifacts:
when: always
expire_in: 1 week
paths:
- coverage/
- test-results/
reports:
junit: test-results/*.trx
coverage_report:
coverage_format: cobertura
path: coverage/*.xml
# (Dev環境向けのコンテナビルド & プッシュ)
deploy_dev_job:
stage: deploy_dev
needs: ["unit_test_job"]
image: docker:26
services:
- name: docker:26-dind
command: ["--tls=false"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
ACR_LOGIN_SERVER: "cravanfr.azurecr.io"
IMAGE_NAME: "eventattendeesapp"
IMAGE_TAG: "latest"
when: manual
script:
- docker login "$ACR_LOGIN_SERVER" -u "$ACR_USERNAME" -p "$ACR_PASSWORD"
- docker build ...
- docker push ...
environment:
name: dev
これを GitHub Actions Importer で変換すると、次のようなワークフローになりました(自動生成されたものをほぼそのまま掲載します)
name: union.dml-group/event-attendees-app
on:
push:
workflow_dispatch:
concurrency:
group: "${{ github.ref }}"
cancel-in-progress: true
env:
ACR_USERNAME: "${{ secrets.ACR_USERNAME }}"
ACR_PASSWORD: "${{ secrets.ACR_PASSWORD }}"
jobs:
unit_test_job:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:8.0
if: # Unable to map conditional expression to GitHub Actions equivalent
# ${{ github.ref }} == "main" || false
timeout-minutes: 60
env:
BUILD_CONFIGURATION: Release
steps:
- uses: actions/checkout@v4.1.0
with:
fetch-depth: 20
lfs: true
- run: echo "Discovering test projects..."
- run: TEST_PROJECTS_LIST=$(find . -type f -name '*.Tests.csproj')
- run: echo "Found test projects:"
- run: echo "$TEST_PROJECTS_LIST"
- run: mkdir -p coverage
- run: mkdir -p test-results
- run: |
for proj in $TEST_PROJECTS_LIST; do
echo "=== Processing $proj ==="
NAME=$(basename "$proj" .csproj)
echo "[restore] $proj"
dotnet restore "$proj"
echo "[build] $proj"
dotnet build "$proj" --no-restore --configuration "$BUILD_CONFIGURATION"
echo "[test] $proj (with coverlet.collector)"
dotnet test "$proj" \
--no-build \
--configuration "$BUILD_CONFIGURATION" \
--collect:"XPlat Code Coverage" \
--logger "trx;LogFileName=$(pwd)/test-results/${NAME}-test-results.trx"
echo "[gather coverage] $proj"
RESULT_DIR=$(dirname "$proj")/TestResults
FOUND_FILE=$(find "$RESULT_DIR" -type f -name "coverage.cobertura.xml" | head -n 1 || true)
if [ -n "$FOUND_FILE" ]; then
cp "$FOUND_FILE" "coverage/${NAME}-coverage.cobertura.xml"
else
echo "No coverage.cobertura.xml found for $proj"
fi
done
- run: echo "==== coverage dir ===="
- run: ls -R coverage || true
- run: echo "==== test-results dir ===="
- run: ls -R test-results || true
# # 'artifacts.junit' は GitHub Actions に相当するネイティブ機能がないため未変換
# # 'artifacts.coverage_report' も同様
- uses: actions/upload-artifact@v4.1.0
if: always()
with:
name: "${{ github.job }}"
retention-days: 7
path: |-
coverage/
test-results/
deploy_dev_job:
needs: unit_test_job
runs-on: ubuntu-latest
container:
image: docker:26
if: github.event_name == 'workflow_dispatch'
environment:
name: dev
timeout-minutes: 60
services:
docker:26-dind:
image: docker:26-dind
options: docker:26-dind --tls=false
env:
BUILD_CONFIGURATION: Release
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ''
ACR_LOGIN_SERVER: cravanfr.azurecr.io
IMAGE_NAME: eventattendeesapp
IMAGE_TAG: latest
steps:
- uses: actions/checkout@v4.1.0
with:
fetch-depth: 20
lfs: true
- uses: actions/download-artifact@v4.1.0
- run: echo "=== Deploy to Dev (build & push container image) ==="
- run: echo "Logging in to $ACR_LOGIN_SERVER ..."
- run: docker login "$ACR_LOGIN_SERVER" -u "$ACR_USERNAME" -p "$ACR_PASSWORD"
- run: echo "Building image..."
- run: docker build -t "$ACR_LOGIN_SERVER/$IMAGE_NAME:$IMAGE_TAG" -f src/Dockerfile ./src
- run: echo "Pushing image to $ACR_LOGIN_SERVER ..."
- run: docker push "$ACR_LOGIN_SERVER/$IMAGE_NAME:$IMAGE_TAG"
- run: echo "Done. Image pushed as $ACR_LOGIN_SERVER/$IMAGE_NAME:$IMAGE_TAG"
一見それっぽいのですが、実際にはいくつか課題があります。
-
lfs: trueになっているが、実際には Git LFS を使っていない/Runner で LFS をセットアップしていない- uses: actions/checkout@v4.1.0 with: lfs: trueこのままだと不要な処理やエラーの元になることがあります。
-
条件付き実行 (
if:) が十分に変換されていない
GitLab のrules:のようにmainブランチだけ動かす、特定パスの変更時だけ動かす、といった条件は GitHub Actions ではon.push.pathsなど別の表現に落とし込む必要があります。Importer はここを完全には自動化しきれません。
コメントにも# Unable to map conditional expression...と正直に書かれています。 -
環境変数に
secretsを直接マージしているenv: ACR_USERNAME: "${{ secrets.ACR_USERNAME }}" ACR_PASSWORD: "${{ secrets.ACR_PASSWORD }}"こういう「ワークフロー全体の env で secrets を展開する」書き方は、ジョブ境界・ステップ境界で意図どおりに渡らないことがあります。GitHub Actions では、
jobs.<job>.steps[*].envに都度渡すほうが確実です。 -
Docker の扱いが GitLab 前提のまま
GitLab CI ではdocker:dindサービスを sidecar 的に起動するのが定番ですが、GitHub Actions のubuntu-latestランナーは基本的に Docker がローカルでそのまま使えるので、無理に dind 形式に合わせる必要はありません。
つまり、Importer が吐いた YAML は「GitLab の世界観をそのまま Actions に持ち込もうとしている」ので、ここも手修正ポイントになります。
まとめると、Importer は「たたき台としての YAML」を生成してくれるツールです。
“そのまま本番運用できる定義を自動で作ってくれる魔法ツール” ではない、という理解が正しいと思います。
最後に GitHub Actions として正常に動くワークフローを記載します。
name: CI/CD (event-attendees-app)
on:
push:
branches:
- main
paths:
- 'src/**'
- 'database/**'
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build_and_test:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:8.0
env:
BUILD_CONFIGURATION: Release
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: false
- name: Locate solution or projects
id: locate
shell: bash
run: |
set -euo pipefail
sln=$(ls -1 *.sln 2>/dev/null || true)
if [ -z "$sln" ]; then
sln=$(ls -1 src/*.sln 2>/dev/null || true)
fi
if [ -n "$sln" ]; then
echo "mode=sln" >> $GITHUB_OUTPUT
echo "target=$sln" >> $GITHUB_OUTPUT
echo "Using solution: $sln"
exit 0
fi
mapfile -t projects < <(find . -type f -name '*.csproj' ! -name '*.Tests.csproj' | sort)
if [ ${#projects[@]} -eq 0 ]; then
echo "No buildable *.csproj found (excluding *Tests.csproj)." >&2
exit 1
fi
echo "mode=proj" >> $GITHUB_OUTPUT
echo "target=$(printf "%s " "${projects[@]}")" >> $GITHUB_OUTPUT
printf "Using projects:\n%s\n" "${projects[@]}"
- name: Discover test projects
id: discover
shell: bash
run: |
set -euo pipefail
TEST_PROJECTS_LIST=$(find . -type f -name '*.Tests.csproj' | sort || true)
echo "Found test projects:"
echo "$TEST_PROJECTS_LIST"
echo "projects=$TEST_PROJECTS_LIST" >> $GITHUB_OUTPUT
- name: Restore and Build
shell: bash
run: |
set -euo pipefail
if [ "${{ steps.locate.outputs.mode }}" = "sln" ]; then
dotnet restore "${{ steps.locate.outputs.target }}"
dotnet build "${{ steps.locate.outputs.target }}" --no-restore --configuration "$BUILD_CONFIGURATION"
else
for p in ${{ steps.locate.outputs.target }}; do
dotnet restore "$p"
dotnet build "$p" --no-restore --configuration "$BUILD_CONFIGURATION"
done
fi
- name: Test with coverage
shell: bash
run: |
set -euo pipefail
mkdir -p coverage test-results
if [ -z "${{ steps.discover.outputs.projects }}" ]; then
echo "No test projects found. Skipping tests."
exit 0
fi
# 複数行の安全なループ
while IFS= read -r proj; do
[ -z "$proj" ] && continue
echo "=== Testing $proj ==="
NAME=$(basename "$proj" .csproj)
dotnet test "$proj" \
--no-build \
--configuration "$BUILD_CONFIGURATION" \
--collect:"XPlat Code Coverage" \
--logger "trx;LogFileName=${NAME}-test-results.trx" || true
RESULTS_DIR="$(dirname "$proj")/TestResults"
COV_FILE=$(find "$RESULTS_DIR" -type f -name "coverage.cobertura.xml" | head -n 1 || true)
[ -n "$COV_FILE" ] && cp "$COV_FILE" "coverage/${NAME}-coverage.cobertura.xml" || echo "No coverage for $proj"
TRX_FILE=$(find "$RESULTS_DIR" -type f -name "*.trx" | head -n 1 || true)
[ -n "$TRX_FILE" ] && cp "$TRX_FILE" "test-results/${NAME}-test-results.trx" || echo "No TRX for $proj"
done <<< "${{ steps.discover.outputs.projects }}"
- name: Upload test artifacts (coverage & trx)
if: always()
uses: actions/upload-artifact@v4
with:
name: test-artifacts
retention-days: 7
path: |
coverage/
test-results/
build_and_push_image:
needs: build_and_test
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
env:
BUILD_CONFIGURATION: Release
ACR_LOGIN_SERVER: cravanfr.azurecr.io
IMAGE_NAME: eventattendeesapp
IMAGE_TAG: latest
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: false
- name: Download test artifacts
uses: actions/download-artifact@v4
with:
name: test-artifacts
- name: Login to Azure Container Registry
shell: bash
run: |
echo "${ACR_PASSWORD}" | docker login "${ACR_LOGIN_SERVER}" --username "${ACR_USERNAME}" --password-stdin
- name: Build container image
shell: bash
run: |
docker build \
-t "${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}" \
-f src/Dockerfile ./src
- name: Push container image
shell: bash
run: docker push "${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
まとめ
-
リポジトリ(コードと履歴)
git clone --mirror→git push --mirrorでかなり素直に移行できます。ブランチやタグも含めてほぼ忠実に持っていけます。 -
Issue(課題管理)
OSS ツール(node-gitlab-2-github)を使えばある程度移行できます。
ただし現時点(2025-10-27 時点)ではソースの手修正が必要でした。
また、Merge Request は Pull Request として完全には再現されず、クローズ済みMRが Issue として取り込まれるなどの割り切りが発生します。 -
CI/CD パイプライン
gh-actions-importerを使うと GitLab CI 設定から GitHub Actions の下書き (ワークフローYAMLや PR) を生成できます。
ただし、環境変数や secrets の扱い、Docker のまわし方、ブランチ条件などは手で直す必要があり、そのままでは動かないケースがあります。
結局「人間がレビューして仕上げる前提の半自動化ツール」と考えるのが現実的です。
今回の検証結果が、GitLab から GitHub へ移行を検討している方の初期調査や、見積もりの材料になればうれしいです。
参考リンク / Reference URLs
Issue移行ツール関連
- node-gitlab-2-github (GitLab Issue → GitHub Issues 変換ツール)
https://github.com/piceaTech/node-gitlab-2-github - 上記ツールの修正コミット(2025-10-27時点で必要だったパッチ)
https://github.com/piceaTech/node-gitlab-2-github/pull/246/commits
CI/CD移行ツール関連
- GitHub Actions Importer 本体
https://github.com/github/gh-actions-importer
GitHub Actions Importer ドキュメント
- GitLab環境の監査 (
audit)
https://docs.github.com/en/actions/tutorials/migrate-to-github-actions/automated-migrations/gitlab-migration#perform-an-audit-of-gitlab - ランナー使用量の予測 (
forecast)
https://docs.github.com/en/actions/tutorials/migrate-to-github-actions/automated-migrations/gitlab-migration#forecast-potential-build-runner-usage - ドライラン変換 (
dry-run)
https://docs.github.com/en/actions/tutorials/migrate-to-github-actions/automated-migrations/gitlab-migration#perform-a-dry-run-migration-of-a-gitlab-pipeline - 本番向け移行 (
migrate/ PR生成)
https://docs.github.com/en/actions/tutorials/migrate-to-github-actions/automated-migrations/gitlab-migration#perform-a-production-migration-of-a-gitlab-pipeline
全体リファレンス(日本語版)
- GitLab から GitHub Actions への移行ガイド(自動化ドキュメント 日本語版トップ)
https://docs.github.com/ja/actions/tutorials/migrate-to-github-actions/automated-migrations/gitlab-migration
ベースとなるホスト
- GitHub.com
https://github.com - GitLab.com
https://gitlab.com
Discussion