GitLab(EE Free版)はじめてガイド
リポジトリ・マージリクエスト・Issueの基本操作
このガイドは、GitLabを初めて使う個人開発者向けに、Free版(無料ティア)で使える基本操作をまとめたチュートリアルです。
💡 Free版について
GitLabには「エディション(CE/EE)」と「ティア(Free/Premium/Ultimate)」という2つの軸があります。GitLab.comやセルフホストでEEを使っていても、契約がFreeティアであれば、ここで紹介する基本機能を無料で使えます。
全体の流れ(まずはここだけ見れば把握できます)
このガイドで扱う一連の作業は、次のような流れになっています。細かい手順を読む前に、まず全体像をつかんでおくと理解が早いです。
目次
- アカウント作成とプロジェクト作成
- リポジトリの基本操作
- Issueで課題管理する
- ブランチで作業する
- マージリクエスト(MR)を作る
- CI/CDパイプラインを組んでみる
- 応用: IssueとMRの紐付けを必須化する
- タグを打ってリリースを作る
- Free版の制限まわりの注意点
- 次のステップ
1. アカウント作成とプロジェクト作成
このガイドでは、以降すべて同じ1つの題材(ログインページの不具合を直す)を通して手順を進めます。表示される画面例やコマンドは、(メールアドレスなど個人情報を除いて)そのまま入力すれば動くように統一してあります。
1-1. アカウント登録
- GitLab.com を開きます。
- 右上の 「Register」 をクリックします。
- 登録フォームに、次の通り入力します(Email・Passwordはご自身のもので構いません)。
-
Name:
Taro Yamada -
Username:
taro-yamada - Email: ご自身のメールアドレスを入力
- Password: 任意のパスワードを入力
-
Name:
- 「Register」 をクリックします。
- 届いた確認メールを開き、本文中のリンクをクリックします。
- リンクをクリックするとログイン済みの状態でダッシュボード画面が開きます。
1-2. 新しいプロジェクトの作成
- ダッシュボードの 「New project」 をクリックします。
- 「Create blank project」 をクリックします。
- フォームに次の通り入力します。
-
Project name 欄に
my-first-projectと入力 -
Project slug は自動で
my-first-projectになるので、そのままにします -
Visibility Level は
Privateを選びます - Project Configuration の 「Initialize repository with a README」 にチェックを入れます
-
Project name 欄に
- 「Create project」 をクリックします。
数秒でプロジェクトのトップページに移動します。青い 「Code」 ボタンを押すと、クローン用のURL(HTTPS/SSH)が表示されます。このURLは2章で使うので、このままこのページを開いておいてください。
2. リポジトリの基本操作
2-1. クローン方法は2種類ある(HTTPS / SSH)
GitLabのプロジェクトをパソコンにダウンロード(クローン)するには、まずGitLabに「あなたが本人である」と証明する必要があります。この証明の仕方に、大きく2つの方式があります。
| 方式 | URL | どうやって証明するか |
|---|---|---|
| HTTPS | https://gitlab.com/taro-yamada/my-first-project.git |
Personal Access Token(トークン=合言葉のようなもの)を毎回、もしくはPCに記憶させて使う |
| SSH | git@gitlab.com:taro-yamada/my-first-project.git |
事前にPCに作った「鍵」を使う。一度設定すれば、以降は合言葉の入力なしで自動的に証明される |
(上のURLの taro-yamada の部分は、1章でご自身が登録したUsernameに置き換えてください。以下も同様です。)
GitLab公式ドキュメントでも、毎回の認証入力をなくしたい場合はSSHでのクローンを勧めています。個人で開発を続けるなら、最初にSSHの鍵を用意しておくと後がずっと楽になります。ここではその手順を説明します。
2-2. SSH鍵を設定する(OS別)
そもそもSSH鍵とは何か
SSH鍵は「合鍵」のようなものですが、普通の鍵と違って2つで1セットです。
- 秘密鍵(private key): 自分のパソコンだけに置いておく、絶対に人に見せない鍵
- 公開鍵(public key): GitLab側に登録しておく鍵。人に見られても問題ない
この2つは数学的にペアになっていて、「公開鍵で鍵をかけた箱は、対になる秘密鍵でしか開けられない」という仕組みになっています。GitLabは自分のパソコンに「その公開鍵とペアの秘密鍵、本当に持ってる?」と暗号を使って確認するので、パスワードを送らなくても本人確認ができます。これがSSH鍵を使う理由です。
一度これを設定してしまえば、以降GitLabは「あ、この人はさっき確認できた人だ」と分かるので、毎回パスワードやトークンを入力する必要がなくなります。
パスワードや合言葉そのものをネットワーク越しに送らないので、途中で盗み見られても安全、というのがSSH鍵認証の考え方です。
鍵の種類について(ed25519を使う理由)
ssh-keygen というコマンドで鍵を作る際、鍵の「作り方(アルゴリズム)」をいくつかの中から選べます。ここでは ed25519 という方式を使います。ED25519は、従来よく使われてきたRSAという方式より安全性が高いとされている比較的新しい方式です。特別な理由がなければこれを選んでおけば間違いありません。
🍎 macOS
# 1. 鍵を生成する
# -t ed25519 → 鍵の方式を指定
# -C "..." → 鍵に付けるコメント(自分のメールなどで、後で見て誰の鍵か分かるようにするだけ)
# 実行するとファイルの保存場所とパスフレーズ(鍵自体を守る合言葉)を聞かれますが、
# 保存場所はEnterでデフォルトのままでOK。パスフレーズは設定しなくても動きます(任意)。
ssh-keygen -t ed25519 -C "your_email@example.com"
# 2. ssh-agentという「鍵の番人」を起動して、作った鍵を覚えさせる
# これをやっておくと、この後ずっとパスフレーズの入力を省略できる
eval "$(ssh-agent -s)"
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
# 3. 公開鍵(人に見せてよい方)をクリップボードにコピーする
# ここでコピーした文字列を、次のステップでGitLabに貼り付けます
pbcopy < ~/.ssh/id_ed25519.pub
--apple-use-keychain を付けておくと、macOSのキーチェーン(パスワード管理機能)にパスフレーズが保存され、パソコンを再起動しても毎回聞かれずに済みます。
🪟 Windows
Windowsには標準でGitが入っていないので、まず Git for Windows をインストールしてください。インストールすると「Git Bash」という、Mac/Linuxと似たコマンドが使えるアプリが一緒に入ります。以下はGit Bashで実行します。
# 1. 鍵を生成する(意味はmacOSと同じ)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 2. ssh-agent(鍵の番人)を起動して鍵を登録
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# 3. 公開鍵をクリップボードにコピー
clip < ~/.ssh/id_ed25519.pub
PowerShellを使いたい場合は、Windowsの「アプリと機能」→「オプション機能」から「OpenSSH クライアント」を追加でインストールすれば同じコマンドが使えます。クリップボードへのコピーだけは Get-Content ~/.ssh/id_ed25519.pub | clip と書き方が変わります。
🐧 Linux(Ubuntu)
Ubuntuには通常SSH関連のコマンドが最初から入っています。
# 1. 鍵を生成する(意味はmacOSと同じ)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 2. ssh-agent(鍵の番人)を起動して鍵を登録
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# 3. 公開鍵を画面に表示する(そのまま選択してコピーすればOK)
cat ~/.ssh/id_ed25519.pub
# クリップボードに直接コピーしたい場合は、xclipという小さなツールを入れると楽になる
sudo apt install xclip -y
xclip -sel clip < ~/.ssh/id_ed25519.pub
共通: コピーした公開鍵をGitLabに登録する
ここまでで「鍵ペアを作って、公開鍵をコピーする」ところまで終わりました。次は、その公開鍵をGitLab側に「これがあなたの公開鍵ですよ」と教えてあげる作業です。
- GitLabにログインし、右上のアバター →「Edit profile」
- 左サイドバーの「SSH Keys」→「Add new key」
-
Key欄に、先ほどコピーした公開鍵(ssh-ed25519 ...から始まる長い文字列)を貼り付け -
Title欄にMy Laptopと入力します(複数の端末を使う場合は、後で見て分かる名前に変えてください) - 「Add key」で保存
これで、あなたのパソコンの秘密鍵とGitLabに登録した公開鍵がペアとして紐づきました。
接続確認(全OS共通)
正しく設定できたか確認するために、実際にGitLabへ接続してみます。
ssh -T git@gitlab.com
初回だけ「本当にこのサーバーに接続していいか」という確認メッセージが出るので、yes と入力してEnterします(これはGitLabのサーバーが偽物でないかの確認で、一度承認すれば次回からは聞かれません)。
正しく設定できていれば、次のように表示されます。
Welcome to GitLab, @yourusername!
このメッセージが出れば、SSH鍵の設定は完了です。
2-3. ローカルにクローンする
SSH鍵の設定が済んだら、次のコマンドを実行します。クローンとは、GitLab上にあるプロジェクトのコピーを自分のパソコンに持ってくる操作です。作業したい適当なフォルダ(例: デスクトップ)でターミナルを開いてから実行してください。
git clone git@gitlab.com:taro-yamada/my-first-project.git
cd my-first-project
(taro-yamada の部分は、ご自身のUsernameに置き換えてください)
SSH鍵をまだ設定していない場合や、会社のネットワークなどでSSHが使うポート(22番)がブロックされていて繋がらない場合は、代わりに次のコマンドでHTTPS方式でクローンします。
git clone https://gitlab.com/taro-yamada/my-first-project.git
cd my-first-project
HTTPSの場合、初回は認証情報(ユーザー名とPersonal Access Token)の入力を求められます。
📝 HTTPSで毎回認証を聞かれるのが面倒な場合: URLの中にトークンを直接書き込む方法は避けてください。パソコンの設定ファイルに合言葉が平文(そのまま読める状態)で残ってしまうためです。代わりに
git config --global credential.helperというコマンドで、OSごとの安全な保管場所(macOS: osxkeychain / Windows: manager / Linux: libsecret など)にトークンを覚えさせる方法がおすすめです。一度設定すれば、以降はSSHと同じように入力なしで使えます。
2-4. 初回だけ: Gitに名前とメールアドレスを設定する
クローンができたら、コミットを作る前に一度だけやっておくべき設定があります。Gitは「誰がこの変更をしたか」をコミットごとに記録するのですが、これを先に教えておかないと、コミットのたびに警告が出たり、意図しない名前で記録されたりします。
git config --global user.name "Taro Yamada"
git config --global user.email "your_email@example.com"
(Taro Yamada は1章で登録したNameと同じものを、your_email@example.com の部分はご自身のメールアドレスに書き換えてください)
-
--globalを付けると、この設定はパソコン全体(すべてのリポジトリ)に適用されます。 - ここで指定するメールアドレスは、GitLabアカウントに登録しているものと同じにしておくと、あとでコミットがGitLab上のあなたのアカウントに正しく紐づいて表示されます(アイコンが表示されるようになる、など)。
設定できたか確認するには次を実行します。
git config --global --list
user.name=... と user.email=... が表示されていればOKです。
2-5. 基本的なGit操作
ここからは実際にファイルを編集して、その変更をGitLabに送る(プッシュする)流れです。それぞれのコマンドが何をしているかも合わせて説明します。
# 適当にファイルを編集してみる(例: READMEに1行追記)
echo "はじめてのGitLab編集です" >> README.md
# 1. git add : 「次のコミットに含める変更」を選ぶ操作(ステージング)
# 「.」は「今いるフォルダ以下の変更を全部」という意味
git add .
# 2. git status で、今からコミットされる内容を確認できる(任意だが確認推奨)
git status
# 3. git commit : ステージングした変更を、メッセージを添えて記録する
# -m の後の文字列が、その変更の内容を説明するコミットメッセージ
git commit -m "READMEに一言追加"
# 4. git push : ローカルで記録した変更を、GitLab(リモート)に送信する
# origin = クローン時に自動で設定されたリモートの名前
# main = 送信先のブランチ名
git push origin main
git push が成功すると、ターミナルに送信されたデータ量などが表示されます。GitLabのプロジェクトページを開いて(またはリロードして)、README.md に追記した内容が反映されていれば成功です。
2-6. ファイルをGitLab上で直接編集する
- リポジトリ画面の任意のファイルを開き、鉛筆アイコン(Edit)をクリックすると、Web UIから直接編集・コミットできます。
- 画面下部に「Commit message」を入力する欄が出るので、内容を書いて「Commit changes」を押せばそのままリポジトリに反映されます。
- ちょっとした修正なら、ローカル環境がなくてもブラウザだけで完結します。
3. Issueで課題管理する
3-1. Issueの作成(UI版 / コマンドライン版)
UI版
- プロジェクトを開き、左サイドバーの 「Plan」→「Issues」(GitLabのバージョンによっては単に「Issues」)を選択します。
- 右上の 「New issue」 ボタンをクリックします。
- Issue作成フォームに、次の通り入力します。
-
Title 欄に
ログインページのエラーを修正すると入力 -
Type は
Issueのままにします -
Description 欄に、次の内容をそのまま貼り付けます。
## 概要 スマートフォンでログインボタンを押すとエラーになる ## 再現手順 1. スマホでログインページを開く 2. メールアドレス・パスワードを入力 3. ログインボタンをタップ ## 期待する動作 正常にログインできること - Assignee は自分のアカウントを選択します
- Labels は、まだ何もなければ一旦空欄のままで進めます(作り方は次の項目を参照)
- Milestone も一旦空欄のままで進めます
- Due date は空欄のままにします
- Confidential issue はチェックを入れません
-
Title 欄に
- フォーム下部の 「Create issue」(バージョンによっては「Submit issue」)ボタンをクリックします。
これでIssueが作成され、プロジェクト内で最初のIssueであれば #1 という番号が自動的に振られます。この番号は、この後の4章(ブランチ作業)や5章(MR作成)で、コミットメッセージやMRの説明文に書いて「どの作業に対応する変更か」を示すために使います。
コマンドライン版
コマンドラインから操作したい場合は、GitLab公式のCLIツール glab を使います。以降の章でもたびたび登場するので、ここで導入しておきます。
# インストール(macOSの場合)
brew install glab
# インストール(Ubuntu/Debianの場合)
sudo apt install glab
インストールできたら、初回だけ認証を行います。
glab auth login
画面の指示に従って、GitLab.comへのログインとブラウザでの認証を済ませてください。認証が終わったら、Issueを作成します。
glab issue create \
--title "ログインページのエラーを修正する" \
--description "$(cat <<'EOF'
## 概要
スマートフォンでログインボタンを押すとエラーになる
## 再現手順
1. スマホでログインページを開く
2. メールアドレス・パスワードを入力
3. ログインボタンをタップ
## 期待する動作
正常にログインできること
EOF
)"
実行するとIssueが作成され、ターミナルにIssueのURLと番号が表示されます。ラベルやマイルストーンを付けたい場合は、--label bug や --milestone "v1.0リリース" のようにオプションを追加できます(ラベル・マイルストーン自体は次の項目の手順で先に作っておく必要があります)。
ラベル(Label)を先に作っておく
新規プロジェクトには最初、ラベルが1つも用意されていません。次の手順で bug というラベルを作ります。
- 左サイドバーの「Plan」→「Labels」を開く
- 右上の「New label」をクリック
-
Title欄にbugと入力し、Colorは好きな色を1つクリックして選ぶ - 「Create label」をクリックして保存
同じ手順で feature(新機能)、documentation(ドキュメント)も作っておくと、以降のIssue作成時にラベルとして選べるようになります。先ほど作成した #1 のIssueを開き直し、Labels に bug を設定しておいてください。
マイルストーン(Milestone)を先に作っておく
- 左サイドバーの「Plan」→「Milestones」を開く
- 「New milestone」をクリック
-
Title欄にv1.0リリースと入力 - 「Create milestone」をクリックして保存
3-2. Issueテンプレートを用意する
毎回Issueの説明欄をゼロから書くのは手間ですし、書く人によって内容がバラバラになりがちです。そこで、あらかじめ「型」を用意しておく Issueテンプレート を作ります。テンプレートは特別な設定画面ではなく、リポジトリの決まった場所にMarkdownファイルを置くだけで使えるようになります。
-
ローカルのプロジェクトフォルダで、テンプレート用のフォルダを作ります。
mkdir -p .gitlab/issue_templates -
.gitlab/issue_templates/Bug.mdというファイルを作り、次の内容をそのまま貼り付けます。## 概要 <!-- 何が起きているかを簡潔に書いてください --> ## 再現手順 1. 2. 3. ## 期待する動作 <!-- 本来どうなるべきかを書いてください --> ## 実際の動作 <!-- 実際に何が起きたかを書いてください --> /label ~bug最後の
/label ~bugは「クイックアクション」と呼ばれる特殊な記法で、このテンプレートを使ってIssueを作成すると自動的にbugラベルが付きます。 -
続けて、新機能を提案するときに使うテンプレートも同じフォルダに作ります。
.gitlab/issue_templates/Feature.mdというファイルを作り、次の内容を貼り付けます。## やりたいこと <!-- どんな機能が欲しいかを簡潔に書いてください --> ## 背景・目的 <!-- なぜこの機能が必要なのかを書いてください --> ## 完了条件 <!-- 何ができれば「完了」と言えるかを箇条書きにしてください --> - - /label ~feature/label ~featureのクイックアクションにより、このテンプレートを使うと自動的にfeatureラベルが付きます(3-1でfeatureラベルを作成済みであることが前提です)。 -
2つのファイルができたら、まとめてコミット・プッシュします。
git add .gitlab/issue_templates/Bug.md .gitlab/issue_templates/Feature.md git commit -m "BugとFeature用のIssueテンプレートを追加" git push origin main📝 4章で
mainへのブランチ保護を設定済みの場合は、直接pushできないのでgit checkout -b add-issue-templateのように別ブランチで作業し、MRを経由してマージしてください。 -
GitLabの「Plan」→「Issues」→「New issue」を開くと、Description欄の上に 「Choose a template」 というドロップダウンが表示されるようになります。ここで
BugまたはFeatureを選ぶと、それぞれの内容が自動的に流し込まれます。
3-3. Issueとコードを紐付ける
コミットメッセージやMRの説明文に #1 や Closes #1 のようにIssue番号を書くと、Issueと自動的にリンクされ、マージ時にIssueをクローズできます。
git commit -m "ログイン機能を修正 (Closes #1)"
-
Closes #1と書いた場合: そのMRがmainにマージされたタイミングで、Issue #1が自動的にクローズ(完了扱い)されます - 単に
#1とだけ書いた場合: クローズはされず、「関連するIssue/MR」として相互にリンク表示されるだけになります
「マージされたら自動で閉じてほしい」場合は Closes / Fixes / Resolves のいずれかを付けるのがポイントです。
3-4. Issueを手動でクローズ・再オープンする
自動クローズを使わない場合や、対応不要になった場合は、Issue詳細ページの右上(または下部のコメント欄近く)にある 「Close issue」 ボタンから手動でクローズできます。間違えて閉じた場合は同じ場所に表示される「Reopen issue」で再オープンできます。
3-5. Issueボード
「Plan」→「Issue boards」から、カンバン形式でIssueの進捗を管理できます。
- デフォルトでは「Open」と「Closed」の2列ですが、先ほど作ったラベル(
bugなど)ごとにリストを追加すると、ラベル単位でカードを分けて表示できます - ボード上でカードをドラッグ&ドロップすると、対応するラベルが自動的に付け替えられます
こうした基本的な使い方はFree版でも可能です。
4. ブランチで作業する
機能追加やバグ修正は、main ブランチを直接触らず、作業用ブランチを切るのが基本です。ブランチとは「作業を分岐させて、他の変更に影響を与えずに編集できる場所」のことです。mainはいわば「完成品を置く場所」、ブランチは「自分専用の作業スペース」だとイメージすると分かりやすいです。
4-1. ブランチを作る(UI版 / コマンドライン版)
ブランチの作成は、GitLabの画面からでも、コマンドラインからでもできます。
UI版
- 左サイドバーの 「Code」→「Branches」 を開きます。
- 右上の 「New branch」 をクリックします。
- フォームに次の通り入力します。
-
Branch name 欄に
fix/login-errorと入力 -
Create from は
mainを選択
-
Branch name 欄に
- 「Create branch」 をクリックします。
これでブランチだけがGitLab上に作成されます。あとは2-6で説明した「ファイルをGitLab上で直接編集する」方法でこのブランチ上のファイルを編集すれば、ローカル環境やコマンドラインを一切使わずに作業できます。編集時にブランチを切り替えるのを忘れないよう、画面上部のブランチ名が fix/login-error になっていることを確認してください。
逆に、UIで作ったブランチをローカルでも使いたい場合は、次のコマンドで手元に取得できます。
git fetch origin
git checkout fix/login-error
コマンドライン版
# 1. 新しいブランチを作成して、そこに切り替える
# -b を付けると「作成」と「切り替え」を同時に行う
# ブランチ名は「種類/内容」の形にするのが定番。ここでは fix/login-error と打ちます
git checkout -b fix/login-error
# 2. 今どのブランチにいるか確認したい場合
git branch
# 一覧が表示され、今いるブランチの先頭に * が付く
# 3. README.mdを開いて、適当に1行書き足して保存してください
# (実際の開発ではここでコードを修正しますが、練習として保存するだけでOKです)
# 4. 変更をステージング・コミットする
# 3章で作ったIssue #1に関連する作業なので、コミットメッセージに #1 と入れておく
# (まだ本当のバグ修正ではないので、ここでは Closes ではなく単なる参照の #1 にする)
git add .
git commit -m "ログインページのエラーを修正 (#1)"
# 5. プッシュ: 初めてこのブランチをプッシュするときは -u を付けておくと、
# 次回以降は git push だけで済むようになる
git push -u origin fix/login-error
初めて fix/login-error をプッシュすると、ターミナルに「このブランチからMerge Requestを作成できます」という趣旨のリンクが表示されることがあります。これをそのままブラウザで開けば、次のセクションのMR作成にすぐ進めます。
4-2. mainブランチを保護する(Protected Branches)
「作業用ブランチを切るのが基本」と言っても、うっかり main に直接 git push してしまう、あるいは間違えて main ブランチ自体を削除してしまう、といったミスは誰にでも起こります。これを仕組みとして防ぐのが ブランチ保護(Protected Branches) です。
何ができるようになるか
ブランチ保護を設定すると、指定したブランチ(ここではmain)に対して次のようなルールを強制できます。
-
mainに直接git pushすることを禁止し、必ずMR経由でしか変更できないようにする -
mainブランチ自体を誤って削除できないようにする - 過去のコミット履歴を書き換える
force push(強制プッシュ)を禁止する
この機能はプロジェクトのデフォルトブランチには最初から自動的に適用されており、意図せずコードが壊れたり失われたりするのを防ぐ役割があります。
設定手順
- プロジェクトの
Settings→Repositoryを開く -
Protected branches(またはBranch rules)のセクションを展開 - ブランチ名に
mainを指定 -
Allowed to merge(マージを許可するロール)のドロップダウンからDevelopers + Maintainersを選択 -
Allowed to push and merge(直接pushを許可するロール)を選択-
mainを守りたい場合はここをNo oneにするのがポイント
-
たとえば「Allowed to push」を「No one」に、「Allowed to merge」を「Developers + Maintainers」に設定すると、mainへの変更は必ずMRを経由しなければならなくなります。
-
Protectをクリックして保存
おすすめの設定(個人開発〜小規模チーム向け)
| 項目 | おすすめ設定 |
|---|---|
| Allowed to merge | Developers + Maintainers |
| Allowed to push and merge | No one(直接pushを禁止し、MR経由に統一) |
この設定を入れると、変更が main に入るまでの流れはこうなります。
main への直接pushという「抜け道」を塞いでしまうことで、全員が自然とMR経由のフローに沿うようになります。この設定にしておけば、うっかり main に直接pushしてしまうミスがそもそも起きなくなり、すべての変更がMRを経由するようになるので、これまでのセクションで説明したレビューの流れも自然と徹底されます。
5. マージリクエスト(MR)を作る
MR(Merge Request)は、GitHubでいう「Pull Request」に相当する、変更内容をレビュー・統合するための仕組みです。
5-1. MRの作成手順(UI版 / コマンドライン版)
UI版
- ブランチをプッシュした直後は、GitLab画面上部に 「Create merge request」 というバナーが表示されます。これをクリックするのが一番簡単です。
- バナーが出ていない場合は、左サイドバーの「Code」→「Merge requests」→右上の「New merge request」から進みます。
-
ソースブランチ(自分が変更した
fix/login-errorなど)とターゲットブランチ(通常はmain)を選び、「Compare branches and continue」をクリックします。 - 新規MR作成フォームが開きます。ここで入力する項目は次の通りです。
-
Title: 直前のコミットメッセージ「
ログインページのエラーを修正 (#1)」が自動で入力されます。そのままで構いません -
Description: 何をどう変更したか、なぜ必要かを書きます。次の1行をそのまま入力してください。
(
#1#1は3章で作成したIssueの番号です。ここではまだ本当のバグ修正ではないので、あえてCloses #1ではなく単なる参照の#1にしています。マージしてもIssueはクローズされず、「関連するIssue」としてリンクされるだけです。実際にIssueを閉じるのは、6章で本物のコード修正を行うときです) - Assignee: この変更を最終的に「対応する人」(自分自身でもOK)
- Reviewers: レビューしてほしい相手を指定できます
- Milestone / Labels: プロジェクトで使っていれば設定(個人開発なら省略可)
- 下部の 「Delete source branch when merge request is accepted」 にチェックを入れておくと、マージ後に作業ブランチを自動削除できて便利です
-
Title: 直前のコミットメッセージ「
- 内容を確認したら、下部の 「Create merge request」 ボタンをクリックします。
これでMRが作成され、MRの詳細ページに移動します。
コマンドライン版
fix/login-error ブランチにいる状態(プッシュ済み)で、glabを使ってMRを作成します。
glab mr create \
--title "ログインページのエラーを修正 (#1)" \
--description "#1" \
--target-branch main \
--remove-source-branch
-
--target-branch main: マージ先のブランチを指定 -
--remove-source-branch: マージ後に作業ブランチを自動削除する、UI版の「Delete source branch」チェックと同じ設定 - 対話形式で作りたい場合は、オプションを付けずに
glab mr createとだけ実行すると、タイトルや説明を聞かれながら作成できます
実行するとMRが作成され、ターミナルにMRのURLが表示されます。
5-2. MRでできること(Free版で利用可能)
- 差分(Diff)の確認とインラインコメント(コードの特定の行にコメントを付けられる)
- レビュアーのアサイン
- ✅ マージ前のCI/CDパイプライン結果の確認(月400分の範囲内)
- Draft(下書き)MRとして、作業途中でも早めに共有(タイトルの先頭に
Draft:と付けるか、作成時に「Mark as draft」を選ぶ)
レビュアーがコメントを付けた場合は、該当箇所を修正して再度 git push origin fix/login-error するだけで、同じMRに新しいコミットが追加されます(MRを作り直す必要はありません)。
5-3. マージする(UI版 / コマンドライン版)
UI版
レビューが完了し、必要な数のCI/CDパイプラインが通ったら、MR詳細ページ上部に表示される 「Merge」 ボタンをクリックします。
- ボタンが押せない(グレーアウトしている)場合は、パイプラインが失敗している、またはコメントのスレッドが未解決のままになっている可能性があります。該当箇所を確認してください。
- マージが完了すると、変更内容が
mainブランチに取り込まれ、MRのステータスが「Merged」になります。
コマンドライン版
fix/login-error ブランチにいる状態で、次のコマンドを実行します。
glab mr merge
MR番号を省略すると、今いるブランチに対応するMRを自動的に見つけてマージしてくれます。UIと同様に、パイプラインが失敗していたり承認が足りなかったりする場合はマージがブロックされ、エラーメッセージが表示されます。番号を指定してマージしたい場合は glab mr merge <MR番号> のように書きます。
5-4. MRテンプレートを用意する
3章でIssue用のテンプレートを作りましたが、同じ仕組みがMRにもあります。毎回同じ項目(変更内容・関連Issue・確認事項など)を書くなら、テンプレート化しておくと入力の手間が減り、レビュアーが見るべき情報も揃います。
-
ローカルのプロジェクトフォルダで、テンプレート用のフォルダを作ります。
mkdir -p .gitlab/merge_request_templates -
.gitlab/merge_request_templates/Default.mdというファイルを作り、次の内容をそのまま貼り付けます。ファイル名をDefault.md(大文字小文字は問いません)にしておくと、新しいMRを作るときに毎回自動でこの内容が入るようになります。## 変更内容 <!-- 何をどう変更したかを書いてください --> ## 関連Issue Closes # ## 確認事項 - [ ] ローカルでテストが通ることを確認した - [ ] 影響範囲を確認した - [ ] レビュアーをアサインした -
変更をコミットしてプッシュします。
git add .gitlab/merge_request_templates/Default.md git commit -m "MRのデフォルトテンプレートを追加" git push origin main📝 4章でブランチ保護を設定済みの場合は
mainに直接pushできないので、別ブランチで作業し、MRを経由してマージしてください。 -
以降、「New merge request」を開くと、Description欄に自動的にこの内容が入った状態になります。
Closes #の後ろにIssue番号(例:1)を書き足すだけで、5-1で説明した紐付けができます。
Default.md 以外の名前(例: Hotfix.md)でファイルを作ると、Issueテンプレートのときと同様に「Choose a template」ドロップダウンから選んで使う、複数種類のテンプレートも用意できます。緊急対応用・通常のリリース用など、状況に応じて使い分けたい場合はこちらが便利です。
6. CI/CDパイプラインを組んでみる
ここまでの章で「MRが承認されたらマージする」という流れを扱いましたが、実際の開発ではマージ前に自動でテストを実行したいものです。それを行う仕組みが CI/CD パイプライン で、.gitlab-ci.yml という1つの設定ファイルで組み立てます。
6-1. しくみのイメージ
-
stage(ステージ):
test→buildのような実行の「順番」の区切り - job(ジョブ): 各ステージの中で実際に実行する処理のかたまり
- なお「パイプラインが成功しないとマージできない」ようにする設定(
Pipelines must succeed)は6-5で扱います。今の時点ではまだ有効にしていなくても問題ありません
6-2. 最初の .gitlab-ci.yml を作る(UI版 / ローカル版)
UI版(Pipeline Editor)
ローカルでファイルを作らず、ブラウザだけで.gitlab-ci.ymlを作成・編集することもできます。
-
左サイドバーの 「Build」→「Pipeline editor」 を開きます。
-
まだ
.gitlab-ci.ymlが存在しない場合は、「Configure pipeline」ボタンが表示されるのでクリックすると、サンプル設定が入った状態でエディタが開きます。 -
エディタ画面に、次の内容を貼り付けます。
stages: - test - build test_job: stage: test script: - echo "テストを実行しています" - echo "1 + 1 は $((1 + 1)) です" build_job: stage: build script: - echo "ビルドを実行しています"入力しながら文法エラーがあれば画面上部にリアルタイムで表示されます。
-
画面上部の 「Validate」タブ で、実際にパイプラインを動かす前に設定内容をシミュレーションできます。
-
下部の 「Commit changes」 から、コミットメッセージとコミット先のブランチ(
add-ci-configなど)を指定してコミットします。
Pipeline Editorは、YAMLの文法チェックをその場でしてくれるので、インデントミスなどに気づきやすいのが利点です。以降の章でも、ローカルでファイルを編集する代わりにこの画面を使って進めることができます。
内容の意味は次の通りです。
-
stages:でtest→buildの順に実行することを宣言 -
test_jobはtestステージに属し、script:に書いたコマンドを上から順に実行 -
build_jobはbuildステージに属し、test_jobがすべて成功した後に実行される
ローカルで作成する場合
プロジェクトのルート(README.mdと同じ階層)に、.gitlab-ci.yml という名前で上記と同じ内容のファイルを作ります。ファイル名の先頭のドットを忘れないでください。
6-3. ブランチを切ってMR経由で追加する
4〜5章で説明した「作業はブランチを切ってMR経由でmainに入れる」という流れは、.gitlab-ci.yml を追加するときも同じです。ここで一度その流れをおさらいしておきます。
git checkout -b add-ci-config
git add .gitlab-ci.yml
git commit -m "CI/CDパイプラインを追加"
git push -u origin add-ci-config
プッシュ後、5章の手順と同じように GitLab 上で 「Create merge request」 からMRを作成してください。タイトルは自動入力のままで構いません。
📝 4章で
mainへのブランチ保護(Allowed to push and merge: No one)を設定していない場合は、git push origin mainと直接pushしても動作します。ただし以降もこのブランチ+MRの流れで統一して進めます。
6-4. パイプラインの結果を確認してからマージする
- 作成したMRの詳細ページを開くと、5章では出てこなかった パイプラインの状態(黄色い丸=実行中 → 緑のチェック=成功) がMR画面上部に表示されます。
- その部分をクリックすると、
test→buildの2つのステージと、それぞれの中のtest_job・build_jobが図で表示されます。 - ジョブ名(例:
test_job)をクリックすると、scriptに書いたコマンドの実行ログ(今回なら「テストを実行しています」などの出力)が確認できます。 - 全ジョブが緑になったら、5-3で説明した通り 「Merge」 ボタンをクリックして
mainに取り込みます。
もし左サイドバーから直接確認したい場合は、「Build」→「Pipelines」 でも同じ一覧を見られます。
これで、コードをプッシュするたびに自動でチェックが走る状態になりました。次の節では、echoだけのおもちゃではなく、実際にコードのテストを実行する例をやってみます。
6-5. パイプラインの成功をマージの必須条件にする
今のままだと、パイプラインが赤(失敗)でもMRの「Merge」ボタンは押せてしまいます。せっかくテストを自動化したので、「テストが通らない限りマージできない」ように設定しておきましょう。
- プロジェクトの
Settings→Merge requestsを開く -
Merge checksのセクションを探す - 「Pipelines must succeed」 にチェックを入れる(Maintainer以上の権限が必要)
- 「Save changes」で保存
これで以降、パイプラインが失敗しているMRは「Merge」ボタンがグレーアウトして押せなくなります。次の節以降で実際にテストを失敗させてみるので、この設定がどう効いてくるかを実感できるはずです。
6-6. カバレッジ(Coverage)とは何か
テストが「通るか失敗するか」に加えて、「どれくらいのコードがテストされているか」を測る指標が カバレッジ(網羅率) です。「全体のうち何%の行がテストで実行されたか」を数値化したものだと考えてください。
GitLabには、カバレッジを扱う仕組みが2つあります。
-
coverage:キーワード(正規表現): テストツールのログ出力からカバレッジの数値(例:87%)を抜き出し、MRのウィジェットやパイプラインのジョブ一覧にパーセンテージ表示するだけの仕組み -
artifacts: reports: coverage_report: Cobertura形式などのXMLレポートを使って、MRの差分(Diff)画面に行単位で緑(カバー済み)/赤(未カバー)の色付けをする仕組み
この2つは別々の設定なので、両方使うと「MRを開いた瞬間にパーセンテージが見え、差分を開けばどの行がテストされていないかも一目で分かる」状態になります。以降の各言語の例では、両方とも設定します。
📝 以降のPython/Node.js/Ruby/Go/Cの例は、同じプロジェクトで5つ全部を試す想定ではなく、普段使っている言語を1つ選んで進める想定で書いています(
.gitlab-ci.ymlを言語ごとに丸ごと置き換える書き方になっているため、複数の言語を同じプロジェクトに順にマージすると、後からマージした言語の設定で上書きされてしまいます)。
6-7. もっと実践的な例: 実際にテストを走らせる(Python)
ここまでのパイプラインは「メッセージを表示するだけ」でした。ここからは、これまでの題材である「ログインページのエラー」を、実際に動くコードとテストで再現します。ここでは4〜5章と同じブランチ+MRの流れに沿って進め、「パイプラインが赤いとマージできない」ことも実際に確認します。
1. 作業用ブランチを作る
git checkout -b add-login-check
2. バグを含んだコードを用意する
プロジェクトのルートに login.py というファイルを作り、次の内容をそのまま貼り付けます(あえてバグを埋め込んであります)。
def check_login(username: str, password: str) -> bool:
"""ユーザー名とパスワードが正しければTrueを返す"""
correct_username = "admin"
correct_password = "password123"
# バグ: パスワードの比較を書き忘れている(ユーザー名だけで判定してしまっている)
if username == correct_username:
return True
return False
3. テストコードを用意する
同じくルートに test_login.py を作り、次の内容を貼り付けます。
from login import check_login
def test_login_success():
assert check_login("admin", "password123") is True
def test_login_wrong_password():
# パスワードが違うのにログインできてしまったら、このテストは失敗するはず
assert check_login("admin", "wrong-password") is False
4. .gitlab-ci.yml をPython用に書き換える
先ほど作った .gitlab-ci.yml の中身を、次の内容で丸ごと置き換えます。
image: python:3.12-slim
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
stages:
- lint
- test
before_script:
- pip install --cache-dir "$PIP_CACHE_DIR" pytest pytest-cov flake8
lint_job:
stage: lint
script:
- flake8 --max-line-length=100 login.py test_login.py
test_job:
stage: test
script:
- pytest -v --junitxml=report.xml --cov=. --cov-report=xml --cov-report=term
coverage: '/TOTAL.+?(\d+\%)$/'
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
内容の補足です。
-
image: python:3.12-slim: GitLabのジョブをPython 3.12入りのコンテナ上で実行する、という指定 -
cache:とPIP_CACHE_DIR:pip installでダウンロードしたパッケージをキャッシュし、次回以降のパイプラインを高速化する -
before_script:: どのジョブでも共通で最初に実行される処理(ここでは必要なパッケージのインストール) -
lint_job:flake8でコードの書き方に問題がないかチェック -
test_job:pytestで先ほどのテストコードを実行し、--junitxml=report.xmlでJUnit形式のレポートを出力 -
--cov=. --cov-report=xml --cov-report=term:pytest-covによるカバレッジ計測。--cov-report=termはターミナル(ログ)にサマリーを表示し、--cov-report=xmlはCobertura互換のcoverage.xmlを出力する -
coverage: '/TOTAL.+?(\d+\%)$/': ログ出力のTOTAL行から数値を正規表現で抜き出し、MRやジョブ一覧にパーセンテージ表示する設定 -
artifacts: reports: junit:/coverage_report:: 出力したXMLをGitLabに渡し、MR画面の「Test summary」やDiff上の行単位カバレッジ表示に反映させるための設定(when: alwaysを付けておくと、テストが失敗したときもレポートがアップロードされる)
5. コミット・プッシュしてMRを作る
git add login.py test_login.py .gitlab-ci.yml
git commit -m "ログインチェック機能とテストを追加 (#1)"
git push -u origin add-login-check
📝
#1はIssue #1への参照です。まだ本当のバグ修正が終わっていない段階なので、ここでは(マージ時にクローズする)Closes #1ではなく、単に関連付けるだけの#1にしています。
プッシュ後、GitLab上でMRを作成してください(5章の手順と同じです)。
6. パイプラインが赤くなり、マージがブロックされることを確認する
MRの詳細画面を開くと、test_job が 赤い✕(失敗) になっているはずです。ジョブをクリックしてログを見ると、test_login_wrong_password が失敗していることが分かります。これはlogin.pyのバグ(パスワードチェックの書き忘れ)を、テストが正しく検知した状態です。
先ほど artifacts: reports: junit を設定したので、ログを開かなくても確認できます。MRの詳細画面を下にスクロールすると 「Test summary」 というパネルが表示され、失敗したテスト名(test_login_wrong_password)とエラー内容がそのまま一覧表示されます。テスト名をクリックすると、実行時間やエラー出力の詳細もモーダルで確認できます。
さらに、MRの「Changes」タブ(差分表示)を開くと、login.pyの各行の左側に緑(テストでカバーされている行)や赤(カバーされていない行)の色が付いているのが見えるはずです。これがcoverage_reportの効果です。パイプラインのジョブ一覧やMRウィジェットにも、カバレッジのパーセンテージが表示されます。
さらに、6-5で「Pipelines must succeed」を有効にしているので、MR画面の 「Merge」ボタンがグレーアウトして押せなくなっている はずです。これが「テストが通らないとマージできない」を実際に体験した瞬間です。
7. バグを直して、パイプラインを緑にする
login.py の中身を、次のように修正します。
def check_login(username: str, password: str) -> bool:
"""ユーザー名とパスワードが正しければTrueを返す"""
correct_username = "admin"
correct_password = "password123"
# 修正: パスワードもあわせてチェックする
if username == correct_username and password == correct_password:
return True
return False
修正できたら、同じブランチのまま、もう一度コミット・プッシュします。今度こそ本当にバグが直るので、Closes #1 を使います。
git add login.py
git commit -m "ログインチェックでパスワードも検証するよう修正 (Closes #1)"
git push origin add-login-check
新しいコミットがプッシュされると、同じMRの中でパイプラインが再実行されます。lint_job・test_job の両方が 緑のチェック(成功) になり、グレーアウトしていた「Merge」ボタンが押せるようになります。「Test summary」パネルも「2 passed, 0 failed」のような表示に変わります。
8. マージする
「Merge」ボタンをクリックしてください。マージが完了すると、Closes #1 の効果でIssue #1が自動的にクローズされます。
これが、この章の最初に説明した「パイプラインが失敗するとマージできない」という仕組みの、実際の使われ方です。テストコードがバグを自動的に見つけてくれるので、「レビュアーが目視で気づく」前に、機械的に不具合を検知できるようになります。
以下、同じ「ログインチェックのバグを直す」という題材を、他の言語でもやってみます。使っている言語があれば、そちらを参考にしてください(Pythonの節で説明した「ブランチを切る→バグを仕込む→MRを作る→赤で止まる→直す→緑になってマージできる」という流れは共通なので、以下は要点のみ書いています)。
6-8. 同じ例をNode.js/JavaScriptでやる場合
(git checkout -b add-login-check-js のようにブランチを切ってから進めてください。以下は要点のみです。)
1. package.json を作る
{
"name": "login-check",
"version": "1.0.0",
"scripts": {
"test": "jest --ci --coverage --reporters=default --reporters=jest-junit"
},
"jest": {
"coverageReporters": ["text", "cobertura"]
},
"devDependencies": {
"jest": "^29.0.0",
"jest-junit": "^16.0.0"
}
}
jest-junit は、Jestのテスト結果をJUnit XML形式で出力するためのパッケージです。--coverage を付けるとカバレッジ計測が有効になり、jest.coverageReporters で出力形式(ターミナル表示用のtextと、GitLab連携用のcobertura)を指定しています。
2. login.js(バグ入り)
function checkLogin(username, password) {
const correctUsername = "admin";
const correctPassword = "password123";
// バグ: パスワードの比較を書き忘れている
if (username === correctUsername) {
return true;
}
return false;
}
module.exports = { checkLogin };
3. login.test.js
const { checkLogin } = require("./login");
test("正しいユーザー名とパスワードならtrue", () => {
expect(checkLogin("admin", "password123")).toBe(true);
});
test("パスワードが違うならfalse", () => {
expect(checkLogin("admin", "wrong-password")).toBe(false);
});
4. .gitlab-ci.yml
image: node:20
cache:
paths:
- .npm/
variables:
JEST_JUNIT_OUTPUT_NAME: "report.xml"
before_script:
- npm ci --cache .npm --prefer-offline
stages:
- test
test_job:
stage: test
script:
- npm test
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
JEST_JUNIT_OUTPUT_NAME で出力ファイル名を指定し、artifacts: reports: junit / coverage_report でGitLabに渡しています。coverage: の正規表現は、Jestがターミナルに表示するAll filesのサマリー行からパーセンテージを抜き出しています。
ローカルに package-lock.json がない場合は npm ci の前に一度 npm install を実行してファイルを作り、それもコミットしておいてください。
5. コミット・プッシュしてMRを作る
git add package.json login.js login.test.js .gitlab-ci.yml
git commit -m "ログインチェック機能とテストを追加 (#1)"
git push -u origin add-login-check-js
プッシュ後、GitLab上でMRを作成してください。test_job が失敗し、MR画面の「Test summary」パネルに失敗したテスト名が表示され、「Merge」ボタンが押せないことを確認したら、Pythonの例と同様に checkLogin の中身を username === correctUsername && password === correctPassword に修正し、Closes #1 を付けて同じブランチに再度プッシュします。パイプラインが緑になったら「Merge」でマージします。
6-9. 同じ例をRubyでやる場合
(git checkout -b add-login-check-rb のようにブランチを切ってから進めてください。以下は要点のみです。)
1. login.rb(バグ入り)
def check_login(username, password)
correct_username = "admin"
correct_password = "password123"
# バグ: パスワードの比較を書き忘れている
username == correct_username
end
2. login_spec.rb
require "simplecov"
require "simplecov-cobertura"
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
SimpleCov.start
require "rspec"
require_relative "login"
RSpec.describe "#check_login" do
it "正しいユーザー名とパスワードならtrueを返す" do
expect(check_login("admin", "password123")).to eq(true)
end
it "パスワードが違うならfalseを返す" do
expect(check_login("admin", "wrong-password")).to eq(false)
end
end
simplecov はRubyの定番カバレッジ計測ツールです。計測対象のファイル(login.rb)を読み込むより前にSimpleCov.startを呼ぶ必要があるので、一番上に書きます。simplecov-coberturaはその結果をCobertura形式で出力するためのプラグインです。
3. .gitlab-ci.yml
image: ruby:3.3
before_script:
- gem install rspec rspec_junit_formatter simplecov simplecov-cobertura
stages:
- test
test_job:
stage: test
script:
- rspec login_spec.rb --format RspecJunitFormatter --out report.xml
coverage: '/\(\d+\.\d+\%\) covered/'
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage/coverage.xml
rspec_junit_formatter というgemを使うと、RSpecの結果をJUnit XML形式で出力できます。coverage: の正規表現は、simplecovがターミナルに出す「XX.XX% covered」という表示から数値を抜き出しています(Gemfileやbundlerを使った本格的な構成にする場合は、bundle installをbefore_scriptに足してください)。
4. コミット・プッシュしてMRを作る
git add login.rb login_spec.rb .gitlab-ci.yml
git commit -m "ログインチェック機能とテストを追加 (#1)"
git push -u origin add-login-check-rb
プッシュ後、GitLab上でMRを作成してください。test_job が失敗し、MR画面の「Test summary」パネルに失敗したテスト名が表示され、「Merge」ボタンが押せないことを確認したら、username == correct_username && password == correct_password に修正し、Closes #1 を付けて同じブランチに再プッシュ、緑になったらマージします。
6-10. 同じ例をGoでやる場合
(git checkout -b add-login-check-go のようにブランチを切ってから進めてください。以下は要点のみです。)
1. go.mod
module login-check
go 1.22
2. login.go(バグ入り)
package main
func checkLogin(username, password string) bool {
correctUsername := "admin"
correctPassword := "password123"
// バグ: パスワードの比較を書き忘れている
if username == correctUsername {
return true
}
return false
}
3. login_test.go
package main
import "testing"
func TestLoginSuccess(t *testing.T) {
if !checkLogin("admin", "password123") {
t.Error("正しいユーザー名とパスワードでtrueになるはずが、falseが返った")
}
}
func TestLoginWrongPassword(t *testing.T) {
if checkLogin("admin", "wrong-password") {
t.Error("パスワードが違うのにtrueが返ってしまった")
}
}
4. .gitlab-ci.yml
image: golang:1.22
stages:
- test
test_job:
stage: test
script:
- go install gotest.tools/gotestsum@latest
- go install github.com/boumenot/gocover-cobertura@latest
- gotestsum --junitfile report.xml -- -cover -coverprofile=coverage.out ./...
- gocover-cobertura < coverage.out > coverage.xml
coverage: '/coverage:\s*\d+\.\d+% of statements/'
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
Goの標準テストコマンド go test は、そのままではJUnit形式を出力できません。そこで gotestsum という定番ツールを使い、テスト結果をJUnit XMLに変換して出力します。カバレッジは -coverprofile=coverage.out で計測し、gocover-cobertura というツールでCobertura形式に変換しています。coverage: の正規表現は、go testが出力する「coverage: XX.X% of statements」という行から数値を抜き出しています。
5. コミット・プッシュしてMRを作る
git add go.mod login.go login_test.go .gitlab-ci.yml
git commit -m "ログインチェック機能とテストを追加 (#1)"
git push -u origin add-login-check-go
プッシュ後、GitLab上でMRを作成してください。test_job が失敗し、MR画面の「Test summary」パネルに失敗したテスト名が表示され、「Merge」ボタンが押せないことを確認したら、if username == correctUsername && password == correctPassword に修正し、Closes #1 を付けて同じブランチに再プッシュ、緑になったらマージします。
6-11. 同じ例をCでやる場合
(git checkout -b add-login-check-c のようにブランチを切ってから進めてください。以下は要点のみです。)
Cには言語標準のテストフレームワークはありませんが、cmocka という、モックにも対応した定番のテスト用ライブラリがあります。JUnit形式のXML出力にも標準対応しているので、今回はこれを使います。
1. login.c(バグ入り)
#include <stdbool.h>
#include <string.h>
bool check_login(const char *username, const char *password) {
const char *correct_username = "admin";
const char *correct_password = "password123";
// バグ: パスワードの比較を書き忘れている
if (strcmp(username, correct_username) == 0) {
return true;
}
return false;
}
2. login.h
#ifndef LOGIN_H
#define LOGIN_H
#include <stdbool.h>
bool check_login(const char *username, const char *password);
#endif
3. test_login.c
cmockaは、stdarg.h・stddef.h・setjmp.h・cmocka.h の順にインクルードする必要があります(この順番を守らないとコンパイルエラーになります)。
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include "login.h"
static void test_login_success(void **state) {
(void) state; /* 未使用 */
assert_true(check_login("admin", "password123"));
}
static void test_login_wrong_password(void **state) {
(void) state; /* 未使用 */
assert_false(check_login("admin", "wrong-password"));
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_login_success),
cmocka_unit_test(test_login_wrong_password),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
assert_true / assert_false はcmockaが提供するアサーション関数で、失敗すると詳細なメッセージを出力しつつテストを失敗扱いにしてくれます(生のassertよりずっとテストらしい書き方です)。
4. .gitlab-ci.yml
image: gcc:latest
stages:
- test
test_job:
stage: test
before_script:
- apt-get update -qq && apt-get install -y -qq libcmocka-dev gcovr
script:
- gcc --coverage -o test_login login.c test_login.c -lcmocka
- CMOCKA_MESSAGE_OUTPUT=xml CMOCKA_XML_FILE=report.xml ./test_login
- gcovr --root . --cobertura coverage.xml --print-summary
coverage: '/^lines:\s*(\d+\.\d+)\%/'
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
-
apt-get install libcmocka-dev: cmockaライブラリをインストール -
gcc --coverage ... -lcmocka: コンパイル時にcmockaをリンクしつつ、--coverageでカバレッジ計測用の情報を埋め込む -
CMOCKA_MESSAGE_OUTPUT=xml/CMOCKA_XML_FILE=report.xml: cmockaの出力形式をJUnit XMLに切り替え、出力先ファイルを指定する環境変数 -
gcovr --cobertura coverage.xml --print-summary: gcovr というツールで、--coverage付きコンパイルが生成したカバレッジ情報を集計し、Cobertura形式のXMLとターミナル向けのサマリーの両方を出力する -
coverage: '/^lines:\s*(\d+\.\d+)\%/': gcovrのサマリーに出る「lines: XX.X%」という行から数値を抜き出す
5. コミット・プッシュしてMRを作る
git add login.c login.h test_login.c .gitlab-ci.yml
git commit -m "ログインチェック機能とテストを追加 (#1)"
git push -u origin add-login-check-c
プッシュ後、GitLab上でMRを作成してください。test_job が失敗し、MR画面の「Test summary」パネルに失敗したテスト名が表示され、「Merge」ボタンが押せないことを確認したら、if (strcmp(username, correct_username) == 0 && strcmp(password, correct_password) == 0) に修正し、Closes #1 を付けて同じブランチに再プッシュ、緑になったらマージします。
6-12. カバレッジの目標値を決めて、下回ったら失敗させる
6-6で設定した coverage: は、あくまで数値を表示するだけです。カバレッジが低くてもパイプライン自体は成功してしまいます。「〇〇%を下回ったらマージさせない」ようにするには、テストツール側に「閾値を下回ったら失敗扱いにする」設定を追加する必要があります。この設定と6-5の「Pipelines must succeed」を組み合わせることで、初めて実際の強制力を持ちます。
言語ごとの設定方法は次の通りです。目標値は例として80%にしています(プロジェクトの状況に応じて調整してください)。
Python: pytest-cov の --cov-fail-under
6-7のtest_jobのscriptを次のように変更します。
test_job:
stage: test
script:
- pytest -v --junitxml=report.xml --cov=. --cov-report=xml --cov-report=term --cov-fail-under=80
--cov-fail-under=80 を付けると、カバレッジが80%未満のときにpytest自体が0以外の終了コードを返すようになり、ジョブが失敗します。
Node.js: Jestの coverageThreshold
6-8のpackage.jsonに、次の設定を追加します。
{
"jest": {
"coverageReporters": ["text", "cobertura"],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
coverageThreshold を設定すると、指定した割合をどれか1つでも下回った場合にJestがテスト失敗として終了します。
Ruby: SimpleCovの minimum_coverage
6-9のlogin_spec.rb冒頭のSimpleCov設定に、1行追加します。
require "simplecov"
require "simplecov-cobertura"
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
SimpleCov.minimum_coverage 80
SimpleCov.start
SimpleCov.minimum_coverage 80 を設定すると、カバレッジが80%未満のときにSimpleCovが0以外の終了コードでプロセスを終わらせます。
Go: 自前でチェックするスクリプトを足す
Goの go test にはカバレッジ閾値の機能が標準では無いので、6-10の.gitlab-ci.ymlに閾値チェック用のコマンドを1行足します。
test_job:
stage: test
script:
- go install gotest.tools/gotestsum@latest
- go install github.com/boumenot/gocover-cobertura@latest
- gotestsum --junitfile report.xml -- -cover -coverprofile=coverage.out ./...
- gocover-cobertura < coverage.out > coverage.xml
- |
COVERAGE=$(go tool cover -func=coverage.out | grep total: | awk '{print $3}' | tr -d '%')
echo "カバレッジ: ${COVERAGE}%"
awk -v cov="$COVERAGE" 'BEGIN { if (cov+0 < 80) { print "❌ カバレッジが80%未満です"; exit 1 } }'
go tool cover -func の出力からtotal:の行の数値を取り出し、awkで80未満かどうかを判定して、下回っていればexit 1でジョブを失敗させています。
C: gcovrの --fail-under-line
6-11の.gitlab-ci.ymlのgcovrコマンドに、オプションを1つ追加します。
script:
- gcc --coverage -o test_login login.c test_login.c -lcmocka
- CMOCKA_MESSAGE_OUTPUT=xml CMOCKA_XML_FILE=report.xml ./test_login
- gcovr --root . --cobertura coverage.xml --print-summary --fail-under-line 80
--fail-under-line 80 を付けると、行カバレッジが80%未満のときにgcovrが0以外の終了コードで終了します。
6-13. 動作確認のしかた
どの言語でも確認方法は共通です。
- わざとテストを減らす、あるいは
if文の片方の分岐だけテストするなど、意図的にカバレッジを下げるコードをプッシュしてみる - パイプラインが失敗し、「Merge」ボタンが押せなくなることを確認する
- テストを追加してカバレッジを閾値以上に戻し、緑になることを確認する
これで、「テストが通るだけでは不十分で、一定以上テストされていることまでをマージの条件にする」という、より実践的な運用ができるようになりました。
7. 応用: IssueとMRの紐付けを必須化する
チームで運用していると、「Issueに紐づいていないMRはマージさせたくない」というニーズが出てきます。トレーサビリティ(変更履歴の追跡可能性)を確保したい場合などによく挙がる要望です。
7-1. 前提: ネイティブな「必須化」機能はない
実はGitLabには標準で「MRにIssueのリンクを必須にする」という設定はそのまま用意されていません。GitLab公式のissueトラッカーでも、これはまだ実装待ちの機能要望として上がっている段階です。
MR承認を必須にする「Required Approvals」も、Free版では使えない機能です。Free版では承認自体は可能でも、承認なしでのマージを止めることはできません。
7-2. Free版でも使える回避策
Free版で唯一の現実的な方法は、CI/CDジョブでチェック + 「Pipelines must succeed」設定 の組み合わせです。「Pipelines must succeed」自体は6-5で既に有効化済みなので、ここでは追加のCI/CDジョブだけを組み込みます。
.gitlab-ci.yml にIssue番号チェックのジョブを追加する
check_issue_link:
stage: .pre
script:
- |
if [[ ! "$CI_MERGE_REQUEST_TITLE $CI_MERGE_REQUEST_DESCRIPTION" =~ \#[0-9]+ ]]; then
echo "❌ MRのタイトルか説明にIssue番号(例: #1)を含めてください"
exit 1
fi
echo "✅ Issue番号を確認しました"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
これで、MRのタイトルまたは説明に #1 のようなIssue番号がない場合はパイプラインが失敗し、結果としてマージがブロックされます。
7-3. この方法の弱点(知っておくべき注意点)
この回避策には既知の弱点があります。
- タイトル変更後に再チェックされない: パイプラインはMR作成・更新のタイミングで走るため、チェック通過後にMRタイトルだけを変更してもパイプラインは再実行されず、チェックをすり抜けたまま承認・マージされてしまうケースがあります。
- チェック用ジョブ自体がCI/CDの分数を消費する: Free版は月400分という制限があるため、チェックジョブの実行回数が積み重なると地味に消費します。
厳密な統制が必要な場合は、この回避策の限界を理解した上で運用してください。
8. タグを打ってリリースを作る
ここまでで機能追加やバグ修正のサイクルは一通り体験しました。ここからは、「このコミットの状態をv1.0.0として区切りたい」というときに使う タグ(Tag) と、GitLabの Release 機能を扱います。
8-1. Gitのタグとは何か
タグは、特定のコミットに付ける「目印」です。ブランチと違って、タグを打った後もそのコミットを指したまま動きません(ブランチは新しいコミットを積むと指し先が進みますが、タグは固定されます)。「このコミットがバージョン1.0.0です」という印を残すために使います。
タグには2種類あります。
- 軽量タグ(lightweight tag): コミットに名前を付けるだけの、単純なポインタ
- 注釈付きタグ(annotated tag): 作成者・日付・メッセージなどの情報を持つ、独立したオブジェクト。リリース用のタグには基本的にこちらを使います
8-2. タグを打つ(UI版 / コマンドライン版)
3〜6章でマージしてきた変更が main に入っている状態から、タグを作成します。コマンドラインからでも、GitLabの画面からでも作れます。どちらでも結果は同じです。
UI版
- 左サイドバーの 「Code」→「Tags」 を開きます。
- 右上の 「New tag」 をクリックします。
- フォームに次の通り入力します。
-
Tag name 欄に
v1.0.0と入力 -
Create from は
mainを選択(ブランチ名やコミットSHAを指定できます) -
Message 欄に
ログインエラー修正を含む最初のリリースと入力(ここに入力すると注釈付きタグになります。空欄のままだと軽量タグになります) - Release notes 欄は、8-4で説明するReleaseの内容を兼ねて入力できます(後からでも追加・編集可能です)
-
Tag name 欄に
- 「Create tag」 をクリックします。
UIから作成したタグは、コマンドラインでgit pushする場合と違って、GitLab上で作成した時点でプッシュ操作は不要です(サーバー側で直接作られるため)。手元のパソコンで同じタグを使いたい場合は、git fetch --tags でローカルに取得できます。
コマンドライン版
# mainブランチにいることを確認してから実行してください
git checkout main
git pull origin main
# 1. 注釈付きタグを作成する
# -a で注釈付きタグを指定し、-m でメッセージを付ける
git tag -a v1.0.0 -m "ログインエラー修正を含む最初のリリース"
# 2. タグが作成されたか確認する
git tag
# 3. タグをGitLabにプッシュする
# タグは普通のgit pushだけでは送信されないので、明示的に指定する
git push origin v1.0.0
git push origin main を実行しても、タグは自動的には送られません。タグ専用に git push origin <タグ名> を実行するか、そのブランチの全タグをまとめて送りたい場合は git push origin --tags を使います。
8-3. GitLabのReleaseとは何か
GitLabの Release は、タグに「リリースノート」「関連リンク」「ビルド成果物(アセット)」などの情報を紐付けたものです。タグそのものは「印」でしかありませんが、Releaseにすることで「このバージョンで何が変わったか」を人が読める形で残せます。
8-4. Releaseを作る(UI版 / コマンドライン版)
UI版
-
左サイドバーの 「Deploy」→「Releases」 を開きます。
-
「New release」をクリックします。
-
Tag name のドロップダウンから、先ほど作成した
v1.0.0を選択します。 -
Release title に
v1.0.0などの分かりやすい名前を入力します。 -
Release notes に、そのバージョンの変更内容をMarkdownで書きます。
## 変更点 - ログインページでパスワードチェックが正しく行われず、ログインできてしまう不具合を修正 (#1) -
「Create release」をクリックします。
これで「Deploy」→「Releases」の一覧に、タグ・リリースノート・作成日時が並んだリリースページが表示されるようになります。
コマンドライン版
コマンドラインから操作したい場合は、3章で導入した glab を使います。
# 初回だけ: GitLabアカウントと連携する
glab auth login
# リリースを作成する(対話形式で質問に答えていく場合)
glab release create v1.0.0
# メッセージを直接指定して非対話で作成する場合
glab release create v1.0.0 --notes "ログインエラー修正を含む最初のリリース"
すでに8-2でタグv1.0.0を作成済みなので、glab release createはそのタグに対してReleaseを作成します(タグがまだ無い場合は、このコマンドが新規にタグも作成してくれます)。
8-5. .gitlab-ci.yml でリリースを自動化する
毎回手動でRelease画面から入力するのではなく、「タグがプッシュされたら自動でReleaseを作る」ようにもできます。6章で使ってきた.gitlab-ci.ymlに、次のジョブを追加します。
release_job:
stage: .post
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: '$CI_COMMIT_TAG'
script:
- echo "タグ $CI_COMMIT_TAG のリリースを作成します"
release:
tag_name: '$CI_COMMIT_TAG'
name: 'Release $CI_COMMIT_TAG'
description: 'CI/CDから自動生成されたリリースです。'
-
rules: - if: '$CI_COMMIT_TAG': このジョブは、タグに対するパイプラインが実行されたときだけ動く、という条件 -
release:セクション : リリースの内容(タグ名・リリース名・説明)を指定する、GitLab CI/CD専用のキーワード -
$CI_COMMIT_TAG: パイプラインが実行されたタグ名が自動的に入る、GitLabが用意する変数
この状態で git push origin v1.0.0 のようにタグをプッシュすると、パイプラインが起動し、release_jobが実行された後に自動的にReleaseが作成されます。以降のバージョンでも、タグを打ってプッシュするだけでReleaseが作られるようになります。
9. Free版の制限まわりの注意点
個人開発者としてFree版を使う上で知っておきたい制限です。
| 項目 | 制限内容 |
|---|---|
| グループあたりのユーザー数 | 最大5名(個人ネームスペースは対象外) |
| CI/CDコンピュート時間 | 月400分(SaaS版) |
| ストレージ | 10GiB |
| トップレベルグループ数 | 2026年1月27日以降作成のアカウントは3つまで |
個人利用やちょっとしたOSS活動であれば、これらの制限に達することはあまり多くありません。CI/CDの分数だけは、頻繁にパイプラインを回すと消費が早いので注意してください。
10. 次のステップ
基本操作に慣れてきたら、以下もあわせて触ってみると理解が深まります。
- GitLab Pages で静的サイトを無料公開してみる
- カバレッジの目標値を、プロジェクトの実態に合わせて調整してみる(最初は低めに設定し、少しずつ引き上げていくのが定番のやり方です)
- Issueテンプレートをさらに増やして、
Question.md(質問用)なども用意してみる
このガイドはGitLab Free版(EEディストリビューション含む)の基本操作を対象にしています。UIや機能はアップデートにより変わる場合があるため、最新情報はGitLab公式ドキュメントも併せて確認してください。