.gitignore で親のフォルダを除外すると、その中のファイルは ! で戻せません。Git の文書は、性能のために、除外したフォルダの中を Git が見ないので、中のファイルに向けたパターンは、どこに書いても効かない、と書いています。たとえば logs/ のあとに !logs/keep.log を書いても、logs/keep.log は戻りません。
戻したいなら、フォルダ(logs/)ではなく中身(logs/*)を除外し、そのあとの行に !logs/keep.log を書きます。logs/* はフォルダ logs そのものには当たらないので、!logs/keep.log が効きます。.gitignore には、次の 2 行を書きます。
logs/*
!logs/keep.log
1 階層深いファイル(logs/2026/keep.log)は、logs/* のあとに !logs/2026/ → logs/2026/* → !logs/2026/keep.log と、フォルダを 1 つずつ戻します(手元で試した形です。それより深い所は実行していません)。効いたかは git check-ignore -v で見ます。! の行が出たら戻っています。調べたファイルではなく、その親のフォルダに当たる行(logs/keep.log に logs/、logs/2026/keep.log に logs/*)が出たら、親のフォルダが除外されたままです。.gitignore には、次の 4 行を書きます。
logs/*
!logs/2026/
logs/2026/*
!logs/2026/keep.log
確かめた版は、Docker の Linux の Git 2.54.0 と、手元の Mac の Git 2.39.3(Apple Git-145)です(2026年10月23日に実行しました。最新の 2.56.0 と Windows では実行していません)。表示が版で違う所には、版を添えます。
!が効かない理由(親のフォルダを除外している)logs/ではなくlogs/*にして、フォルダの中の 1 つだけを残す書き方- 深い階層の戻し方と、Git の文書の例(
foo/barのほかは全部無視) !を書く順番と、効いたかの確かめ方
! が効かないのはなぜ?
親のフォルダを除外すると、中のファイルは戻せない
.gitignore の先頭の ! は、パターンの否定です。前のパターンで除外されたファイルを、また無視しないようにします(戻します)。ただし、Git の文書には次の決まりがあります。
It is not possible to re-include a file if a parent directory of that file is excluded. Git doesn’t list excluded directories for performance reasons, so any patterns on contained files have no effect, no matter where they are defined.
訳すと、「ファイルの親のフォルダが除外されていると、そのファイルを含め直すことはできない。Git は性能のために除外したフォルダの中を調べないので、中のファイルに向けたパターンは、どこで定義しても効かない」です。
手元の出力: logs/ のあとに !logs/keep.log を書く
logs フォルダの中に keep.log と a.log があるリポジトリで、次の 2 行を書きました。
$ cat -n .gitignore
1 logs/
2 !logs/keep.log
git status --short --untracked-files=all に、logs/keep.log は出ません(?? は、まだ追跡していないファイルです。--untracked-files=all は、未追跡のフォルダの中のファイルも 1 つずつ出します)。
$ git status --short --untracked-files=all
?? .gitignore
git check-ignore -v では、どちらのファイルも、1 行目のフォルダのパターン logs/ に当たっています。!logs/keep.log の行は出ません。
$ git check-ignore -v logs/keep.log logs/a.log
.gitignore:1:logs/ logs/keep.log
.gitignore:1:logs/ logs/a.log
名前を指定して git add しても止まります。表示に出るのは、ファイルではなく、フォルダの logs です(Git 2.54.0 の表示)。
$ git add logs/keep.log
The following paths are ignored by one of your .gitignore files:
logs
hint: Use -f if you really want to add them.
hint: Disable this message with "git config set advice.addIgnoredFile false"
手元の Mac の Git 2.39.3 では、最後の hint が次の 2 行でした。ほかの行は同じです。
hint: Turn this message off by running
hint: "git config advice.addIgnoredFile false"
フォルダの中の 1 つだけ残すには?
logs/* にしてから ! で戻す
フォルダではなく、中身を除外します。Git の文書は、末尾が / のパターン(foo/)はフォルダとその下に当たり、foo/* は foo/test.json(ファイル)や foo/bar(フォルダ)に当たる、と説明しています。foo/* は、/ を含む foo/bar/hello.c には当たりません(* は / に当たらないからです)。つまり logs/* は、フォルダ logs そのものには当たりません。親のフォルダが除外されないので、!logs/keep.log が効きます。
$ cat -n .gitignore
1 logs/*
2 !logs/keep.log
logs/keep.log が、?? で出るようになりました。
$ git status --short --untracked-files=all
?? .gitignore
?? logs/keep.log
git check-ignore -v では、logs/keep.log は ! の行、logs/a.log は logs/* の行に当たります。
$ git check-ignore -v logs/keep.log logs/a.log
.gitignore:2:!logs/keep.log logs/keep.log
.gitignore:1:logs/* logs/a.log
-v を付けずに調べると、無視されるファイルだけが出ます。
$ git check-ignore logs/keep.log logs/a.log
logs/a.log
1 階層深いファイルを残す
logs/2026/keep.log のように、1 階層深いファイルは、logs/* のあとに !logs/2026/keep.log を書くだけでは戻りません。logs/2026 というフォルダが logs/* に当たって除外されるので、親のフォルダの除外になるからです。
$ cat -n .gitignore
1 logs/*
2 !logs/2026/keep.log
$ git status --short --untracked-files=all
?? .gitignore
$ git check-ignore -v logs/2026/keep.log
.gitignore:1:logs/* logs/2026/keep.log
フォルダを 1 つずつ戻します。logs/* のあとに、!logs/2026/(フォルダを戻す)、logs/2026/*(その中身を除外)、!logs/2026/keep.log(残すファイル)の 4 行にします。
$ cat -n .gitignore
1 logs/*
2 !logs/2026/
3 logs/2026/*
4 !logs/2026/keep.log
logs/2026/keep.log が出るようになりました。
$ git status --short --untracked-files=all
?? .gitignore
?? logs/2026/keep.log
$ git check-ignore -v -n logs/a.log logs/2026/b.log logs/2026/keep.log
.gitignore:1:logs/* logs/a.log
.gitignore:3:logs/2026/* logs/2026/b.log
.gitignore:4:!logs/2026/keep.log logs/2026/keep.log
logs/2026/b.log は、無視されたままです。さらに深い階層は、実行していません。
見本のファイルだけ戻す(.env.example)
GitHub のひな形(github/gitignore の Node.gitignore)には、環境変数のファイルを無視する次の 3 行があります。.env と .env.* を無視し、見本の .env.example だけを戻します。.env.example の親(リポジトリの根。いちばん上のフォルダ)は除外していないので、! が効きます。
$ cat -n .gitignore
1 .env
2 .env.*
3 !.env.example
見本のファイル .env.example だけが、?? で出ます。
$ git status --short --untracked-files=all
?? .env.example
?? .gitignore
$ git check-ignore -v -n .env .env.local .env.example
.gitignore:1:.env .env
.gitignore:2:.env.* .env.local
.gitignore:3:!.env.example .env.example
文書の例(foo/bar のほかは全部無視)
Git の文書に、フォルダ foo/bar のほかは全部無視する例があります。
$ cat -n .gitignore
1 # exclude everything except directory foo/bar
2 /*
3 !/foo
4 /foo/*
5 !/foo/bar
文書は、/* の / を付けないと、ワイルドカードが foo/bar の中まで除外するので注意、と書いています。手元で実行すると、foo/bar/y.txt だけが出ました。
$ git status --short --untracked-files=all
?? foo/bar/y.txt
$ git check-ignore -v -n .gitignore top.txt foo/x.txt foo/baz/z.txt foo/bar/y.txt
.gitignore:2:/* .gitignore
.gitignore:2:/* top.txt
.gitignore:4:/foo/* foo/x.txt
.gitignore:4:/foo/* foo/baz/z.txt
:: foo/bar/y.txt
出力の 1 行目のとおり、.gitignore 自身も /* に当たって、無視されました。この点は、文書には書かれていません。手元で試した結果です。
! を書く順番は?
同じ層(同じ優先の段)の中では、最後に当たった行が結果を決める、と文書に書かれています。だから !keep.log は、*.log よりあとに書きます。手元で *.log → !keep.log の順にすると、keep.log は戻りました。逆の順にすると、戻りません。
$ cat -n .gitignore
1 !keep.log
2 *.log
$ git status --short --untracked-files=all
?? .gitignore
$ git check-ignore -v -n debug.log keep.log
.gitignore:2:*.log debug.log
.gitignore:2:*.log keep.log
keep.log も、*.log の行に当たっています。
効いたか、どう確かめる?
git check-ignore -v で、出た行のパターン(行番号のあと)が ! で始まるなら、その行に当たって、無視されません(戻っています)。文書も、! で始まるパターンに当たったときは「無視されない」という意味、と書いています。調べたファイルではなく、その親のフォルダに当たる行(logs/keep.log に logs/、logs/2026/keep.log に logs/*)が出たら、親のフォルダが除外されたままです。
$ git check-ignore -v logs/keep.log
.gitignore:2:!logs/keep.log logs/keep.log
-v の終了コード(コマンドが終わるときに返す数)の読み方は、書き方の記事にまとめました。書いても効かないときが、追跡済みのファイルの場合は、反映されない原因の記事で説明しています。
よくある質問
名前が ! で始まるファイルは、どう書きますか?
Git の文書は、文字どおり ! で始まる名前のパターンは、先頭の ! の前に \ を付ける、と書いています(例は \!important!.txt)。手元では、\!important.txt が !important.txt に当たりました(試した .gitignore の全体は、書き方の記事にあります)。
下のフォルダの .gitignore で戻せますか?
戻せます。ただし、親のフォルダを除外していないときです(親のフォルダを除外していると、中のファイルに向けたパターンは、どこに書いても効きません)。下のフォルダの .gitignore は、上の .gitignore に勝つ(優先が高い)ので、下のフォルダの .gitignore に ! を書いても戻せます。文書の例は、根の vmlinux* を、arch/foo/kernel/.gitignore の !/vmlinux* で戻すものです。
手元では、根の .gitignore の *.log に対して、sub/.gitignore の !keep.log で、sub/keep.log が戻りました(出力の行は sub/.gitignore:1:!keep.log でした)。
$ cat -n .gitignore
1 *.log
2 build/
$ cat -n sub/.gitignore
1 !keep.log
2 /local.txt
無視されたフォルダの中の 1 つだけを追跡したいときは?
git add -f(--force)で、無視されているファイルを足せます。logs/ を無視したままでも、logs/keep.log が追跡に入りました(Git 2.54.0)。
$ git add -f logs/keep.log
$ git status --short --untracked-files=all
A logs/keep.log
?? .gitignore
追跡したあとは、そのファイルに .gitignore は効きません(追跡済みのファイルの扱いは、反映されない原因の記事にあります)。
自分だけの除外の規則にしたいときは?
人と共有しない規則は、リポジトリに入らない .git/info/exclude に書けます。コミットされず、自分の手元のリポジトリだけで効きます。使い分けは、.git/info/exclude の記事にまとめました。
まとめ
- 親のフォルダを除外すると、その中のファイルは
!で戻せない。Git が、除外したフォルダの中を見ないため logs/ではなくlogs/*で中身を除外し、そのあとに!logs/keep.logを書く- 1 階層深いファイルは、
logs/*→!logs/2026/→logs/2026/*→!logs/2026/keep.logと、フォルダを 1 つずつ戻す(手元で試した形) - 最後に当たった行が結果を決めるので、
!の行は、除外の行のあとに書く - 効いたかは
git check-ignore -vで。!の行が出たら戻っている。親のフォルダに当たる行が出たら、親のフォルダが除外されたまま - この記事で確かめていないこと:
**と!を組み合わせた書き方、Windows、Git 2.56.0 での表示
参考資料
- gitignore(Git 公式の文書)
- git-check-ignore(Git 公式の文書)
- git-add(Git 公式の文書)
- Ignoring files(GitHub Docs)
- github/gitignore の Node.gitignore
※この記事は、2026年10月23日時点の公式の資料と照らして確かめています。誤りに気づいたらお問い合わせからお知らせください。



