もう.envファイルを誤爆しない！Git .gitignore の基本と実践

# はじめに

以前隠しファイルである`.envファイル`を、`git add .`で、誤ってgithubに上げてしまったことがあり、その反省から`.gitignore`の使い方をマスターしようと思って、ドキュメントを見た学びを残します。

ざっくり、gitignore の目的・使い方から、設定時の注意点、具体的な記述例あたりをまとめています。

参考にしたドキュメントはこちらです。

https://git-scm.com/docs/gitignore

gitignoreは、Gitでバージョン管理する際に、**意図的に無視したいファイルやディレクトリ**を指定するための設定ファイルです。適切にgitignoreを設定することで、リポジトリを整理し、**不必要なファイルのコミットを防ぎ、共同開発をスムーズに進める**ことができます。

# なぜgitignoreは必要？

gitignoreの最大の目的は、**意図しないファイルのコミットを防ぐ**ことです。

開発プロジェクトでは、ソースコード以外にも様々なファイルが生成されます。例えば、
* **一時ファイル**: エディタやIDEが自動生成するバックアップファイル、一時ファイル
* **ビルド生成物**: コンパイル後のオブジェクトファイル、実行ファイル、ライブラリ
* **ログファイル**: アプリケーションのログファイル
* **設定ファイル**: 環境設定ファイル（APIキーを設定した`.env`など）、個人設定ファイル
* **OS依存ファイル**: `.DS_Store` (macOS), `Thumbs.db` (Windows)

これらのファイルは、**バージョン管理する必要がない**、もしくは**含めたくない**場合がほとんどです。gitignoreを設定することで、これらのファイルをGitの管理対象から除外し、**リポジトリをクリーンに保ち、必要なファイルだけを管理**できるようになります。

gitignoreを設定するメリットは、以下が挙げられます。
* **リポジトリの肥大化防止**: 不要なファイルをコミットしないことで、リポジトリサイズを抑え、cloneやfetchの時間を短縮
* **コミットログの可読性向上**: 必要な変更のみがコミットログに残り、変更履歴が追いやすくなる
* **共同開発の効率化**: 開発者間で不要なファイルの差分が発生するのを防ぎ、コンフリクトを抑制
* **セキュリティ**: 環境設定ファイルなど、機密情報を含むファイルを誤ってコミットするリスクを低減

# gitignoreの仕組み
gitignoreは、パターンマッチングを用いて、無視するファイルを指定します。Gitは、以下の順序で gitignoreパターンをチェックし、**最初にマッチしたパターン**に基づいてファイルの追跡/無視を決定します。

**gitignoreパターンの優先順位** (高い順)：
1. **コマンドラインパターン**: `git add -f <パターン>` など、コマンドラインで直接指定されたパターン
2. **`.gitignore`ファイル**: プロジェクト内の`.gitignore`ファイル (ディレクトリごとに設定可能)
3. **`$GIT_DIR/info/exclude`**: リポジトリ固有の除外設定ファイル
4. **`core.excludesFile`**: グローバルな除外設定ファイル (`~/.gitconfig` で設定)
![](https://storage.googleapis.com/zenn-user-upload/bb8f0ba775a7-20250302.png)

**`.gitignore`ファイルの探索範囲**：
* `.gitignore`ファイルは、対象ファイルが存在するディレクトリから**ルートディレクトリ** (`.git`ディレクトリがあるディレクトリ) まで、**階層を遡って探索**されます。
* 上位階層の`.gitignore`に記述されたパターンは、下位階層の`.gitignore`で上書きできます。
![](https://storage.googleapis.com/zenn-user-upload/8e2ad83b9388-20250302.png)

# gitignoreの設定方法
gitignoreの設定は、主に以下の3種類の方法があります。

1. **`.gitignore`ファイルを作成する**: プロジェクトルートディレクトリ、または各ディレクトリに `.gitignore` ファイルを作成し、無視したいファイルのパターンを記述します。**最も一般的**な方法で、**プロジェクト内で共有したい設定**を記述するのに適しています。
2. **`$GIT_DIR/info/exclude` を編集する**: リポジトリの `.git/info/exclude` ファイルを直接編集します。**リポジトリ固有の設定**を記述する場合に便利ですが、**`.gitignore` と異なりバージョン管理されない**ため、共有には向きません。
3. **`core.excludesFile` を設定する**:`git config --global core.excludesFile <ファイルパス>`コマンドで、グローバルなgitignoreファイルを設定します。**ユーザー全体で共通して無視したいファイル** (例: エディタの一時ファイル) を記述するのに適しています。
デフォルトは`$XDG_CONFIG_HOME/git/ignore`または`~/.config/git/ignore`です。

## `.gitignore`ファイルの書き方：パターンフォーマット

`.gitignore`ファイルに記述するパターンは、以下のルールに従います。

* **基本ルール**
    * **1行に1パターン**: 各行が1つのパターンを表します。
    * **コメント**: `#` で始まる行はコメントとして扱われます。
    * **空白行**: 空行は無視されます (区切りとして利用可能)。
    * **末尾のスペース**: デフォルトでは無視されます。バックスラッシュ (`\`) でエスケープするとスペースもパターンに含められます。

* **パターン構文**
    * **`/` (スラッシュ)**: ディレクトリ区切り文字。
        * **先頭**: ルートディレクトリからの相対パス (`.gitignore` ファイルと同じディレクトリからの相対パスではない)
        * **中間/末尾**: パターンをディレクトリに限定する場合 (末尾スラッシュ)
    * **`*` (アスタリスク)**: スラッシュを除く任意の文字列にマッチ
    * **`?` (クエスチョンマーク)**: スラッシュを除く任意の1文字にマッチ
    * **`[]` (角括弧)**: 文字クラス (例: `[a-z]`, `[0-9]`)
    * **`!` (エクスクラメーションマーク)**: 否定パターン。**以前のパターンで無視されたファイルを再度含める** (再包含)。ただし、親ディレクトリが除外されている場合は再包含できない。
    * **`**` (二重アスタリスク)**: ディレクトリ全体にマッチ (再帰的なマッチ)
        * `**/foo`:  どの階層の `foo` にもマッチ (ファイル、ディレクトリ両方)
        * `abc/**`: `abc` ディレクトリ以下の全てのファイル、ディレクトリにマッチ
        * `a/**/b`: `a/b`, `a/x/b`, `a/x/y/b` など、`a` と `b` の間に0個以上のディレクトリがあってもマッチ
            * `**/foo` の例:
            ```markdown
            ディレクトリ構造:
            project/
            ├── bar/
            │   └── foo  (マッチ)
            ├── baz/
            │   └── qux/
            │       └── foo  (マッチ)
            └── foo  (マッチ)
            
            `.gitignore` に `**/foo` と記述した場合、上記ディレクトリ構造において、
            - project/foo
            - project/bar/foo
            - project/baz/qux/foo
            が無視されます。
            ```


**実際のgitignoreファイルの記述例**：
```.gitignore
# コメント: ビルド生成物
*.o          # オブジェクトファイル (.o 拡張子のファイル)
*.exe        # 実行ファイル (.exe 拡張子のファイル)
build/       # build ディレクトリ (ディレクトリとその中身全て)

# ログファイル
log/*.log    # log ディレクトリ以下の .log ファイル

# 一時ファイル
*.tmp       # .tmp 拡張子のファイル
._*         # macOS の一時ファイル (._ で始まるファイル)

# 環境設定ファイル (注意: 機密情報を含む可能性があるので、コミットしないように注意！)
.env

# 特定のファイルを例外的に含める (再包含)
!important.txt # important.txt ファイルは無視しない

# ディレクトリを特定
doc/frotz/  # doc/frotz ディレクトリのみ (doc/frotz ファイルにはマッチしない)

# 任意の階層の foo ディレクトリ
**/foo      # どの階層の foo ディレクトリにもマッチ

# 特定のディレクトリ以外を全て除外 (例: foo/bar ディレクトリのみ管理したい場合)
/*
!/foo
/foo/*
!/foo/bar
```

# 設定時の落とし穴

gitignoreを設定する上で、いくつか注意すべき点があります。

* **既に追跡されているファイルは無視されない**: gitignore は、**これから追跡するファイル**に対して有効です。既にGitの管理下にあるファイル (indexに追加済みのファイル) を無視したい場合は、以下のコマンドでindexから削除する必要があります。

    ```bash
    git rm --cached <ファイルパス>
    ```
    その後、gitignore にパターンを追記します。

  - 対策：`.gitignore`を設定する前に`git status`で追跡状況を確認し、意図せず追跡されているファイルがないか確認する

* **除外されたディレクトリは再包含できない**: パフォーマンス上の理由から、除外されたディレクトリ自体はGitが探索しません。そのため、除外されたディレクトリ内のファイルを`!`で再包含することはできません。ディレクトリ単位で除外/包含を管理する必要があります。
    - 対策：ディレクトリを除外する前に、本当にディレクトリ全体を除外して問題ないか慎重に検討する

* **シンボリックリンクの扱い**: Gitは、`.gitignore`ファイルを探索する際にシンボリックリンクをたどりません。シンボリックリンク自体を無視したい場合は、シンボリックリンクのパスをgitignoreに記述する必要があります。

    * 具体的には、シンボリックリンク自体ではなく、リンク先のファイルを無視したい場合は、`.gitignore`にリンク先のファイルのパスを記述します。
    * 例えば、config_linkというシンボリックリンクがconfig.jsonを指している場合で、config.jsonを無視したい場合は、`.gitignore`に`config.json`のように記述します。
    * この場合、config_link自体はGitの追跡対象となりますが、リンク先のconfig.jsonの内容は無視されます。
    * ポイント:`.gitignore`のパターンは、シンボリックリンク自体ではなく、リンク先のパスに対して適用されます。

* **否定パターン (`!`) の順序**: 否定パターンは、**直前の肯定パターンを打ち消す**効果があります。否定パターンを記述する順序によって結果が変わる場合があるので注意が必要です。

# Tips
`.gitignore`を効果的に使うためのTipsを最後にまとめておきます。
- テンプレートを積極的に活用する
- 具体的なファイル名よりも、パターンで記述することを心がける
- コメントを適切に記述して、gitignore の意図を明確にする
- `.gitignore`を変更したら、意図通りにファイルが無視されるか`git status`で確認する
- チーム開発では、メンバー間で`.gitignore`の設定を統一する
- グローバルgitignore(core.excludesFile)は、プロジェクト固有の設定と混同しないように注意する

＜よくある間違い＞
- 既に追跡されているファイルを`.gitignore`に追加しても無視されない
- 否定パターン(`!`)の使い方を誤る
- ディレクトリを除外したつもりで、ファイルだけを除外してしまう(末尾スラッシュ`/`の有無による違い)

# おわりに

gitignoreを適切に設定することで、リポジトリをクリーンに保ち、意図しないファイルのコミットを防ぎ、共同開発をスムーズに進めることができます。

gitignoreのパターンフォーマットは正直難しく、記述例でなんとなく文法を理解できた気がするものの、実際どんな感じのgitignoreファイルがあるんだろうか...と探っていると、便利なテンプレートを発見しました！

https://github.com/github/gitignore?tab=readme-ov-file

言語ごとに用意されていて最高ですね！
私はよくPythonを使うのですが、[Pythonのテンプレート](https://github.com/github/gitignore/blob/main/Python.gitignore)が参考になりましたので、さっそく活用したいと思います。