開発未分類

.gitkeepとは?Gitで空ディレクトリを管理する方法と運用上の落とし穴

結論

.gitkeepは、空のディレクトリをGitで追跡するためのダミーファイル。Gitはファイル単位で変更を追跡する仕組みなので、ファイルが1つもないディレクトリを「存在するもの」として扱えない。そこに空のファイルを1つ置くことで、ディレクトリの存在をGitに認識させる。

ファイル名は .gitkeep でも .keep でも .placeholder でも構わない。慣習として .gitkeep が広く使われている。


Gitが空ディレクトリを追跡できない理由(内部構造から見る)

これは「Gitの設計上の都合」というより、Gitのデータモデルそのものの帰結。理屈を一度押さえると、.gitkeep がなぜ必要なのか腑に落ちる。

Gitはリポジトリの状態を3種類のオブジェクトで表現する。

オブジェクト中身役割
blobファイルの中身(バイト列)ファイル1つ分
treeblob と他の tree への参照リスト1ディレクトリ分のスナップショット
commit1つの tree への参照 + 親 commit + メタ情報履歴の1ノード

注目すべきは tree の中身。tree は「ファイル名 → blob/tree への参照」のリストでしかない。つまり、ファイルが1つもないディレクトリを表す tree を作っても、それは「中身ゼロのリスト」になり、参照先がないので親 tree には何も載せようがない。

これがGitが空ディレクトリを「保持できない」理由。SVNやMercurialのように「ディレクトリそのもの」を一級オブジェクトとして持つVCSとは設計が違う。

# 空ディレクトリを作っても git は何も検出しない
mkdir uploads
git status
# → uploads/ は何も表示されない("untracked files" にも出てこない)

.gitkeepの使い方

基本

空にしたいディレクトリに、中身ゼロのファイルを1つ置く。

# uploadsディレクトリを作成
mkdir uploads

# .gitkeepを作成(中身は空でOK)
touch uploads/.gitkeep

# これで git add できる
git add uploads/.gitkeep
git commit -m "Add uploads directory"

.gitignoreとの組み合わせ(実務で最も多いパターン)

「ディレクトリ構造は維持したいが、中身は除外したい」というケース。アップロード用フォルダ、キャッシュ、ログ出力先などで頻出する。

# uploads ディレクトリの中身は除外
uploads/*

# ただし .gitkeep は追跡する
!uploads/.gitkeep

この設定により、uploads/ 配下にどんなファイルを置いても無視されるが、ディレクトリ構造自体はリポジトリに含まれる。

実際の使用例(顧問先データ分離)

プロジェクトで顧問先ごとにデータを分離する構造を作った際の例。

tests/data/client_001/
├── inbox/           # スキャン待ちレシート
│   └── .gitkeep
├── batches/         # 処理済みバッチ
│   └── .gitkeep
├── exports/         # 出力済みファイル
│   └── .gitkeep
└── receipts.db      # DBファイル(.gitignoreで除外)

.gitignore の設定はこうなる。

# データディレクトリ(中身は除外、構造は維持)
tests/data/client_*/inbox/*
tests/data/client_*/batches/*
tests/data/client_*/exports/*
!tests/data/client_*/**/.gitkeep

# DBファイルは除外
*.db

.gitkeep / .keep / .placeholder の違い

名前使われる文脈補足
.gitkeep個人プロジェクト、OSS全般で広く使用Git公式の機能ではなく慣習
.keepRails系のプロジェクトでよく見るRailsジェネレータが自動生成する
.placeholder「中身が後で来る予定」のニュアンス用途を伝える命名

重要な点: いずれも「Gitの公式機能」ではない。Gitから見れば、ただの「中身ゼロのファイル」。意図を読み手に伝える名前を選んでいるだけ。

.gitkeep を使うと「Gitの都合でここに置いているダミーである」ことが名前から伝わる。レビュー時に「これ何?」と聞かれにくい利点がある。


IDE/エディタでの扱い

  • VSCode: .gitkeep は通常のファイルとして表示される。エクスプローラーで非表示にしたい場合は files.exclude 設定で **/.gitkeep を追加
  • JetBrains系(IntelliJ / WebStorm 等): 同じく通常表示。Settings > Editor > File Types > Ignored Files で除外可能
  • Vim/Neovim: . で始まるファイルは隠しファイル扱い。:set list で確認可能
  • macOS Finder / Windows Explorer: デフォルトでは隠しファイルとして非表示。Cmd+Shift+. / 表示オプションで表示可能

実務でハマる落とし穴

落とし穴 1: .gitignore の * パターンが .gitkeep まで除外する

# NG: これだと .gitkeep も除外されてしまう
uploads/

# NG: 同じく除外される
uploads/**

# OK: 中身だけ除外して .gitkeep は残す
uploads/*
!uploads/.gitkeep

uploads/ という末尾スラッシュ付きや uploads/**ディレクトリ全体 を除外する。.gitkeep も道連れになるため、uploads/* + !uploads/.gitkeep のセットが正解。

落とし穴 2: ネストしたディレクトリで .gitkeep が複数階層必要

data/
├── 2026/
│   ├── Q1/
│   │   └── .gitkeep   # 必要
│   ├── Q2/
│   │   └── .gitkeep   # 必要
│   └── .gitkeep       # data/2026/ が空のことはほぼ無いが念のため
└── .gitkeep

data/ 配下の各四半期ディレクトリを維持したいなら、それぞれに .gitkeep を置く必要がある。1階層下の .gitkeep で上位ディレクトリが「保持される」と誤解しがち。

落とし穴 3: git rm で消すと .gitkeep ごと消える

# ディレクトリの中身を整理しようとして
git rm -r uploads/

# uploads/.gitkeep も消えてディレクトリが消滅

明示的に残す場合:

git rm -r uploads/
mkdir -p uploads/
touch uploads/.gitkeep
git add uploads/.gitkeep

.gitkeep を使わずに済むケース

実は、運用次第で .gitkeep を使わずに済むこともある。

  • ビルド時に生成されるディレクトリ: dist/ .nuxt/ node_modules/ 等は .gitignore で完全に除外し、起動時にツールが自動作成するので不要
  • README.md を入れる: 「このディレクトリは何のため?」を兼ねた説明書きを置けば、それが空ディレクトリ問題も解決する
  • .npmrc .envrc などの設定ファイル: 機能ファイル自体が常に存在するなら、ダミーは不要

.gitkeep を置く前に、本当に空でいいのか」を一度考える価値はある。


まとめ

  • Gitは tree オブジェクトの仕様上、空ディレクトリを保持できない
  • .gitkeep慣習的なダミーファイル。中身ゼロのファイルを置けばいい
  • .gitignore と組み合わせるなら uploads/* + !uploads/.gitkeep のパターン
  • ネストしたディレクトリは階層ごとに .gitkeep が必要
  • 実は使わずに済むケースもある(ビルド生成物・README 兼用)

関連記事