Why does glob pattern with double asterisk not match anything in subdirectories?

I’m trying to write a .gitignore rule to exclude some files in a particular directory that has multiple levels of subdirectories.

The folder structure looks something like this:

  • Why doesn't source control check in my CocoaPods?
  • avoid repeat commits when cherry-pick from master to branch, then merge from branch back to master
  • git: Your branch is Ahead by X commits
  • How to checkout a single file in GIT
  • Why does git rebase often have fewer merge conflicts than a merge?
  • Using a single git repository for multiple git projects
  • out
    ├─a
    │ ├─source
    │ │ ├─.keepme
    │ │ ├─183597.txt
    │ │ ├─271129.txt
    │ │ └─288833.txt
    │ └─parsed
    │   ├─.keepme
    │   ├─183597.csv
    │   ├─271129.csv
    │   └─288833.csv
    ├─b
    │ └─(...)
    
    (etc.)
    

    I would like to keep the .keepme files (so that Git saves the directory structure), so I figure I’ll write a rule to match anything under out that matches the pattern ?*.*:

    out/**/?*.*
    

    However, this does not match any files.

    I thought that ** will match any number of subdirectories; why is this not working?

    I’m running Git 1.8 in Bash 4.2 on a Fedora 18 VM.

  • What are the git concepts of HEAD, master, origin?
  • Git repo where each submodule is a branch of same repo. How to avoid double/triple… download with git clone --recursive?
  • Laravel + git, contributors have different environment
  • Add/commit to bare repo from a non-git folder
  • pdf-writer-1.1.8/lib/pdf/writer.rb:712: invalid multibyte char (US-ASCII)
  • Merging pull requests on Github - why not do it locally instead?
  • 2 Solutions collect form web for “Why does glob pattern with double asterisk not match anything in subdirectories?”

    First, make sure you are using Git 1.8.2 or up, since that’s when ** support was introduced.

    It sounds like you’re trying to exclude .keepme files by matching *.*. However, since * matches zero or more characters, it matches the empty string in front of the period in .keepme, including this file as well.

    Maybe you intended it to work like out/**/?*.*

    If you’d like to match all non-dotfiles, you can use out/**/[!.]* which will also include filenames without periods in them, like Makefile.

    I think that other guy’s answer is a good lead. The pattern out/**/[!.]*.*, which should match everything that contains a period and does not start with a period, also works in my test in bash, Arch Linux:

    $ find * -type f
    out/a/source/4232352.txt
    out/a/source/1312312.txt
    out/a/source/4234234.txt
    out/a/source/.keepme
    out/a/parsed/1231222.csv
    out/a/parsed/9593343.csv
    out/a/parsed/5675675.csv
    out/a/parsed/.keepme
    $ cat .gitignore
    out/**/[!.]*.*
    
    $ git init && git add -A && git status --ignored
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    #   new file:   .gitignore
    #   new file:   out/a/parsed/.keepme
    #   new file:   out/a/source/.keepme
    #
    # Ignored files:
    #   (use "git add -f <file>..." to include in what will be committed)
    #
    #   out/a/parsed/1231222.csv
    #   out/a/parsed/5675675.csv
    #   out/a/parsed/9593343.csv
    #   out/a/source/1312312.txt
    #   out/a/source/4232352.txt
    #   out/a/source/4234234.txt
    

    EDIT: It seems this actually doesn’t work in the git bash bundled with the Windows version of git, there none of the above files are ignored by this .gitignore.

    Git Baby is a git and github fan, let's start git clone.