Git入門:バージョン管理のきほん

追跡しないファイルを決める(.gitignore)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部
.gitignore の関所

このレッスンで分かること

  • なぜ全部のファイルを Git に記録してはいけないのか
  • .gitignore の書き方と、よく使うパターン
  • 一度コミットしたファイルを追跡から外す git rm --cached の使い方

記録してはいけないファイルがある

プロジェクトのフォルダには、自分で書いたコード以外にもたくさんのファイルが生まれます。npm install で入るライブラリ、ビルドで生成される成果物、エディタが勝手に作る設定ファイル、macOS が置いていく .DS_Store などです。

これらを全部コミットすると、次の問題が起きます。

問題具体例
リポジトリが重くなるnode_modules は数万ファイル、数百 MB になる
差分が読めなくなる自動生成物が変わるたび、何千行もの差分が出る
環境の違いで壊れる別の OS 向けにビルドされたファイルが混ざる
機密が漏れる.env の API キーやパスワードがそのまま公開される

とくに最後は取り返しがつきません。GitHub の公開リポジトリに API キーを push すると、数分で自動収集されて悪用されます。「あとで消す」では間に合わないので、最初から記録しない仕組みが要ります。それが .gitignore です。

判断の基準はひとつです。そのファイルは、他の人が同じ手順で作り直せるか。 node_modulesnpm install で作り直せます。dist はビルドすれば作り直せます。作り直せるものは記録しません。逆に、自分が手で書いたソースコードや設定のひな形は、失うと二度と戻らないので記録します。

.env は例外的な扱いになります。作り直せない情報が入っていますが、公開してはいけないので記録しません。代わりに、キー名だけを書いた見本のファイルを別に用意する、というやり方が定番です。これは後半で扱います。

.gitignore を置く

リポジトリの一番上のフォルダに .gitignore という名前のファイルを作り、除外したいものを 1 行 1 つ書きます。

ターミナル

$ touch .gitignore

Windows の PowerShell では次のように作ります。

ターミナル

$ New-Item .gitignore -ItemType File

中身の例です。

プレーンテキスト

# 依存パッケージ node_modules/ # ビルド成果物 dist/ build/ # 環境変数 .env .env.local # OS が作るファイル .DS_Store Thumbs.db # ログ *.log

# で始まる行はコメントで、Git は無視します。分類ごとにコメントを入れておくと、あとから読む人が理解しやすくなります。

この状態で git status を実行すると、書いたファイルが一覧から消えます。

ターミナル

$ git status

プレーンテキスト

On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) modified: src/app.js Untracked files: (use "git add <file>..." to include in what will be committed) .gitignore

node_modules/.envUntracked files から消えている一方で、.gitignore 自身は表示されています。これはこのファイルをコミットしてチームで共有するためです。.gitignore は必ずコミットしてください。

書き方のパターン

書き方は少ないルールの組み合わせでできています。

書き方意味
secret.txtどの階層でも、この名前のファイルを除外
/secret.txtリポジトリ直下の secret.txt だけを除外
logs/logs という名前のフォルダを、中身ごと除外
*.log拡張子が .log のファイルをすべて除外
build/*.jsbuild 直下の .js だけを除外
**/tempどの階層の temp でも除外
!keep.log除外の対象から外す(打ち消し)

末尾のスラッシュが有るか無いかで意味が変わります。logs はファイルでもフォルダでも一致しますが、logs/ はフォルダだけに一致します。フォルダを除外したいときはスラッシュを付けるほうが意図が明確です。

打ち消しの ! は、まとめて除外したうえで例外を作りたいときに使います。

プレーンテキスト

# ログは全部除外 *.log # ただしサンプルだけは残す !sample.log

順番が大事です。あとに書いた行が優先されるので、!sample.log*.log より前に書くと効きません。

親フォルダごと除外していると、その中のファイルを ! で復活させることはできません。logs/ を除外した状態で !logs/keep.log と書いても効かないので、logs/*!logs/keep.log の組み合わせにしてください。

空のフォルダは記録できない

.gitignore を書いていると、必ずぶつかる仕様があります。Git はファイルを記録する仕組みで、フォルダそのものは記録しません。 そのため、中身が空のフォルダはコミットできず、clone した人の手元には作られません。

logs/ のように「フォルダの存在だけは必要だが、中身は記録したくない」場合は、次のように書きます。

プレーンテキスト

logs/* !logs/.gitkeep

そのうえで logs/.gitkeep という空のファイルを作ってコミットします。.gitkeep は Git の機能ではなく、こういう目的で置く空ファイルに慣習的に付けられている名前です。中身は空で構いません。

ターミナル

$ touch logs/.gitkeep $ git add logs/.gitkeep

書く順番と階層

.gitignore はリポジトリ直下だけでなく、サブフォルダにも置けます。その場合、そのフォルダ以下にだけ設定が効きます。フロントエンドとバックエンドでビルド成果物の場所が違うプロジェクトなどで、それぞれのフォルダに置くと見通しがよくなります。

ただし、階層を増やすほど「どこで除外されているのか」が分かりにくくなります。小規模なうちは直下の 1 枚にまとめるほうが管理しやすいです。

一番の落とし穴 — 一度コミットしたファイルは除外されない

ここが .gitignore で最もつまずくところです。

.gitignore は「まだ Git が追跡していないファイル」にしか効きません。 一度でも git add してコミットしたファイルは、あとから .gitignore に書いても、変更するたびに git status に出続けます。

たとえば、うっかり .env をコミットしてしまったあとで .gitignore.env を書き足したとします。

ターミナル

$ git status

プレーンテキスト

On branch main Changes not staged for commit: modified: .env

.gitignore に書いたのに、まだ出ています。Git から見れば .env は「管理対象として登録済みのファイル」であり、.gitignore は登録の候補を選ぶための設定でしかないからです。すでに登録されたものには適用されません。

git rm --cached で追跡から外す

直すには、Git の管理対象から明示的に外します。使うのは git rm --cached です。

ターミナル

$ git rm --cached .env

プレーンテキスト

rm '.env'

--cached を付けると、ステージング(次のコミットに含める場所)と管理対象からは外れますが、作業フォルダのファイルは消えません。手元の .env はそのまま残るので、アプリはこれまで通り動きます。

このあと git status を見ると、削除としてステージされた状態になっています。

ターミナル

$ git status

プレーンテキスト

On branch main Changes to be committed: deleted: .env

この削除をコミットして完了です。

ターミナル

$ git commit -m ".env を追跡対象から外す" $ git status

プレーンテキスト

On branch main nothing to commit, working tree clean

.env が一覧から完全に消えました。ここから先は .gitignore が効きます。

フォルダをまとめて外すときは -r を足します。

ターミナル

$ git rm -r --cached node_modules

node_modules のように大量のファイルが入っている場合、削除の一覧が何万行も流れます。驚く必要はありません。--cached を付けている限り、消えているのは管理対象の登録だけで、実ファイルは無事です。心配なら実行後にフォルダを開いて中身が残っていることを確かめてください。

.gitignore を書き足したあとで、追跡済みのファイルをまとめて整理したいときは、いったん全部の登録を外して付け直す方法もあります。

ターミナル

$ git rm -r --cached . $ git add . $ git commit -m ".gitignore を反映する"

1 行目で全ファイルの登録を外し、2 行目で .gitignore を効かせた状態で登録し直しています。除外対象は 2 行目で拾われないので、結果として追跡から外れます。手元のファイルは 1 つも消えません。

--cached を絶対に付け忘れないでください。 git rm .env と打つと、追跡から外すだけでなく作業フォルダのファイルまで削除されます。設定ファイルを消してアプリが起動しなくなる事故がよく起きます。

消したあとに残るもの

git rm --cached で追跡から外しても、過去のコミットの中には元のファイルが残ったままです。履歴を遡れば中身は読めます。

つまり、API キーやパスワードを公開リポジトリに push してしまった場合、追跡から外しただけでは漏洩は止まりません。やるべきことの順番は次の通りです。

  1. そのキーやパスワードをサービス側で無効化する
  2. 新しいキーを発行して手元の .env に入れ直す
  3. .gitignore.env を追加し、git rm --cached .env で追跡から外す

履歴そのものから消す方法もありますが、共有済みの履歴を書き換える作業になり、他のメンバーの手元と食い違いが出ます。鍵を差し替えるほうが確実で速いです。

自分だけの除外と、テンプレート

.gitignore はチーム共有の設定です。自分のエディタの設定ファイルなど、他の人には関係ないものまで書き足すと余計な差分になります。

自分の環境だけで除外したいものは、リポジトリ内の .git/info/exclude に書きます。書き方は .gitignore と同じで、こちらはコミットされません。

ゼロから書くのが大変なときは、GitHub が公開しているテンプレート集が使えます。Node.gitignorePython.gitignore のように言語ごとの定番がまとまっているので、それを土台にしてプロジェクト固有の行を足すのが早いです。

OS 固有のファイルにも注意が要ります。macOS はフォルダを開くだけで .DS_Store を作り、Windows は Thumbs.dbdesktop.ini を作ります。チームで OS が混在している場合、両方書いておかないと、相手の環境のゴミファイルが混ざります。自分の OS の分だけ書いて満足しないでください。

環境変数のファイルは、扱い方に決まった型があります。

プレーンテキスト

.env .env.local !.env.example

実際の値が入った .env は除外し、キー名だけを書いた .env.example はコミットする、という形です。これなら clone した人が「何を設定すればよいか」は分かり、値そのものは漏れません。

プレーンテキスト

# .env.example DATABASE_URL= API_KEY=

このやり方は多くのプロジェクトで採用されています。自分でリポジトリを作るときも、最初からこの形にしておくと安全です。

最後に、除外の設定が意図通りか確かめる方法です。

ターミナル

$ git check-ignore -v .env

プレーンテキスト

.gitignore:8:.env .env

.gitignore の 8 行目の .env という記述によって除外されている、と教えてくれます。何も表示されなければ、そのファイルは除外されていません。「書いたのに効かない」と悩んだら、まずこのコマンドで確認してください。

いま何が追跡されているかを一覧したいときは、次のコマンドが使えます。

ターミナル

$ git ls-files

追跡中のファイルだけが並びます。ここに .envnode_modules の中身が出てきたら、除外できていないという証拠です。.gitignore を書いた直後に一度確認しておくと、あとで慌てずに済みます。

この章のポイント
  • .gitignore はリポジトリ直下に置き、コミットしてチームで共有する
  • 末尾のスラッシュはフォルダ限定、* はワイルドカード、! は打ち消し
  • 一度コミットしたファイルには .gitignore は効かない。git rm --cached で追跡から外す
  • --cached を忘れると実ファイルまで消える。公開済みの鍵は無効化と再発行が最優先