sindresorhus / globby

User-friendly glob matching
MIT License
2.52k stars 130 forks source link

The pattern in the subdirectory gitignore is not correctly excluded #146

Open sounisi5011 opened 4 years ago

sounisi5011 commented 4 years ago

file structure:

.
├── dir
│   ├── .gitignore
│   ├── bar.log
│   └── subdir
│       └── baz.log
└── foo.log

./dir/.gitignore:

*.log

List of files to be excluded by git:

$ git check-ignore ./foo.log ./dir/bar.log ./dir/subdir/baz.log
./dir/bar.log
./dir/subdir/baz.log

If globby works correctly, only the foo.log file should be output:

const globby = require('globby');

(async () => {
  const paths = await globby(['**/*.log'], { gitignore: true });
  console.log(paths);
  // expected: [ 'foo.log' ]
})();

However, globby@11.0.1 outputs like this:

[ 'foo.log', 'dir/subdir/baz.log' ]

The file dir/subdir/baz.log is not excluded!


When globby@11.0.1 parses the .gitignore in a subdirectory, it adds the path of the parent to the beginning of each rule:

https://github.com/sindresorhus/globby/blob/v11.0.1/gitignore.js#L18-L24

So, internally, globby@11.0.1 has changed the contents of the ./dir/.gitignore file to look like this:

- *.log
+ dir/*.log

Therefore, you can avoid this problem by modifying the contents of the ./dir/.gitignore file as follows:

**/*.log

However, this writing style is redundant. Most projects will probably be written in the *.log style.

I believe this is a bug that should be fixed.

sounisi5011 commented 4 years ago

According to .gitignore spec 2.22.1:

  • The slash / is used as the directory separator. Separators may occur at the beginning, middle or end of the .gitignore search pattern.

  • If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself. Otherwise the pattern may also match at any level below the .gitignore level.

  • For example, a pattern doc/frotz/ matches doc/frotz directory, but not a/doc/frotz directory; however frotz/ matches frotz and a/frotz that is a directory (all paths are relative from the .gitignore file).

A patch of code that conforms to this specification is as follows:

 const mapGitIgnorePatternTo = base => ignore => {
+   const isSubLevelMatch = /^[^/]+\/?$/.test(ignore);
+
    if (ignore.startsWith('!')) {
+       if (isSubLevelMatch) {
+           return '!' + path.posix.join(base, '**', ignore.slice(1));
+       }
        return '!' + path.posix.join(base, ignore.slice(1));
    }

+   if (isSubLevelMatch) {
+       return path.posix.join(base, '**', ignore);
+   }
    return path.posix.join(base, ignore);
 };
sounisi5011 commented 3 years ago

Is there any progress on this? It's been a year since I reported this bug, and it still doesn't seem to have been fixed. https://github.com/sindresorhus/globby/blob/c1a3b3244ae992ba7e7e76f501a510ea0d9306df/gitignore.js#L18-L24