Make .gitignore ignore everything except a few files

Viewed 739838

I understand that a .gitignore file cloaks specified files from Git's version control.

How do I tell .gitignore to ignore everything except the files I'm tracking with Git? Something like:

# Ignore everything:
*

# Do not ignore these files:
script.pl
template.latex
28 Answers

An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again. If a negated pattern matches, this will override lower precedence patterns sources.

# Ignore everything
*

# But not these files...
!.gitignore
!script.pl
!template.latex
# etc...

# ...even if they are in subdirectories
!*/

# if the files to be tracked are in subdirectories
!*/a/b/file1.txt
!*/a/b/c/*
# ignore these
*
# except foo
!foo

Let's toolify!

As @Joakim said, to ignore a file, you can use something like below.

# Ignore everything
*

# But not these files...
!.gitignore
!someFile.txt

but if the file is in nested directories, it's a little difficult to write those rules.

For example, if we want to skip all files but not a.txt, located in aDir/anotherDir/someOtherDir/aDir/bDir/cDir. Then, our .gitignore will be something like this

# Skip all files
*

# But not `aDir/anotherDir/someOtherDir/aDir/bDir/cDir/a.txt`
!aDir/
aDir/*
!aDir/anotherDir/
aDir/anotherDir/*
!aDir/anotherDir/someOtherDir/
aDir/anotherDir/someOtherDir/*
!aDir/anotherDir/someOtherDir/aDir/
aDir/anotherDir/someOtherDir/aDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/
aDir/anotherDir/someOtherDir/aDir/bDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/cDir/
aDir/anotherDir/someOtherDir/aDir/bDir/cDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/cDir/a.txt

This is quite hard to write by hand.

To solve this hurdle, I've created a web app named git-do-not-ignore which will generate the rules for you.

Tool

Demo

Sample Input

aDir/anotherDir/someOtherDir/aDir/bDir/cDir/a.txt

Sample Output

!aDir/
aDir/*
!aDir/anotherDir/
aDir/anotherDir/*
!aDir/anotherDir/someOtherDir/
aDir/anotherDir/someOtherDir/*
!aDir/anotherDir/someOtherDir/aDir/
aDir/anotherDir/someOtherDir/aDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/
aDir/anotherDir/someOtherDir/aDir/bDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/cDir/
aDir/anotherDir/someOtherDir/aDir/bDir/cDir/*
!aDir/anotherDir/someOtherDir/aDir/bDir/cDir/a.txt

Had the similar issue as OP but none of top 10 upvoted answer actually worked.
I finally found out the following

Wrong syntax :

*
!bin/script.sh

Correct syntax :

*
!bin
!bin/script.sh

Explanation from gitignore man page :

An optional prefix "!" which negates the pattern; any matching file excluded by a previous pattern will become included again. 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.

Which means that "Wrong syntax" above is wrong because bin/script.sh cannot be reincluded as bin/ is ignored. That's all.

Extended example :

$ tree .

.
├── .gitignore
└── bin
    ├── ignore.txt
    └── sub
        └── folder
            └── path
                ├── other.sh
                └── script.sh

$ cat .gitignore

*
!.gitignore
!bin
!bin/sub
!bin/sub/folder
!bin/sub/folder/path
!bin/sub/folder/path/script.sh

$ git status --untracked-files --ignored

On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        bin/sub/folder/path/script.sh

Ignored files:
  (use "git add -f <file>..." to include in what will be committed)
        bin/ignore.txt
        bin/sub/folder/path/other.sh

nothing added to commit but untracked files present (use "git add" to track)

You can use git config status.showUntrackedFiles no and all untracked files will be hidden from you. See man git-config for details.

This is how I keep the structure of folders while ignoring everything else. You have to have a README.md file in each directory (or .gitkeep).

/data/*
!/data/README.md

!/data/input/
/data/input/*
!/data/input/README.md

!/data/output/
/data/output/*
!/data/output/README.md

This is how I did it:

# Ignore everything
*

# Whitelist anything that's a directory
!*/

# Whitelist some files
!.gitignore

# Whitelist this folder and everything inside of it
!wordpress/wp-content/themes/my-theme/**

# Ignore this folder inside that folder
wordpress/wp-content/themes/my-theme/node_modules

# Ignore this file recursively
**/.DS_Store

Use gig status -u to view individual files in untracked directories recursively - with git status you'd only see folders, which could fool you into thinking that everything inside them was tracked

I had a problem with subfolder.

Does not work:

/custom/*
!/custom/config/foo.yml.dist

Works:

/custom/config/*
!/custom/config/foo.yml.dist

I tried all answers as given here above, but none worked for me. After reading the gitignore documentation (here) i found out that if you exclude a folder first that the filenames in the subfolder are not being indexed. So if you use the exclamation mark afterwards to include a file, it is not found in the index and thus not being included in your git client.

That was the way to finding the solution. I started with adding exceptions for all subfolders in my folder tree to get it working, which is a hell of a job. Afterwards i was able to compact the detailed configuration to the configuration below, which is a bit contrary to the documentation..

Working .gitignore:

# Ignore the 'Pro' folder, except for the '3rdparty' subfolder 
/Pro/*
!Pro/3rdparty/

# Ignore the '3rdparty' folder, except for the 'domain' subfolder
/Pro/3rdparty/*
!Pro/3rdparty/domain/

# Ignore the 'domain' folder, except for the 'modulename' subfolder
Pro/3rdparty/domain/*
!Pro/3rdparty/domain/modulename/

As result i see in my git client that only the two files inside the Pro/3rdparty/domain/modulename/ folder are being staged for the next commit, and that was exactly what i was looking for.

And if you need to whitelist several subfolders of the same folder then group the exclamation mark lines below the exclude statement like this:

# Ignore the 'Pro' folder, except for the '3rdparty' subfolder 
/Pro/*
!Pro/3rdparty/

# Ignore the '3rdparty' folder, except for the 'domain' & 'hosting' subfolders
/Pro/3rdparty/*
!Pro/3rdparty/domain/
!Pro/3rdparty/hosting/

# Ignore the 'domain' folder, except for the 'modulename' subfolder
Pro/3rdparty/domain/*
!Pro/3rdparty/domain/modulename/

# Ignore the 'hosting' folder, except for the 'modulename' subfolder
Pro/3rdparty/hosting/*
!Pro/3rdparty/hosting/modulename/

Else it wont work as expected.

a lot of complex answers here... here's something pretty simple that I don't see above and works well in many scenarios:

# ignore all files that have an extension
**/*.*

# except for these extensions
!**/*.extension1
!**/*.extension2

this doesn't ignore any extensionless files, but if you have a bunch that all have the same or similar name, you can add exclusions for those

**/EXTENSIONLESSFILETOIGNORE

Simple solution if you need to ignore everything except few files and few root folders:

/*
!.gitignore
!showMe.txt
!my_visible_dir

The magic is in /* (as described above) it ignores everything in the (root) folder BUT NOT recursively.

The simplest way that I go about this is to force add a file. It will be accounted for in git even if it is buried or nested inside a git-ignored subdirectory tree.

For example:

x64 folder is excluded in .gitignore:

x64/

But you want to include the file myFile.py located in x64/Release/ directory. Then you have to:

git add -f x64/Release/myFile.py

You can do this for multiple files of files that match a pattern e.g.

git add -f x64/Release/myFile*.py

and so on.

I got this working

# Vendor
/vendor/braintree/braintree_php/*
!/vendor/braintree/braintree_php/lib

Nothing worked for me so far because I was trying to add one jar from lib.

This did not worked:

build/*
!build/libs/*
!build/libs/
!build/libs/myjarfile.jar 

This worked:

build/*
!build/libs

I also had some issues with the negation of single files. I was able to commit them, but my IDE (IntelliJ) always complained about ignored files, which are tracked.

git ls-files -i --exclude-from .gitignore

Displayed the two files, which I've excluded this way:

public/
!public/typo3conf/LocalConfiguration.php
!public/typo3conf/PackageStates.php

In the end, this worked for me:

public/*
!public/typo3conf/
public/typo3conf/*
!public/typo3conf/LocalConfiguration.php
!public/typo3conf/PackageStates.php

The key was the negation of the folder typo3conf/ first.

Also, it seems that the order of the statements doesn't matter. Instead, you need to explicitly negate all subfolders, before you can negate single files in it.

The folder !public/typo3conf/ and the folder contents public/typo3conf/* are two different things for .gitignore.

Great thread! This issue bothered me for a while ;)

I seem to have found something that worked for me which no one else mentioned.

# Ignore everything
*

# But not these files...
!.gitignore
!script.pl
!template.latex
# etc...

# And if you want to include a sub-directory and all sub-directory and files under it, but not all sub-directories
!subdir/
!subdir/**/*

Basically, it seems to negate a sub-directory from being ignored, you have to have two entries, one for the sub-directory itself !subdir/ and then another one which expands to all files and folders under it !subdir/**/*

It's a disaster that after 16 years (2005) of using git, there is no simple and obvious way to exclude one file when located deep in a directory hierarchy, that should otherwise be ignored. Instead we need to resort to crazy things like repeatedly digging down the structure and excluding and including directories in the correct order. Something which is darn right impossible to remember those 4 times a year you need to do it.

The only smart, but very weird alternative, is to use the git add --force. Something which is bound to fail at some point, when you're not working on the gitted repo alone.

So I wrote a small bash function to fix this for you, for easy copy paste. Let me first explain the one liner:

f=0; y='';for x in $(echo "uploads/rubbish/stuff/KEEP_ME/a.py" | tr '\/' '\n'); do y="$y/$x"; if [ $(($f)) -eq 0 ]; then y="$x"; f=1; fi; echo -e '!'"$y/\n$y/*"; done | sed '$d' |sed -z 's/..$//'; echo;

# output:
!uploads/
uploads/*
!uploads/rubbish/
uploads/rubbish/*
!uploads/rubbish/stuff/
uploads/rubbish/stuff/*
!uploads/rubbish/stuff/KEEP_ME/
uploads/rubbish/stuff/KEEP_ME/*
!uploads/rubbish/stuff/KEEP_ME/a.py

Description

  • There is an if statement to adjust for the 1st item not getting a /.
  • tr '\/' '\n' - translate the / to \n in standard POSIX paths (to get a list)
  • y="$y/$x" - add the next sub-dirctory (in list) after the previous directory.
  • echo -e '!'"$y/\n$y/*" -
    print the 2 lines of sub-directory item, 1st with ! prefix, and 2nd with /* postfix.
  • sed '$d' - remove last line
  • sed -z 's/..$//' - remove last 2 characters /* from last line

Then we create the function:

function gitkeep () { f=0; y='';for x in $(echo "$1" | tr '\/' '\n'); do y="$y/$x"; if [[ f -eq 0 ]]; then y="$x"; f=1; fi; echo -e '!'"$y/\n$y/*"; done | sed '$d' |sed -z 's/..$//'; echo; }

# gitkeep "uploads/rubbish/stuff/KEEP_ME/a"

!uploads/
uploads/*
!uploads/rubbish/
uploads/rubbish/*
!uploads/rubbish/stuff/
uploads/rubbish/stuff/*
!uploads/rubbish/stuff/KEEP_ME/
uploads/rubbish/stuff/KEEP_ME/*
!uploads/rubbish/stuff/KEEP_ME/a

Here I simplified the arithmetic if statement to if [[ f -eq 0 ]];.

Enjoy!

Not sure if this has been pointed out already but I was having trouble ignoring a subfolder inside an ignored folder using the ! in front of the folder i want gitignore to ignore.

However I figured out that the folder that was being ignored had no * in its path. My .gitignore looked like this:

/folder/
!/folder/subfolder

The subfolder was still being ignored, but adding a * after folder made it work like so

/folder/*
!/folder/subfolder

Gist

# Ignore everything
*

# But not these files...
!script.pl
!template.latex

And probably include:

!.gitignore

Reference

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

An optional prefix "!" which negates the pattern; any matching file excluded by a previous pattern will become included again. 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. Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!", for example, "\!important!.txt".

...

Example to exclude everything except a specific directory foo/bar (note the /* - without the slash, the wildcard would also exclude everything within foo/bar):

$ cat .gitignore
# exclude everything except directory foo/bar
/*
!/foo
/foo/*
!/foo/bar

Via github.com/gitinclude your can download a command line tool (for windows, linux and osx) for including deeply nested sets of files.

Define the file sets in a .gitinclude and execute the tool (via double click or command line). Your .gitignore is now updated.

Related