.envやビルド生成物を.gitignoreに追加したのに、GitHub DesktopやVisual Studio Codeの変更一覧へ出続ける原因は、そのファイルがすでにGitの追跡対象になっているためです。
.gitignoreは、未追跡ファイルを新たに追跡しないためのルールです。すでにコミット済み、またはgit add済みのファイルに対して、あとから.gitignoreへパターンを書いても追跡は止まりません。
解決するには、必要なローカルファイルを保全したうえで、.gitignoreへルールを追加し、対象ファイルだけをgit rm --cachedで追跡対象から外します。リポジトリ全体ではなく、ファイルやディレクトリを明示して操作することが重要です。([GitHub Docs][1])
.gitignoreに追加しても追跡が止まらない理由
Gitでは、作業フォルダーに存在するファイルと、次回のコミット対象を管理するインデックスが区別されています。
一度インデックスへ登録されたファイルは、Gitにとって「追跡済みファイル」です。.gitignoreは、追跡済みファイルには影響しません。Git公式ドキュメントでも、.gitignoreは意図的に追跡しないファイルを指定するものであり、すでに追跡されているファイルには適用されないと説明されています。([Git][2])
| ファイルの状態 | .gitignoreだけで無視できるか | 必要な対応 |
|---|---|---|
| まだ一度も追跡されていない | できる | .gitignoreへ追加する |
git addまたはコミット済み | できない | .gitignore追加後に追跡を外す |
| 過去のコミットに含まれている | 過去分は消えない | 必要に応じて認証情報の交換や履歴対応を行う |
変更一覧に出ているという現象だけを見て、.gitignoreの書き方が間違っていると判断しないことが大切です。最初に、そのファイルが追跡済みかどうかを確認します。
まず対象ファイルが追跡済みか確認する
リポジトリのルートディレクトリで、次のコマンドを実行します。
git ls-files -- .env
.envと表示された場合、そのファイルはGitのインデックスに登録されています。何も表示されなければ、少なくとも指定したパスは追跡されていません。
git ls-filesは、標準ではGitのインデックスに登録された追跡済みファイルを表示します。末尾の--は、以降の文字列をコマンドオプションではなくパスとして扱わせるための区切りです。([Git][3])
対象が別の場所にある場合は、リポジトリルートからの相対パスを指定します。
git ls-files -- config/local.json
生成ディレクトリ内の追跡ファイルを確認する場合は、次のようにディレクトリを指定できます。
git ls-files -- dist/
.gitignoreのパターンが正しいか確認する
追跡済みファイルは通常のgit check-ignoreでは表示されないため、確認時は--no-indexを付けます。
git check-ignore -v --no-index -- .env
パターンが一致していれば、どの無視設定ファイルの何行目が適用されたかを確認できます。何も表示されない場合は、.gitignoreのパターン、配置場所、対象パスを見直します。
git check-ignoreは通常、追跡済みファイルを無視判定の対象として表示しません。--no-indexを付けることで、追跡状態を考慮せずにパターンの一致を調べられます。([Git][4])
.gitignoreに追加しても追跡が止まらないファイルを外す方法
必要なローカルファイルを保全する
git rm --cachedは通常、作業フォルダー内のファイルを残したまま、Gitのインデックスから対象を外します。ただし、操作ミスや共同作業者による変更の取り込みに備え、.envなど再作成が難しいファイルはリポジトリ外へコピーしておくと安全です。
特に次のようなファイルは、先に保全してください。
- APIキーや接続先を含む
.env - ローカル専用の設定ファイル
- 手作業で編集した生成物
- 再生成に時間がかかるキャッシュや成果物
バックアップ先は、同じリポジトリの中ではなく、リポジトリ外のフォルダーにします。リポジトリ内へコピーすると、コピーしたファイルまで誤って追跡する可能性があります。
.gitignoreへ適切なパターンを追加する
リポジトリ直下の.envだけを無視する場合は、ルートの.gitignoreへ次のように記述します。
/.env
複数の環境設定ファイルを無視し、共有用の.env.exampleだけを残す場合は、次のように記述できます。
/.env
/.env.*
!/.env.example
生成ディレクトリを無視する例は次のとおりです。
/dist/
/coverage/
先頭の/を付けると、その.gitignoreが置かれた位置を基準にパスが固定されます。たとえばルートの.gitignoreに書いた/.envは、リポジトリ直下の.envだけに一致します。
一方、次のように先頭の/を付けなければ、下位ディレクトリにある同名ファイルにも一致します。
.env
モノレポなどで複数のアプリがそれぞれ.envを持つ場合は.env、リポジトリ直下だけを対象にする場合は/.envというように使い分けます。
また、!で始まるパターンは、それ以前の無視ルールを打ち消します。同じ優先順位では、後ろにある一致パターンが最終的に適用されます。([Git][2])
対象ファイルだけを追跡対象から外す
.gitignoreへルールを追加したら、対象ファイルを明示して追跡を外します。
.envを対象にする場合は、次のコマンドです。
git rm --cached -- .env
config/local.jsonを対象にする場合は、パスを置き換えます。
git rm --cached -- config/local.json
ディレクトリを追跡対象から外す場合は、-rを付けます。
git rm -r --cached -- dist/
--cachedを付けると、対象はGitのインデックスから外れますが、通常は作業フォルダー内のローカルファイルが残ります。ディレクトリを対象にするときは、再帰処理を許可する-rが必要です。([Git][5])
リポジトリ全体を対象にしない
追跡を外したい対象が.envだけなら、.envだけを指定します。生成物がdist/だけなら、dist/だけを指定します。
リポジトリ全体のインデックスを一度削除して登録し直す方法も見かけますが、意図していないファイルまで大量に変更扱いになる可能性があります。改行コード、ファイルモード、無視ルールの不足などが重なると、確認すべき差分が急増します。
この記事のケースでは、対象ファイルまたは対象ディレクトリを明示する方法が基本です。
エラーが出ても安易に強制実行しない
git rm --cachedは、インデックス、現在のコミット、作業フォルダーの内容に複雑な差異があると処理を拒否することがあります。
その場合は、すぐに強制オプションを追加せず、次のコマンドで状態を確認します。
git status
特に、ファイルの一部だけをステージしている場合や、ステージ後に内容を再編集している場合は注意が必要です。必要な内容をリポジトリ外へ保全し、どの変更を残すか整理してから追跡解除を行います。git rmには安全確認を上書きするオプションがありますが、内容を理解せず使用すると意図しない変更につながります。([Git][5])
変更内容を確認してコミットする
追跡を外したら、.gitignoreをステージします。
git add .gitignore
続いて、変更一覧を確認します。
git status
この時点では、対象ファイルが「削除」として表示されることがあります。これはローカルファイルを消すという意味ではなく、次回のコミットから、そのファイルをリポジトリの管理対象から削除する変更がステージされているためです。
この削除表示は異常ではありません。コミットが完了するまでは、追跡解除そのものが変更として表示されます。
ステージ済みファイルの種類だけを確認するには、次のコマンドが使えます。
git diff --cached --name-status
.gitignoreの記述内容だけを確認する場合は、対象を限定します。
git diff --cached -- .gitignore
.envに秘密情報が含まれている場合、画面共有中やログが保存されるターミナルで、.envの内容を含む差分を表示しないよう注意してください。ファイル名の確認には--name-statusを使い、秘密情報そのものを不用意に出力しない方が安全です。
問題がなければコミットします。
git commit -m "Stop tracking local environment files"
コミット後、状態を再確認します。
git status
さらに、.envへ無視ルールが適用されているかを確認できます。
git check-ignore -v -- .env
追跡解除後は対象が未追跡状態になるため、適用されている無視ルールが表示されます。
.envと生成ファイルの設定例
| 対象 | .gitignoreの例 | 追跡解除コマンド |
|---|---|---|
ルート直下の.env | /.env | git rm --cached -- .env |
ルート直下の.env.local | /.env.local | git rm --cached -- .env.local |
共有用以外の.env.* | /.env.*と!/.env.example | 実際に追跡されている対象だけを個別指定 |
ルート直下のdist | /dist/ | git rm -r --cached -- dist/ |
ルート直下のcoverage | /coverage/ | git rm -r --cached -- coverage/ |
| ローカル設定ファイル | /config/local.json | git rm --cached -- config/local.json |
すべての生成物を無条件に無視すればよいわけではありません。配布物、GitHub Pages用ファイル、静的サイトの公開成果物など、プロジェクトによっては生成物をリポジトリで管理する設計もあります。
.gitignoreへ追加する前に、次の点を確認します。
- CIやデプロイ処理が生成物を作れるか
- 配布時に必要なファイルではないか
- チーム内で同じ無視ルールを適用してよいか
- 再生成に必要なソースや設定が追跡されているか
.env.exampleなどの設定例を別途共有できるか
共同作業者への影響を知らせる
.gitignoreと追跡解除をコミットすると、リポジトリ上では対象ファイルの削除として記録されます。
共同作業者がそのコミットを取り込むと、これまで追跡されていたローカルファイルが削除される可能性があります。ローカルで変更済みの場合は、取り込みが停止したり、競合対応が必要になったりすることもあります。
.envを追跡対象から外す場合は、コミットやプルリクエストの説明に、少なくとも次の内容を記載します。
.envをリポジトリの追跡対象から外したこと- 取り込み前に各自の
.envを保全すること - 取り込み後も各自で
.envを保持または再作成すること - 必要な環境変数は
.env.exampleなどで確認できること - 実際のパスワードやAPIキーは共有用ファイルに書かないこと
共有用ファイルには値そのものではなく、必要な変数名や安全な初期値だけを記載します。
API_BASE_URL=
API_TOKEN=
DATABASE_HOST=localhost
自分の環境だけで無視するなら.git/info/excludeを使う
チーム全員ではなく、自分のローカル環境だけで無視したい未追跡ファイルは、.git/info/excludeへルールを追加できます。
たとえば、自分だけが使うメモファイルを無視する場合は、.git/info/excludeへ次のように記述します。
/local-notes.txt
.git/info/excludeはリポジトリへコミットされないため、他の共同作業者には影響しません。エディターが生成する個人用ファイルや、自分専用の補助ファイルに適しています。GitHub Docsでも、共有しないローカル専用ルールの保存先として案内されています。([GitHub Docs][1])
ただし、.git/info/excludeも追跡済みファイルには効きません。
すでにリポジトリで管理されているファイルの変更を、自分の環境だけで見えなくする目的には適していません。共有ファイルを追跡したまま各自が別の内容を持つ設計は、更新の見落としや競合の原因になります。
| 設定場所 | 適した用途 | リポジトリへコミットされるか |
|---|---|---|
.gitignore | チーム全員が無視するファイル | される |
.git/info/exclude | そのリポジトリ内の個人用ファイル | されない |
| グローバル無視設定 | 複数リポジトリ共通の個人用ファイル | されない |
追跡解除後も変更一覧に出るときの確認項目
削除として表示されている
git rm --cachedを実行した直後であれば、追跡解除がステージ済みの削除として表示されるのは正常です。
git status
.gitignoreの変更と対象ファイルの削除が意図どおりなら、その状態をコミットします。コミット後は、対象ファイルの通常の変更が一覧に出なくなります。
まだ追跡されている
次のコマンドで対象パスが表示されるなら、まだインデックスへ登録されています。
git ls-files -- .env
指定したパスが正しいか確認し、対象を明示して追跡を外します。
git rm --cached -- .env
.gitignoreのルールが一致していない
次のコマンドで、一致したルールを確認します。
git check-ignore -v --no-index -- .env
何も表示されない場合は、次の点を見直します。
.gitignoreが保存されているか- リポジトリ内の正しい場所へ置かれているか
- ファイル名やディレクトリ名が正しいか
- 先頭の
/によって対象範囲を狭めすぎていないか - 後ろにある
!ルールで無視が解除されていないか - 別の階層の
.gitignoreが影響していないか
無視ルールは複数の場所から読み込まれ、同じ優先順位では後ろの一致パターンが最終結果を決めます。git check-ignore -vを使うと、適用元とパターンを確認できます。([Git][2])
Gitのコマンドでは消えたがGUIに残る
git statusでは変更がないのに、GitHub Desktopやエディター上だけに表示が残る場合は、GUI側の表示更新を確認します。
まず、リポジトリのルートで次を実行します。
git status
Gitの状態が正常なら、利用しているアプリでリポジトリの再読み込みや更新を行います。判断基準はGUIの表示だけではなく、Git自身が返す状態に置きます。
過去のコミットに入った.envや秘密情報は消えない
git rm --cachedと.gitignoreで止められるのは、今後そのファイルを通常のコミットへ追加することです。過去のコミットに保存された内容は削除されません。
パスワード、APIキー、アクセストークン、秘密鍵などをGitHubへプッシュした場合は、履歴削除より先に、その認証情報を失効または交換します。GitHubも、秘密情報を削除する必要がある場合は、最初にシークレットを無効化またはローテーションするよう案内しています。([GitHub Docs][6])
その後、必要性と影響範囲を確認して履歴からの削除を検討します。履歴の書き換えには、次のような影響があります。
- コミットIDが変わる
- 共同作業者のクローンとの履歴が分岐する
- 古いクローンやフォークに秘密情報が残る
- プルリクエストの差分表示へ影響する
- 誤ったマージやプッシュで秘密情報が再混入する
- ブランチ保護や署名済みコミットへ影響する場合がある
履歴削除は、通常の追跡解除とは別の作業です。共同作業者との調整を行わず、リポジトリ全体へ一括処理を実行しないでください。GitHubのドキュメントでも、履歴書き換えには複数の副作用があり、クローンやフォーク、プルリクエストに秘密情報が残る可能性が説明されています。([GitHub Docs][6])
.gitignoreに追加したのに出続けるときの対応手順
.envや生成ファイルが変更一覧に出続ける場合は、次の順番で対応します。
git ls-filesで追跡済みか確認する- 必要なローカルファイルをリポジトリ外へ保全する
.gitignoreへ共有すべき無視ルールを追加するgit rm --cachedで対象ファイルだけを追跡対象から外すgit statusとgit diff --cached --name-statusで変更を確認する.gitignoreと追跡解除をコミットする- 共同作業者へローカルファイルへの影響を知らせる
- 秘密情報を公開していた場合は、認証情報を失効・交換する
重要なのは、.gitignoreの追加だけで解決しようとしないことです。無視ルールの追加と、既存の追跡解除は別の操作として行います。
[1]: https://docs.github.com/en/get-started/git-basics/ignoring-files “Ignoring files – GitHub Docs”
[2]: https://git-scm.com/docs/gitignore “Git – gitignore Documentation”
[3]: https://git-scm.com/docs/git-ls-files “Git – git-ls-files Documentation”
[4]: https://git-scm.com/docs/git-check-ignore “Git – git-check-ignore Documentation”
[5]: https://git-scm.com/docs/git-rm “Git – git-rm Documentation”
[6]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository “Removing sensitive data from a repository – GitHub Docs”

コメント