gitignore: Ignore all files in folder hierarchy except one specific filetype
Asked Answered
P

3

28

I'd like to ignore all files below and in a folder except a specific filetype that could be somewhere in the folders hierarchy:

Example

/Test
/Test/unknown/folder/structure/below

Now I'd like to ignore all files in and below the Test folder except a certain css file named layout.css, e.g.:

/Test/layout.css
/Test/fileto.ignore
/Test/another/folder/ig.nore
/Test/in/a/unknown/folder/layout.css
/Test/in/a/unknown/folder/ignore.me

.gitignore should ignore

/Test/fileto.ignore
/Test/another/folder/ig.nore
/Test/in/a/unknown/folder/ignore.me

My .gitignore file does not work:

Test/
!layout.css

Any suggestions?

Privilege answered 18/10, 2011 at 7:16 Comment(3)
Have you tried in the other order too?Cooperate
@Jan Yes, I did - unfortunately without successTichon
See: stackoverflow.com/a/17821597 for a centralized solution.Macrophage
M
30

I was able to get your example to work by putting a .gitignore file in the Test/ directory with the following contents.

*
!*/
!.gitignore
!layout.css
Mahone answered 18/10, 2011 at 8:1 Comment(3)
David, thanks for your answer. Since I've a lot of those Test folders spread around, I'd prefer a 'centralized' solution than having various .gitignore-files throughout.Tichon
@Privilege Unfortunately, I was not able to find a way to do that. You may have tried to adapt my solution and use it in the top-level directory by appending Test to the first two lines (and removing the third line). If you did, you discovered that does not solve the problem. Hopefully someone has an idea how to provide a more complete solution.Mahone
Yes, I tried various combinations and some variations of your solution but as you said it doesn't work. Anyway, thanks for spending your time.Tichon
T
14

Quite an old question, but since I was struggling with this issue even now, I figured I would share the actual solution to the problem.

The thing is, while you cannot commit empty directories in Git as if they were ignored, this rule does not apply to .gitignore.

From https://git-scm.com/docs/gitignore

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.

** Matches everything inside - literally everything - files AND directories.

This leads us to another point in gitignore docs:

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

If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in Git).

Now, let's say we have a following dir structure:

/path/to/git/repo
|-- .gitignore
|-- /cache
    |-- somefile
    |-- README    
    |-- /dir
        |-- somefile2
        |-- README

And we want to ignore all files inside cache/ except for README files.

If we specify gitignore like this:

/cache/**
!README

Git will ignore everything except for /cache/README. The other README won't show up because its directory /cache/dir was excluded with /cache/** and !README won't even apply to it. To solve the issue we need to specify gitignore as this:

# Ignore everything inside cache/ dir
/cache/**
# Except for subdirectories(won't be commited anyway if there is no commited file inside)
!/cache/**/
# And except for README files
!README
Tole answered 13/12, 2016 at 0:54 Comment(2)
Amazingly, this is the only answer that actually answers the original question.Pitchfork
Confirming that this works for me. It's crucial to have the !/cache/**/ (Came here from https://mcmap.net/q/12120/-correctly-ignore-all-files-recursively-under-a-specific-folder-except-for-a-specific-file-type)Abdominal
M
12

In order to accomplish what you want, you'll need to use some negative exclusions.

The basic idea is that you need to exclude every parent directory of any file that you want unignored.

So, if you want /Test/in/a/unknown/folder/layout.css to be added to your git repo, then you'll have to unignore /Test/, /Test/in/, /Test/in/a/, /Test/in/a/unknown/, and /Test/in/a/unknown/folder/.

Then, when you finally get to the directory with some ignored and some unignored files, you'll need to specify each individually as follows:

# Fille: .gitignore
!Test/
Test/*
!Test/layout.css
Test/fileto.ignore
!Test/another/
Test/another/*
!Test/another/folder/
Test/another/folder/*
!Test/in/
Test/in/*
!Test/in/a/
Test/in/a/*
!Test/in/a/unknown/
Test/in/a/unknown/*
!Test/in/a/unknown/folder/
Test/in/a/unknown/folder/ignore.me
!Test/in/a/unknown/folder/layout.css

So when you run $ git add-all you'll see your desired results:

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   .gitignore
    new file:   Test/in/a/unknown/folder/layout.css
    new file:   Test/layout.css

Note: You can find an explanation of why git add-all is the best way to add files to a git repo at http://lexsheehan.blogspot.com/2014/05/git-add-update.html

Maiolica answered 12/2, 2015 at 23:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.