How do .gitignore exclusion rules actually work?
Asked Answered
C

5

177

I'm trying to solve a gitignore problem on a large directory structure, but to simplify my question I have reduced it to the following.

I have the following directory structure of two files (foo, bar) in a brand new git repository (no commits so far):

a/b/c/foo
a/b/c/bar

Obviously, a git status -u shows:

# Untracked files:
...
#       a/b/c/bar
#       a/b/c/foo

What I want to do is create a .gitignore file that ignores everything inside a/b/c but does not ignore the file foo.

If I create a .gitignore thus:

c/

Then a git status -u shows both foo and bar as ignored:

# Untracked files:
...
#       .gitignore

Which is as I expect.

Now if I add an exclusion rule for foo, thus:

c/
!foo

According to the gitignore manpage, I'd expect this to to work. But it doesn't - it still ignores foo:

# Untracked files:
...
#       .gitignore

This doesn't work either:

c/
!a/b/c/foo

Neither does this:

c/*
!foo

Gives:

# Untracked files:
...
#       .gitignore
#       a/b/c/bar
#       a/b/c/foo

In that case, although foo is no longer ignored, bar is also not ignored.

The order of the rules in .gitignore doesn't seem to matter either.

This also doesn't do what I'd expect:

a/b/c/
!a/b/c/foo

That one ignores both foo and bar.

One situation that does work is if I create the file a/b/c/.gitignore and put in there:

*
!foo

But the problem with this is that eventually there will be other subdirectories under a/b/c and I don't want to have to put a separate .gitignore into every single one - I was hoping to create project-based .gitignore files that can sit in the top directory of each project, and cover all the "standard" subdirectory structure.

This also seems to be equivalent:

a/b/c/*
!a/b/c/foo

This might be the closest thing to "working" that I can achieve, but the full relative paths and explicit exceptions need to be stated, which is going to be a pain if I have a lot of files of name foo in different levels of the subdirectory tree.

Anyway, either I don't quite understand how exclusion rules work, or they don't work at all when directories (rather than wildcards) are ignored - by a rule ending in a /.

Can anyone please shed some light on this?

Is there a way to make gitignore use something sensible like regular expressions instead of this clumsy shell-based syntax?

I'm using and observe this with git-1.6.6.1 on Cygwin/bash3 and git-1.7.1 on Ubuntu/bash3.

Caterwaul answered 8/6, 2010 at 22:46 Comment(2)
related question: #2820755Tinytinya
Excellently written question! The amount of cases you tried really helped me to understand what I was doing wrong in my similar case. Thank god, I've been looking for a long time today.Standstill
H
184
/a/b/c/*
!foo

Seems to work for me (git 1.7.0.4 on Linux). The * is important as otherwise you're ignoring the directory itself (so git won't look inside) instead of the files within the directory (which allows for the exclusion).

Think of the exclusions as saying "but not this one" rather than "but include this" - "ignore this directory (/a/b/c/) but not this one (foo)" doesn't make much sense; "ignore all files in this directory (/a/b/c/*) but not this one (foo)" does. To quote the man page:

An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again.

i.e., the file has to have been excluded already to be included again. Hope that sheds some light.

Hanseatic answered 8/6, 2010 at 23:0 Comment(5)
Yes, the example you give does work fine for me too. The problem is that /a/b/* does not work if foo is in c, which means if I have multiple foo files in various subdirectories under c (e.g. d/, e/, f/g/h/, i/j/, etc) then the negate rule '!foo' won't catch those.Caterwaul
@meowsqueak Indeed it won't. If you ignore /a/b/* then there isn't a directory for foo to be in! If you're trying to ignore the whole directory tree under /a/b but include any file named "foo" which can be anywhere in the tree, I don't think it's doable with .gitignore patterns :\Hanseatic
Actually this might explain why this doesn't work for me - the following rules do not work: "/a/b/*", "!c/foo". If that worked, there might not be a problem.Caterwaul
"I don't think it's doable with .gitignore patterns" - I think you are right, unfortunately.Caterwaul
@meowsqueak You could possibly ignore /a/b/c and then write a hook to git add --force any matching file pre-commit or somethingHanseatic
S
33

I have a similar situation, my solution was to use:

/a/**/*
!/a/**/foo

That should work for an arbitrary number of intermediate directories if I read ** correctly.

Scow answered 7/3, 2012 at 15:35 Comment(0)
B
11

Here is another option:

*
!/a*
!/a/*
!/a/*/*
!/a/*/*/*

That would ignore every file and directory, except files/directories three levels deep within a.

Biblical answered 6/2, 2011 at 0:18 Comment(0)
S
7

this is definitely not clear from the .gitignore man page. This works:

*
!/a
!/a/b
!/a/b/c
!/a/b/c/foo

# don't forget this one
!.gitignore

As mentioned by Chris a directory is not even opened if it is excluded. So if you want to be able to ignore * but some files, you have to build the path to those files as above. For me this is convenient, because I want to do a code review on 1 file of a library and if I want to do another later I just add it, and all else is ignored.

Schism answered 10/11, 2010 at 0:42 Comment(0)
I
5

On a more general note, git1.8.2 will include the patch (also in its v4, prompted by some Stack Overflow question) from Adam Spiers about determining which gitignore rule actually ignores your file.

See git1.8.2 release notes and the SO question "which gitignore rule is ignoring my file":
that will be the command git check-ignore.

Insouciance answered 18/2, 2013 at 12:1 Comment(2)
Thanks for broadcasting this information. check-ignore will also tell you which rule is preventing your file from being ignored, if that's the case.Rectal
@AdamSpiers sounds great. I will sure have to play with it.Insouciance

© 2022 - 2024 — McMap. All rights reserved.