空のディレクトリを維持するための、 .gitkeep と .gitignore の使い分け

空のディレクトリをコミットに含めたいときは、2つのやり方があります。`.gitkeep` を使う方法 と、 `.gitignore` をおいておく方法（例えば[PHPのフレームワーク `Laravel` で用いられている方法](https://github.com/laravel/laravel/blob/master/storage/logs/.gitignore)）です。  
空のディレクトリを保持する目的で使用する `.gitignore` は、たいていの場合、以下のような内容になっています。

```text:.gitignore
*
!.gitignore
```

## 使い分けの基準
`.gitkeep` と `.gitignore` は、「空のディレクトリにファイルが追加されたときに、そのファイルを Git での管理対象に含めたいか？」という基準で、使い分けられます。
含めたい場合は `.gitkeep` 、含めたくない場合は `.gitignore` を使います。

### `.gitkeep` を使う基準と例
**`.gitkeep`は、「デフォルトではファイルが存在しないけれど、ファイルが追加されたら、そのファイルを Git での管理対象にしたい」場合** に使います。    
例えば、[lumen の `databse/migration` ディレクトリ](https://github.com/laravel/lumen/tree/e833011183296dc9f2d3d08f1fc4e44f1f2237a0/database/migrations)などは、良い例です。
このディレクトリは、データベースの初期化をするスクリプトをおいておくために使われるディレクトリなので、「フレームワーク開発者的には、 **デフォルトでおいておくファイルは特にない。しかし、** ユーザが **作ったファイルは Git でバージョン管理する対象にしたい** 」というユースケースが容易に想定できるので、 `.gitkeep` が使われています。

### `.gitignore` を使う基準と例
**`.gitignore` は、「デフォルトではファイルが存在しないし、そこに作られたファイルもバージョン管理したくない」場合** に使います。
例えば、「[ログの出力先](https://github.com/laravel/lumen/tree/e833011183296dc9f2d3d08f1fc4e44f1f2237a0/storage/logs)」や[キャッシュ置き場](https://github.com/laravel/lumen/tree/e833011183296dc9f2d3d08f1fc4e44f1f2237a0/storage/framework/cache)などです。ログファイルやキャッシュファイルは、 **バージョン管理する必要がありません。むしろ、したくないでしょう。**

## そもそも空のディレクトリをコミットに含めるべきではない例
コンパイル言語では、コンパイルで出力されるファイルを、「プロジェクトルートの `.gitignore` に拡張子を指定して書かれている」ことが多いと思います。例えば[C++](https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore)や[Java](https://github.com/github/gitignore/blob/master/Java.gitignore) がそうです。その結果、コンパイル結果の出力先ディレクトリは空になるので、 Git の管理対象から外れます。
これは、その言語のユーザに **「コンパイル結果の出力先ディレクトリの名前」が一般的に共通認識として知れ渡っており、ディレクトリ構成として明示的に維持しておく必要性がない** ため、このアプローチで良いと思います。

# 空のディレクトリのバージョン管理方法の良くない例
## コミットに含めるべきなのに含められていない例
コンパイル結果の出力先ディレクトリは、ディレクトリ自体をバージョン管理の対象に含めなくて良い、と言いました。しかし、 TypeScript は別です。  
TypeScript の、コンパイル結果の出力先のディレクトリは、バージョン管理の対象になっていないことが多いようです。例えば、[ionic の `dist` ディレクトリ](https://github.com/ionic-team/ionic-framework/blob/b3c05289bba10920649e0314ee4801c16113e16c/.gitignore#L17)などがそうです。  
拡張子 `.js` は、ヘルパースクリプトを書いたりしたときや、 `gulp.js` などに使用されるので、他のコンパイル言語と違って、 **拡張子を用いて `.gitignore` できません** 。
さらに、出力されるディレクトリを指定するにしても、一般的な共通認識がないため、ディレクトリを `.gitignore` に追加すべきではありません。なぜなら、ディレクトリ自体をバージョン管理の対象から外してしまった場合、プロジェクトのディレクトリ構造がわからなくなってしまいます。
わざわざディレクトリ構造を調べるためだけに、 `package.json` や `gulp.js` に処理を追加するのは馬鹿げている、と私は思います。  

ここまで読んできた方なら、この場合では「 空のディレクトリを保持する目的で使用する `.gitignore` 」を用いるのが適切だとわかるでしょう。

## 独自路線を行こうとしてミスっていた、CakePHP
これは、あとからプロジェクトルートの `.gitignore` を調整したパターンです。  
CakePHP では、そのディレクトリがデフォルトで空であることを、[empty という名前のファイルを置いて、示していました](https://github.com/cakephp/app/tree/a6122a0a49e93d2c18ddadf8b961418a1a4040a3/tmp/cache/models)。これは、（おそらく）empty ファイルをコミットした後で、プロジェクトルートの `.gitignore` に「ディレクトリ構成は維持したいけれど、ファイルはバージョン管理したくない」ディレクトリを追加したのかなと思います。  
「今現在の .gitignore のルールでは、バージョン管理の対象にならないディレクトリが、なぜ含まれているのだろう？」と疑問に思う人も少なくないでしょう。  
冪等性が保てていない様に見えるので、私はこの方法はやめたほうがいいんじゃないかなと思います。  

→ [こちらの Pull Request で `.gitkeep` を使う手法に変更されました！](https://github.com/cakephp/app/pull/662)

何にもファイルの変更をしていないのに、キャッシュの再生成をしたら、差分が生まれる例
![2017_04_17_15_56_45.png](https://qiita-image-store.s3.amazonaws.com/0/29233/e432a7bb-fb76-72c4-b8ac-682a4963f5eb.png)