Are there any different between foo/
and /foo/
in .gitignore
?
I've been reading in git-doc but I found nothing. Are those two the same?
There is a difference between /foo
and foo
.
To understand the difference, think about directories (or folders if you prefer that word) and the fact that they may contain nested sub-directories (sub-folders):
foo/a/file1
foo/b/file2
foo/file3
quux/foo/file4
Here, the top level directory foo
contains two sub-directories a
and b
plus one file, and the sub-directories contain one file each. The top-level directory quux
contains a sub-directory foo
, which contains one file.
.gitignore
lists foo
If you tell Git to ignore foo
, Git won't have to look inside foo
to find foo/a
, foo/b
, and foo/file3
. Having not looked inside foo
, Git won't look inside foo/a
or foo/b
either, and won't find either of foo/a/file1
or foo/b/file2
.
Meanwhile, nothing tells Git to skip quux
, so Git will look inside quux
and find foo
. If you tell Git to ignore foo
, Git won't have to look inside quux/foo
, so it won't find quux/foo/file4
.
.gitignore
lists /foo
insteadHowever, if you have told Git to ignore /foo
rather than foo
, when Git finds foo
inside quux
, it won't match /foo
because /foo
means only ignore foo
at the top level. So Git will look inside quux/foo
and hence will find quux/foo/file4
.
As Void Raider answered, writing foo/
in a .gitignore
tells Git that it should match (and hence not look inside) the directory foo
, but should not match (and hence will gripe about—see below) a plain file named foo
. Here, too, the leading slash remains significant: it tells Git whether to apply this to all things named foo
, or only those things at the top level.
All of the above describes the action as if you have only one .gitignore
file stored at the top level of your work-tree. But you can store one .gitignore
file per directory. If you have a quux
directory, you can create quux/.gitignore
, for instance. And, in the top level .gitignore
, you could list a name like quux/foo
, or you could list foo
in quux/.gitignore
. What do each of these do?
First, let's define some terms:
Unix-style path names are normally called either absolute if they start with /
, such as /a/b/c
, or relative if they do not, such as a/b/c
. An absolute path tells the operating system to start at the top of the host's directory tree, while a relative path tells the operating system to start at the current directory.
Unix-style shells support what is called globbing or glob actions on path names. Here, for instance, *
matches any number (including zero) of any character, so that a name like a*c
matches ac
, abc,
abbbc,
axyzc`, and so on.
Git supports (in multiple places, including in .gitattributes
and as command line options) path name patterns, which Git calls pathspecs. Different features in Git support different kinds of pathspecs. Ignore files in particular implement glob patterns.
In a .gitignore
file, any absolute path always refers to the current directory. Hence, if quux/.gitignore
exists, and contains /foo
, that matches a file or directory named quux/foo
. You could put this into the top level .gitignore
file as /quux/foo
.
In any .gitignore
file, any path name that contains an embedded /
(not at the end of the pattern) is also treated as absolute! This means that putting quux/foo
into the top level .gitignore
is the same as putting /quux/foo
into the top level .gitignore
!
Putting foo
(with no leading slash) into quux/.gitignore
means that Git would ignore quux/sub/foo
, while putting quux/foo
into the top-level .gitignore
means that Git would not ignore quux/sub/foo
, because of this peculiarity of treating embedded-slash pathnames as if they were absolute.
If a path name appears in your repository's index, no amount of fiddling with .gitignore
files will make it ignored.
To see everything that is currently in the index, use git ls-files --stage
. Note that this prints a lot of names in a large repository! This is why you normally don't ever look at the index directly: it's just too much, like trying to look at the sun. It's much better to compare what's in the index to something else. That's what git status
does, and that's where .gitignore
files really enter the picture.
When you run git status
, Git runs two comparisons. The first one compares the current, or HEAD
, commit to the index. Whatever is different here, git status
calls this staged for commit. This is because git commit
will use whatever is in the index right now to make the new snapshot. We're usually concerned not with every file that will be in the snapshot, but rather with those files that will be different in the new snapshot, as compared to the current snapshot. So that's what git status
shows us: files that are different, or staged for commit.
The second comparison compares what's in the index to what is in your work-tree. Whatever is different here, git status
calls this not staged for commit or—for some files—untracked. Some of these untracked files will be files that you do not want to commit, and you do not want Git bothering you about them.
An untracked file is quite simply defined as a path name that is not in the index at all. If that same path name exists in the work-tree, the file is untracked. Git will whine about it. Listing that path name in a .gitignore
file will tell Git: Shut up about this file, it's supposed to be untracked. But if it's tracked—if the file is already in the index—Git won't check .gitignore
for its name; it will just assume that the file should be committed.