Search code examples
gitgitignore

Nested .gitignore files -- difference between /folder/* and !folder/


I have the folder structure thus

project/
       ----A/
            ----B/
                 -1.txt
                 -2.txt
                 -.gitignore [ content is: (Line1) * (Line2) !1.txt ]
            -.gitignore [ content is: (Line1) /B/* ]
       -.gitignore [ content is: (Line1) /A/*
.git/
-.gitignore [content is: (Line1) /project/*]

The above does not track 1.txt nor does it track 2.txt

My understanding of project/.gitignore which contains:

/A/* 

was:

Ignore everything under folder A/ except for exceptions you may encounter in deeper .gitignores in subfolders, for instance, due to, say project/A/B/.gitignore which is:

*
!1.txt

that force you to track 1.txt. That was also my interpretation of project/A/.gitignore which is:

/B/*

That is, ignore everything under folder B/ except for exceptions you may encounter in deeper .gitignores in subfolders, for instance, due to, say project/A/B/.gitignore.

Since in the example above neither 1.txt nor 2.txt are tracked, I am unclear what the right interpretation of /A/* and /B/* mean in the context above.

Everything else being the same, the following change to project/.gitignore of:

!A/

tracks 1.txt while not tracking 2.txt.

I would like to understand clearly why /A/* does not work while !A/ works in this case.


Solution

  • The information you provide alone is not enough to reproduce your setup :

    running the following script :

    #!/bin/bash
    
    rm -rf /tmp/testrepo
    mkdir -p /tmp/testrepo
    cd /tmp/testrepo
    
    git init
    
    mkdir -p project/A/B
    
    touch project/A/B/1.txt project/A/B/2.txt
    
    check_ignore () {
            local path=$1
            echo "--- checking $path:"
            git check-ignore -v "$path"
    }
    
    echo "# with initial .gitignore files:"
    
    check_ignore project/A
    check_ignore project/A/B
    check_ignore project/A/B/1.txt
    check_ignore project/A/B/2.txt
    
    echo "!A/" >> project/.gitignore
    
    echo
    echo "# after adding '!A/' in project/.gitignore:"
    
    check_ignore project/A
    check_ignore project/A/B       # that directory is still gitignored
                                   # by the '/A/*' gitignore rule
    check_ignore project/A/B/1.txt # so its content is not inspected
    check_ignore project/A/B/2.txt
    

    I have directory B (in project/A/B) completely ignored, which makes that neither 1.txt nor 2.txt is tracked.


    If an ignore rule matches a directory, then git will not descend into that directory at all and no inner .gitignore file can act on what is tracked within it.

    So, in your case :

    • the /A/* rule will not ignore directory /A/ : git will inspect its content, and possibly apply rules described in /A/.gitignore,
    • if however no rule counters the /A/* for A/B, then B/ will be completely ignored, and neither B/1.txt nor B/2.txt will be tracked.

    Such a rule can be :

    • a !B/ rule in project/A/.gitignore
    • or a !A/B rule in project/.gitignore

    Your sentence should be adjusted :

    a /A/* pattern allows you to unignore files and folders one level down (in A/.gitignore), but .gitignore files at deeper levels will not have an impact on their own.