.gitignore Syntax: bin vs bin/ vs. bin/* vs. bin/**
Asked Answered
I

6

99

What's the difference between adding bin, bin/, bin/* and bin/** in my .gitignore file? I've been using bin/, but looking at other .gitignore files (in the eclipse file the double and single star are even used together like this: tmp/**/* what's up with that?) I see that the first two patterns are also widely used as well. Can someone please explain the differences between the three?

Indictment answered 9/1, 2012 at 1:42 Comment(3)
@unutbu: The accepted answer for that question is apparently disputed. One of the top comments claims the answer is in fact a complete myth.Indictment
The behavior is completely specified in the manpage, and I'm sure there's a question/answer around here (or ten) that include all that information.Yvette
With respect to **: stackoverflow.com/questions/1470572/…Yvette
S
93

bin matches any files or directories named 'bin'.

bin/ matches any directories named 'bin', which in effect means all of its contents since Git doesn't track directories alone.

bin/* matches all files and directories directly in any bin/. This prevents Git automatically finding any files in its subdirectories, but if, say a bin/foo subdirectory is created, this rule will not match foo's contents.

bin/** matches all files and directories in any bin/ directory and all of its subdirectories.

The word "any" is critical here since rules are not relative to the repository root and apply anywhere in the filesystem tree. You must begin rules with a / (or !/ to un-ignore) which means the repository's root, not the system's root, in order to match only what was intended.

WARNING: You should never use rules like dir/*, /dir/**, etc. alone unless you also un-ignore something that exists inside that directory. Omit the asterisk or you could permanently lose a lot of data from certain invocations of git gc, git stash and more.

I don't really know what tmp/**/* is meant to do. I initially thought it could be used to match files in the sub-directories of tmp/ but not files directly present in tmp/ itself. But a simple test seems to suggest that this ignores all files in tmp/.

Skiascope answered 9/1, 2012 at 3:40 Comment(10)
just to clarify, what's the difference between bin/ and bin/**?Indictment
I suspect bin/ will ignore the bin directory, whereas bin/** will include the bin directory but not any of its contentsInterpellation
That seems inconsistent with Siddhartha's answer. Drawing from the answer, bin/ will ignore the directory itself (including all sub-directories and files), whereas bin/** will ignore all files in the bin directory and its sub-directories, but not the bin directory itself. Whether or not that's accurate, I'm unsure.Aleksandrovsk
note that, if you want to track all files in the bin/ directory but ignore all files in its subdirectories, you can do (on subsequent lines) bin/** \n !bin/* (since I can't see how to force a linebreak in mini-Markdown)Lyndalynde
If you're wondering why ignoring bin/ is distinct from ignoring everything inside bin/**, try excluding at least one of the files inside from the match. Adding the double asterisk lowers the precedence of the pattern below matches to specific files in the directory.Skin
the ** was the solution for me, but i had to upgrade git - 1.6 was too old and did not support ** linuxg.net/…Chiseler
And what /bin would do ? I've seen this in joomla gitignore fileProfitable
This answer is wrong in so many ways. First, git does not keep track of directories, so a .gitignore entry can only ever match directory contents, never a directory as such. Second, bin matches both a file named bin and the contents of the bin folder. Third, bin/* does match any files in its subdirectories. Did you guys even test this?Elson
Git doesn't track directories so why would it ignore a directory? Seems weird :)Moray
This answer is still incorrect. But the "Suggested edit queue is full" so I can't do anything about it myself. beginning a rule with / does not match the repository root - it matches the directory the .gitignore file is in. See https://mcmap.net/q/12414/-gitignore-folder-vs-folder (I also double checked this (in git version 2.17.1) myself)Disturbed
O
49

bin and bin/ differ only in that the latter will only match a directory.

bin/**/* is the same as bin/** (apparently since 1.8.2, according to @VonC's answer).

The tricky one, that I just spent an hour or so ripping my hair out over, is that bin/ and bin/** are not quite the same! Since the earlier ignores the directory as a whole, and the latter ignores each of the files within it, and git in nearly all cases doesn't care about directories, there is normally no difference. However, if you try to use ! to un-ignore a subpath, then you will find that git (ahem) ignores it if you ignored the parent directory! (again, rather than the directory contents)

This is clearest by example, so for a newly init-ed repository set up so:

$ cat .gitignore
ignored-file
or-dir
dir-only/
!dir-only/cant-reinclude
dir-contents/**
!dir-contents/can-reinclude

$ mkdir or-dir dir-only dir-contents

$ touch file ignored-file or-dir/ignored-file dir-only/cant-reinclude dir-contents/can-reinclude

The following untracked files exist:

$ git ls-files --other
.gitignore
dir-contents/can-reinclude
dir-only/cant-reinclude
file
ignored-file
or-dir/ignored-file

But you can see the following files are not ignored:

$ git ls-files --other --exclude-standard
.gitignore
dir-contents/can-reinclude
file

And if you try to add, you get:

$ git add dir-only/cant-reinclude
The following paths are ignored by one of your .gitignore files:
dir-only/cant-reinclude
Use -f if you really want to add them.
fatal: no files added

I consider this behavior a bug. (This is all on git version 1.8.4.msysgit.0)

Operand answered 5/12, 2013 at 5:4 Comment(2)
+1, indeed. You should consider filing an actual bug report on this because the behavior does seem unexpected.Indictment
The differing behavior of dir/ and dir/** re. un-ignoring with ! happens because "It is not possible to re-include a file if a parent directory of that file is excluded" [source]. Confusing, but done for performance reasons. See a related SO question.Electroluminescence
E
26

Note that, strictly speaking, git does not track directories, only files. It is hence not possible to add a directory, only its contents.

In the context of .gitignore however, git pretends to understand directories for the sole reason that

It is not possible to re-include a file if a parent directory of that file is excluded.
https://git-scm.com/docs/gitignore#_pattern_format

What does this mean for the exclude patterns? Let's go through them in detail:

bin

This ignores

  • files named bin.
  • the contents of folders named bin

You can whitelist ignored bin files and folders by adding subsequent ! entries, but you cannot whitelist the contents of folders named bin

bin

!bin/file_in_bin # has no effect, since bin/ is blacklisted!
!bin/* # has no effect, since bin/ is blacklisted!
!file_in_bin # has no effect, since bin/ is blacklisted!

!bin # this works

bin/

Same as above, except it does not match files named bin. Adding a trailing / tells git to match directories only.

bin/*

This ignores

  • files contained in a folder named bin
  • contents of direct subfolders of folders named bin
bin/*  # blacklists bin/file_in_bin and bin/subfolder/

!bin/subfolder/file_in_sub # has no effect, since bin/subfolder is blacklisted!
!bin # whitelists files named bin/bin, since bin/ itself is not blacklisted
!bin/ # has no effect, since bin/ itself is not blacklisted


!bin/file_in_bin # works since bin/ itself is not blacklisted
!file_in_bin # works too
!bin/subfolder # works (so implicitly whitelists bin/subfolder/file_in_sub)
!bin/subfolder/ # works just as well
!bin/* # works for file_in_bin and subfolder/

bin/**

This ignores

  • contents of bin
  • contents of subfolders (any level of nesting) within bin
bin/**  # blacklists bin/file_in_bin and
        # bin/subfolder/ and bin/subfolder/file_in_sub and
        # bin/subfolder/2/ and bin/subfolder/2/file_in_sub_2

!bin/subfolder/file_in_sub # has no effect, since bin/subfolder is blacklisted
!bin/subfolder/2/ # has no effect, since bin/subfolder is blacklisted
!bin/subfolder/2/file_in_sub_2 # has no effect, since bin/subfolder is blacklisted

!bin/subfolder # works only in combinations with other whitelist entries,
               # since all contents of subfolder are blacklisted (1)

!bin/file_in_bin # works since bin itself is not blacklisted
!bin/* # works for file_in_bin and subfolder; see (1)
Elson answered 23/2, 2016 at 5:0 Comment(0)
G
10

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


The rule to remember (and which help understand the difference of intent behind those syntax) is:

It is not possible to re-include a file if a parent directory of that file is excluded.


Typically, if you want to exclude files from a subfolder of an ignore folder f, you would do:

f/**
!f/**/
!f/a/sub/folder/someFile.txt

That is:

  • If the first rule was f/, the folder f/ would be ignored and rules below regarding f would not matter.
  • f/** achieve the same as f/, but ignore all sub-elements (files and subfolders).
    That gives you the opportunity to whitelist (exclude from gitignore) the subfolders: !f/**/.
  • Since all f subfolders are not ignored, you can add a rule to exclude a file (!f/a/sub/folder/someFile.txt)
Gutierrez answered 18/2, 2013 at 7:14 Comment(1)
How does this answer the question?Elson
M
9

I just made a new repo and tried some things. Here are my results:

NEW RESULTS

git version 2.10.1.windows.1

  1. Initialize nearly-empty repo. Only the README file
  2. Populate the bin directory several layers deep
    • bin.txt
    • Test.txt
    • bin/a/b/bin.txt
    • bin/a/b/Test.txt
    • bin/a/bin/bin.txt
    • bin/a/bin/Test.txt
    • bin/a/bin.txt
    • bin/a/Test.txt
    • bin/bin.txt
    • bin/Test.txt
  3. Add bin to gitignore: Results
    • Everything beneath the bin directory (and deeper) is now ignored
    • Root level is not ignored (/bin.txt and /Test.txt still show)
  4. Edit bin to bin/ in the gitignore: Results
    • No change
  5. Edit bin/ to bin/*
    • No change
  6. Edit bin/* to bin/**
    • No change
  7. Edit bin/** to bin/**/
    • bin/bin.txt and bin/Test.txt are no longer ignored
  8. Edit bin/**/ to bin/**/*
    • bin/bin.txt and bin/Test.txt are back to being ignored

OLD RESULTS

git version: 2.7.0.windows.1

  1. Initialize nearly-empty repo. Only the README file
  2. Populate the bin directory several layers deep
    • bin/a/b/Test.txt
    • bin/a/bin/Test.txt
    • bin/a/Test.txt
    • bin/Test.txt
  3. Add bin to gitignore: Results
    • Everything beneath the bin directory (and deeper) is now ignored
  4. Edit bin to bin/ in the gitignore: Results
    • Everything beneath the bin directory (and deeper) is still ignored (no change)
  5. Edit bin/ to bin/*
    • Everything beneath the bin directory (and deeper) is still ignored (no change)
  6. Edit bin/* to bin/**
    • Everything beneath the bin directory (and deeper) is still ignored (no change)
  7. Edit bin/** to bin/**/
    • bin/Test.txt is no longer ignored
  8. Edit bin/**/ to bin/**/*
    • Everything beneath the bin directory (and deeper) is ignored again
Moray answered 23/2, 2016 at 5:22 Comment(4)
This is a good test, I think that renaming text.txt to bin.txt would better show some key differences. If you did this I would vote for this answer.Bombardon
@MattJohnson True... unfortunately I am on a newer git version at the momentMoray
@MattJohnson I finally got around to thisMoray
Nice summary but typo/incorrect: #3: "Root level is not ignored" ... no change no change no change ... "no longer ignored"? Wait, when was it ignored? Now 5 years old the detail here doesn't matter anyway and should be rechecked in current systems.Regularize
K
0

There's another difference between bin/* and bin/.

bin/ matches foo/bin/test.txt (as expected), but bin/* does not, which seems strange, but it's documented: https://git-scm.com/docs/gitignore

"Documentation/*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html".

The reason for this appears to be these rules:

  • If the pattern ends with a slash, it is removed for the purpose of the following description …

  • If the pattern does not contain a slash /, Git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file …

  • Otherwise, Git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag …

So if the pattern ends with a slash, the slash is removed and it's treated as a shell glob pattern, in which case bin matches foo/bin/test.txt. If it ends with /*, the slash isn't removed and it's passed to fnmatch, which doesn't match in subdirectories.

However, the same is not true for foo/bin/ and foo/bin/*, because even after removing the trailing slash from foo/bin/, it still contains a slash, so it's treated as an fnmatch pattern, not a glob. I.e. it will not match bar/foo/bin/test.txt

Koenig answered 16/11, 2017 at 15:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.