I would like to have lines in my .gitignore
file that ignore an entire directory and its subdirectories, aside from any files (and their folder structure) that end .pdf
At the moment i use;
outputs/*
!outputs/**/*.pdf
and this excludes the outputs directory and then seems to include a random selection of subdirectories and .csv's and .png's.
The folder structure is not fixed down to the pdf files (could be 1 or 8 subdirectories deep), but i just want .pdf files keeping only in that outputs/
folder. To be honest, i thought what i had was right!
I'll use this layout as an example.
$ tree outputs/
outputs/
├── foo.csv
├── foo.pdf
└── subdir
├── bar.csv
├── bar.pdf
└── subdir2
├── baz.csv
├── baz.pdf
└── subdir3
├── woof.csv
└── woof.pdf
We can see exactly how Git is interpreting the ignore file with git check-ignore -v
.
$ cat .gitignore
outputs/*
!outputs/**/*.pdf
$ git check-ignore -v outputs/**
.gitignore:1:outputs/* outputs/
.gitignore:1:outputs/* outputs/foo.csv
.gitignore:2:!outputs/**/*.pdf outputs/foo.pdf
.gitignore:1:outputs/* outputs/subdir
.gitignore:1:outputs/* outputs/subdir/bar.csv
.gitignore:1:outputs/* outputs/subdir/bar.pdf
.gitignore:1:outputs/* outputs/subdir/subdir2
.gitignore:1:outputs/* outputs/subdir/subdir2/baz.csv
.gitignore:1:outputs/* outputs/subdir/subdir2/baz.pdf
.gitignore:1:outputs/* outputs/subdir/subdir2/subdir3
.gitignore:1:outputs/* outputs/subdir/subdir2/subdir3/woof.csv
.gitignore:1:outputs/* outputs/subdir/subdir2/subdir3/woof.pdf
The problem is outputs/*
ignores all subdirectories of output/ and Git doesn't bother to look inside ignored directories. We need to unignore the subdirectories of outputs/ with !outputs/**/
.
$ cat .gitignore
outputs/*
!outputs/**/
!outputs/**/*.pdf
$ git check-ignore -v outputs/**
.gitignore:2:!outputs/**/ outputs/
.gitignore:1:outputs/* outputs/foo.csv
.gitignore:3:!outputs/**/*.pdf outputs/foo.pdf
.gitignore:2:!outputs/**/ outputs/subdir
.gitignore:3:!outputs/**/*.pdf outputs/subdir/bar.pdf
.gitignore:2:!outputs/**/ outputs/subdir/subdir2
.gitignore:3:!outputs/**/*.pdf outputs/subdir/subdir2/baz.pdf
.gitignore:2:!outputs/**/ outputs/subdir/subdir2/subdir3
.gitignore:3:!outputs/**/*.pdf outputs/subdir/subdir2/subdir3/woof.pdf
Whoops, we also unignored the CSV files in the subdirectories! This is because outputs/*
only ignores the first level of outputs/. To ignore everything under outputs/ change outputs/*
to outputs/**
.
$ cat .gitignore
outputs/**
!outputs/**/
!outputs/**/*.pdf
$ git check-ignore -v outputs/**
.gitignore:2:!outputs/**/ outputs/
.gitignore:1:outputs/** outputs/foo.csv
.gitignore:3:!outputs/**/*.pdf outputs/foo.pdf
.gitignore:2:!outputs/**/ outputs/subdir
.gitignore:1:outputs/** outputs/subdir/bar.csv
.gitignore:3:!outputs/**/*.pdf outputs/subdir/bar.pdf
.gitignore:2:!outputs/**/ outputs/subdir/subdir2
.gitignore:1:outputs/** outputs/subdir/subdir2/baz.csv
.gitignore:3:!outputs/**/*.pdf outputs/subdir/subdir2/baz.pdf
.gitignore:2:!outputs/**/ outputs/subdir/subdir2/subdir3
.gitignore:1:outputs/** outputs/subdir/subdir2/subdir3/woof.csv
.gitignore:3:!outputs/**/*.pdf outputs/subdir/subdir2/subdir3/woof.pdf
And now it works.
$ git add outputs/
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: outputs/foo.pdf
new file: outputs/subdir/bar.pdf
new file: outputs/subdir/subdir2/baz.pdf
new file: outputs/subdir/subdir2/subdir3/woof.pdf
However, I would instead recommend not mixing up build artifacts with generated files you want to keep. Instead, change your build process to write those PDF files somewhere else. Then your .gitignore becomes simpler, and cleaning up outputs/ is simply rm -rf outputs/*