2

I am observing a strange behavior while using git-status with pathspecs. I would like your opinion on whether it is the expected behavior, or an undefined behavior in git.

Initial setup

$ mkdir main && cd main
$ git init .
Initialized empty Git repository in d:/temp/main/.git/
$ touch test[1].txt
$ touch test[2].txt

The following ones are understandable

$ # 1. escaped square brackets
$ git status test\[1\].txt --short        
?? test[1].txt

$ # 2. escaped square brackets with range
$ git status test\[[1-9]\].txt --short    
?? test[1].txt
?? test[2].txt

$ # 3. unmatched range, with what looks like a fallback to literal comparison
$ git status test[1].txt --short          
?? test[1].txt

Additional setup for unexpected behavior

$ touch test1.txt

Unexpected behavior

$ # 4. matched range, so no fallback to literal comparison (this one looks OK)
$ git status test[1].txt --short
?? test1.txt

$ # 5. escaped square brackets => I would expect only test[1].txt to be returned
$ git status test\[1\].txt --short
?? test1.txt
?? test[1].txt

$ # 6. escaped square brackets with range => I would expect only test[1].txt 
$ # and test[2].txt to be returned
$ git status test\[[1-9]\].txt --short
?? test1.txt
?? test[1].txt
?? test[2].txt

$ # 7. only escaping the last square bracket
$ # result looks similar to the 5th case
$ git status test[1\].txt --short
?? test1.txt
?? test[1].txt

Additional setup for more fun

$ git add test1.txt
$ rm test1.txt
$ touch test2.txt

More unexpected behavior

$ # 8. ???
$ git status test[1].txt --short
AD test1.txt
?? test[1].txt

$ # 9. We lost test1.txt ???
$ git status test[1-2].txt --short
?? test2.txt

$ # Woo... Should this really work?
$ git status test[*.txt --short
AD test1.txt
?? test2.txt
?? test[1].txt
?? test[2].txt

I'm a bit confused there. I've read the Git documentation related to pathspec and it's not that detailed.

Could anyone help me understand the logic behind?

nulltoken
  • 64,429
  • 20
  • 138
  • 130
yorah
  • 2,653
  • 14
  • 24

1 Answers1

4

There are a lot of things to discuss here but I will try to focus on: 1. the logic behind this, 2. How it modifies the behavior.

The logic

Most of the path expansion is done by the shell (hence my comment). Some is done by git, when it has what it takes.

Some tests

The setup

I used this program to investigate the issue:

include <stdio.h>

int
main(int argc, char **argv)
{
    int i;

    for (i = 1; i < argc; i++) {
            puts(argv[i]);
    }
}

I know, it's very high-skill programming.

Tests

We can now have a look at what is going on, and see how the shell modifies what git receives:

Point 1, 2, 3, 4: Everything is working fine, running the small program will give you the same.

$ ./a.out test\[1\].txt test\[[1-9]\].txt test[1].txt
test[1].txt
test[1].txt
test[2].txt
test[1].txt

Point 5, 6, 7: This time it's handled by Git, behavior is not that surprising (doing both glob and literal comparison)

$ ./a.out test\[1\].txt test\[[1-9]\].txt test[1\].txt
test[1].txt
test[1].txt
test[2].txt
test[1].txt

Point 8, 9, 10: Well, according to what we've seen before, it's no longer surprising. For 9., no bash comparison matches with test1.txt (removed, hence, ... removed)

$ ./a.out test[1].txt                         
test[1].txt

$ ./a.out test[1-2].txt          
test2.txt

$ ./a.out test[*.txt
test[1].txt
test[2].txt

Conclusion

If you want to test the way Git handles pathspec, you should enclose your path in double quotes:

$ ./a.out "test[*.txt" "test[1\].txt"
test[*.txt
test[1\].txt

Hope it helped,

Antoine Pelisse
  • 12,871
  • 4
  • 34
  • 34