67

I've been using git but still having confusion about the .gitignore file paths.

So, what is the difference between the following two paths in .gitignore file?

tmp/*
public/documents/**/*

I can understand that tmp/* will ignore all the files and folders inside it. Am I right? But what does that second line path mean?

Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
millisami
  • 9,931
  • 15
  • 70
  • 112
  • 7
    Please change your accepted answer. It is wrong, and this myth is all over the web. – cdunn2001 Jul 26 '11 at 00:10
  • Just to follow-up: It seems that `fnmatch` differs on MacOS from that on Linux. Hopefully someone else will verify this. – cdunn2001 Jul 30 '11 at 08:36
  • This thread (http://thread.gmane.org/gmane.comp.version-control.git/188174), and the lack of objections to the patch, suggests the developers don't think this works anywhere.. `Double asterisk does not have a special meaning and is interpreted just like a single asterisk`. – antak Aug 13 '12 at 07:24
  • Since git1.8.2 (Early 2013), '`**`' works better, *not* depending on the shell. See [my answer below](http://stackoverflow.com/a/14931416/6309). – VonC Jul 17 '13 at 14:15
  • @cdunn2001, the (current accepted answer)[http://stackoverflow.com/a/681294/1995714] is by John Feminella (rev 2). Is that still the same incorrect one? – cp.engr Mar 04 '16 at 18:20
  • 1
    @cp.engr, today I am unable to find a machine where `**` does not work as claimed. That includes OSX-10.11.3 (El Capitan) and Ubuntu-14.04.1 (Trusty). Possibly git-ignore as been updated, or possibly recent **fnmatch** handles `**` as people expect. So the accepted answer might now be correct in practice. – cdunn2001 Mar 08 '16 at 18:21

6 Answers6

67

This depends on the behavior of your shell. Git doesn't do any work to determine how to expand these. In general, * matches any single file or folder:

/a/*/z
 matches        /a/b/z
 matches        /a/c/z
 doesn't match  /a/b/c/z

** matches any string of folders:

/a/**/z
 matches        /a/b/z
 matches        /a/b/c/z
 matches        /a/b/c/d/e/f/g/h/i/z
 doesn't match  /a/b/c/z/d.pr0n

Combine ** with * to match files in an entire folder tree:

/a/**/z/*.pr0n
 matches        /a/b/c/z/d.pr0n
 matches        /a/b/z/foo.pr0n
 doesn't match  /a/b/z/bar.txt
John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • 16
    No. It depends on fnmatch, not the shell. bash-4 supports this, but git ignores ** on my machine, running bash-4. I am not aware of anywhere that ** means anything. Most people commenting on this have not actually tested it. If you have tested this with git (not the shell), please post your O/S. Try `git -nv` for testing. – cdunn2001 Jul 26 '11 at 00:08
  • 1
    @cdunn2001: I just double-checked; this works as expected for me. I'm on the Ubuntu nightly build with `bash version 4.2.8(1)-release (x86_64-pc-linux-gnu)`. If you're adamant that this is incorrect I will be happy to investigate further though. – John Feminella Jul 26 '11 at 18:30
  • 2
    Are you testing `bash`, or `git`? I have no doubt that `bash` works as advertised, at least with the appropriate settings. `git -nv add .` is the way to test this stuff. After trying precisely these tests, I am nearly positive that `git` does not work this way on MacOS. Maybe there is a difference between `fnmatch` on Linux and MacOS. (`git` uses `fnmatch`, not the shell, for this match.) Could you perform your tests on MacOS, to double-check my own? – cdunn2001 Jul 30 '11 at 08:34
  • I'm using `git -nv add .`. I don't have an OSX available to test, but I will ask a team member. – John Feminella Jul 30 '11 at 16:42
  • 1
    `git` is definitely using `fnmatch`, check `dir.c` in the `git` source. @cdunn2001 is 100% correct that support for ANT-style `**` is `fnmatch`-dependent. Most `fnamtch` implementations (e.g. BSD, GNU libiberty) collapse multiple stars as if it were one, not sure which implementations cater to ANT-style `**`. – vladr Oct 29 '12 at 01:56
  • Works for me, even though it's 2020. – not2qubit Nov 14 '20 at 11:58
19

Update (08-Mar-2016)

Today, I am unable to find a machine where ** does not work as claimed. That includes OSX-10.11.3 (El Capitan) and Ubuntu-14.04.1 (Trusty). Possibly git-ignore as been updated, or possibly recent fnmatch handles ** as people expect. So the accepted answer now seems to be correct in practice.


Original post

The ** has no special meaning in git. It is a feature of bash >= 4.0, via

shopt -s globstar

But git does not use bash. To see what git actually does, you can experiment with git add -nv and files in several levels of sub-directories.

For the OP, I've tried every combination I can think of for the .gitignore file, and nothing works any better than this:

public/documents/

The following does not do what everyone seems to think:

public/documents/**/*.obj

I cannot get that to work no matter what I try, but at least that is consistent with the git docs. I suspect that when people add that to .gitignore, it works by accident, only because their .obj files are precisely one sub-directory deep. They probably copied the double-asterisk from a bash script. But perhaps there are systems where fnmatch(3) can handle the double-asterisk as bash can.

cdunn2001
  • 17,657
  • 8
  • 55
  • 45
17

Note that the '**', when combined with a sub-directory (**/bar), must have changed from its default behavior, since the release note for git1.8.2 now mentions:

The patterns in .gitignore and .gitattributes files can have **/, as a pattern that matches 0 or more levels of subdirectory.

E.g. "foo/**/bar" matches "bar" in "foo" itself or in a subdirectory of "foo".


See commit 4c251e5cb5c245ee3bb98c7cedbe944df93e45f4:

"foo/**/bar" matches "foo/x/bar", "foo/x/y/bar"... but not "foo/bar".
We make a special case, when foo/**/ is detected (and "foo/" part is already matched), try matching "bar" with the rest of the string.

"Match one or more directories" semantics can be easily achieved using "foo/*/**/bar".

This also makes "**/foo" match "foo" in addition to "x/foo", "x/y/foo"..

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>


Simon Buchan also commented:

current docs (.gitignore man page) are pretty clear that no subdirectory is needed, x/** matches all files under (possibly empty) x

The .gitignore man page does mention:

A trailing "/**" matches everything inside. For example, "abc/**" matches all files inside directory "abc", relative to the location of the .gitignore file, with infinite depth.

A slash followed by two consecutive asterisks then a slash matches zero or more directories. For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • +1 for being the most accurate current answer, but current docs are pretty clear that no subdirectory is needed, `x/**` matches all files under (possibly empty) `x` - note that this may be a change from 1.8.2 – Simon Buchan Dec 05 '13 at 05:42
  • @SimonBuchan Interesting. I have included your comment in the answer for more visibility. Note that it was already part of 1.8.2 (March 2012), since only one commit has been done on that topic since: https://github.com/git/git/commits/master/wildmatch.c – VonC Dec 05 '13 at 06:41
  • Note to self: see also http://stackoverflow.com/a/20391855/6309 for a difference between ignoring `x/` and `x/**`. – VonC Dec 05 '13 at 06:52
17

If you're using a shell such as Bash 4, then ** is essentially a recursive version of *, which will match any number of subdirectories.

This makes more sense if you add a file extension to your examples. To match log files immediately inside tmp, you would type:

/tmp/*.log

To match log files anywhere in any subdirectory of tmp, you would type:

/tmp/**/*.log

But testing with git version 1.6.0.4 and bash version 3.2.17(1)-release, it appears that git does not support ** globs at all. The most recent man page for gitignore doesn't mention **, either, so this is either (1) very new, (2) unsupported, or (3) somehow dependent on your system's implementation of globbing.

Also, there's something subtle going on in your examples. This expression:

tmp/*

...actually means "ignore any file inside a tmp directory, anywhere in the source tree, but don't ignore the tmp directories themselves". Under normal circumstances, you'd probably just write:

/tmp

...which would ignore a single top-level tmp directory. If you do need to keep the tmp directories around, while ignoring their contents, you should place an empty .gitignore file in each tmp directory to make sure that git actually creates the directory.

emk
  • 60,150
  • 6
  • 45
  • 50
  • 6
    The answer is (3): the manpage clearly says that the glob will be passed un-altered to the system's fnmatch library function. Therefore the behavior of gitignore globs is system dependent. – Jörg W Mittag Mar 25 '09 at 14:05
  • You're right--the man page does suggest case (3) is the right answer. But I've encountered quite a few cases where the git man pages are slightly out of date, so I'm not going to commit to a specific answer without reading the code. – emk Mar 25 '09 at 15:13
  • 1
    BTW, tmp/* will only match at the top level. The rule is that any '/' other than a single one at the end switches you to exact-match mode. The initial slash is only needed if there are no other ones. – Ben Martin Jun 18 '12 at 15:42
5

When ** isn't supported, the "/" is essentially a terminating character for the wildcard, so when you have something like:

public/documents/**/*

it is essentially looking for two wildcard items in between the slashes and does not pick up the slashes themselves. Consequently, this would be the same as:

public/documents/*/*
Hazok
  • 5,373
  • 4
  • 38
  • 48
2

It doesn't work for me but you could create a new .gitignore in that subdirectory:

tmp/**/*.log

can be replaced by a .gitignore in tmp:

*.log
Oleks
  • 31,955
  • 11
  • 77
  • 132