146

Let's say I have ignored a directory, but I want to unignore specific subdirectories therein. So I have the setup:

/uploads/
/uploads/rubbish/
/uploads/rubbish/stuff/KEEP_ME/
/uploads/foo/
/uploads/foo/bar/lose/

And I want to ignore everything but the KEEP_ME directory. I would hope the ignore would look something like:

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

But that's not working, and neither are several permutations on the same theme.

One that does work is

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

But that feels a little restrictive and verbose?

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
Wil
  • 4,887
  • 3
  • 22
  • 30
  • 1
    The code you provided didn't work for me (using git version 1.9.1). One that worked is /uploads/* !/uploads/rubbish/ /uploads/rubbish/* !/uploads/rubbish/stuff /uploads/rubbish/stuff/* !/uploads/rubbish/stuff/keep (which is similar to example in http://git-scm.com/docs/gitignore). My folder struct; └ README.md └ uploads/foo/bar/lose/lose.txt └ uploads/rubbish/stuff/keep/keep.txt – gihanchanuka Nov 14 '14 at 13:09

6 Answers6

166

According to pattern format section of the gitignore documentation:

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".

Therefore the previously-excluded parent directory /uploads/rubbish/stuff/keep/ pattern must be exclusively negated before negating its content:

#ignore everything within /uploads/ 
/uploads/*

#include everything within /uploads/rubbish/stuff/keep
!/uploads/rubbish/stuff/keep/  
!/uploads/rubbish/stuff/keep/*

To include subdirectories inside /uploads/rubbish/stuff/keep/ add the third line:

!/uploads/rubbish/stuff/keep/**/*
Buo-ren Lin
  • 142
  • 8
Art Shayderov
  • 5,002
  • 1
  • 26
  • 33
  • 1
    What version of git were you using? Maybe it's something only available in certain versions. –  Mar 06 '13 at 21:09
  • Embarrassed. I forgot I posted this comment. I got it working, though. FWIW, I don't use the leading slashes. I don't remember if that was the issue. I am using 1.7.2.5. –  Mar 07 '13 at 21:44
  • I have a global gitignore file where I ignored all *.zip file. Yet for a particular project, I want to include zip files. I added this line to that project's `.gitignore` and it works great!: `!*.zip` – Jinghao Shi Mar 21 '15 at 03:44
  • I found it necessary to explicitly specify arbitrary directories both before and after the given folder **both while excluding the folder and while including the sub-folder**. ([Full answer](https://stackoverflow.com/a/56775820/1048186)). – Josiah Yoder Jun 26 '19 at 15:02
  • 1
    This method does not work (as of git 2.25 -- maybe something in git changed). I posted https://stackoverflow.com/a/60288435/295691 below as a correction. – user295691 Feb 18 '20 at 19:50
  • Note that if you accidentally ignore the directory *directly* with `/uploads/`, instead of ignoring its _contents_ with `/uploads/*`, then the attempts to UNignore something within that ignored dir by using the `!` rules will NOT work. I write about this [in my answer here to "Force add despite the .gitignore file"](https://stackoverflow.com/a/67551691/4561887). – Gabriel Staples May 15 '21 at 22:27
46

Even if you add something to .gitignore, you can force git to add it to the index

git add --force uploads/rubbish/stuff/KEEP_ME/

However, "KEEP_ME" seems to be a directory and git usually doesnt like empty folder, so you should can add a "placeholder"-holder file instead, if the folder is empty

git add --force uploads/rubbish/stuff/KEEP_ME/.keep_me
KingCrunch
  • 128,817
  • 21
  • 151
  • 173
  • The only issue with that is that this specific example refers to a global gitignore which I should have mentioned. I know I can force add items, but I'll have to do that if any new items are added, as well as initially for each new repository. Does the .keep ensure the contents are not ignored? – Wil Mar 12 '11 at 08:32
  • 1
    Did a little test. Yes, you have to use `git add --force` for every new item or folder in `KEEP_ME`. but after that, updates just get added (when doing a `git add -u` for example) as if the files are 'unignored'. The `.keep_me` does nothing, its just a file so that git operates on the folder. – commonpike Oct 21 '15 at 14:56
  • 3
    `git add --force` is the wrong solution for this. If you move a directory around or move the file to another directory, git's tracking will delete the file but not add it to the new location and you have to do `git add --force` again. by unignoring on a generic pattern, git will track the movement of a file within the repo. – Merlin Nov 26 '17 at 07:21
  • This is the only solution that worked for me. My folder was NOT ignored, but I could not add it by any means. This way solved it for me. – Akito May 02 '19 at 04:02
13

The solution presented as the most-upvoted answer is incorrect, and easily demonstrable as such.

Start with ignoring everything in uploads/*:

mkdir -p uploads/rubbish/stuff/KEEP_ME
touch uploads/a uploads/rubbish/a uploads/rubbish/stuff/a uploads/rubbish/stuff/KEEP_ME/a
echo '/uploads/*' >> .gitignore
git init
git add .
git commit -m "Initial commit"

Now unignore the parent directory of the ignored stuff as above:

echo 'uploads/rubbish/stuff/KEEP_ME/' >> .gitignore
echo 'uploads/rubbish/stuff/KEEP_ME/*' >> .gitignore
git status -u

Shows no untracked files.

In order to get it to work, you need to ignore all files under the uploads/ tree (uploads/**/*, not just the top level, uploads/*) and then add all parent directories of the tree you want to keep

echo '/uploads/**/*' > .gitignore
echo '!/uploads/rubbish/' >> .gitignore
echo '!/uploads/rubbish/stuff' >> .gitignore
echo '!/uploads/rubbish/stuff/KEEP_ME' >> .gitignore
echo '!/uploads/rubbish/stuff/KEEP_ME/*' >> .gitignore
git status -u

Which gives:

On branch master
...
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        uploads/rubbish/stuff/KEEP_ME/a

If we had used uploads/* in the .gitignore above, then all the intermediate files would have been included as well, so for example uploads/rubbish/a would show up in the status command above.

user295691
  • 7,108
  • 1
  • 26
  • 35
9

Buo-Ren Lin and John's answers are quite helpful, but I needed to combine both.

When wanting to exclude other sub-folders as well as files within uploads, I found it necessary to explicitly specify arbitrary directories before the given folder both while excluding the folder and while including the sub-folder

**/uploads/*
!**/uploads/rubbish/
!**/uploads/rubbish/*

I also found it necessary to explicitly re-include both the sub-folder and its contents to show both items within the folder and sub-folders.

Josiah Yoder
  • 3,321
  • 4
  • 40
  • 58
6

Was trying to figure out how to include a specific folder when excluding all folders with the generic exclusion

**/build

if you add the /* to the end of your generic exclude you continue to exclude all build files **/build/*

Then you add another line to correct for the path that you want to be included to look like

!**/folder/build/* 

leaving us a gitignore that reads

**/build/* 
!**/folder/build/* 
rkeet
  • 3,406
  • 2
  • 23
  • 49
John
  • 61
  • 1
  • 1
0

git version 2.30.1 (Apple Git-130)

For people like me who need to unignore one type of files, the ! method also works. Also, like Art Shayderov mentioned, this method is still listed in git's official doc

So my case is that the dsym files are completely ignored from our iOS project. However, we need to check in some necessary dsym files that come with the xcframeworks. Here is part of my .gitignore

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
!*.dSYM

After I added the last line and saved the file, those dsym immediately show up in my source control tool.

infinity_coding7
  • 434
  • 5
  • 16