Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
This PR was squashed before being merged into the 4.3-dev branch (closes #30448). Discussion ---------- [Finder] Ignore paths from .gitignore #26714 | Q | A | ------------- | --- | Branch? | master for features | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #26714 | License | MIT | Doc PR | symfony/symfony-docs#... <!-- required for new features --> <!-- Write a short README entry for your feature/bugfix here (replace this comment block.) This will help people understand your PR and can be used as a start of the Doc PR. Additionally: - Bug fixes must be submitted against the lowest branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against the master branch. --> Implementation of feature request #26714 Finder::ignoreVCS() is great at ignoring file patterns for the files created by popular VCS systems. However, it would be great to be able to instruct Finder to actually exclude the paths excluded by .gitignore. So if we have .gitignore: vendor/ cache/ Finder::create() ->files() ->ignoreVCS(true) // <--- Ignores `.git` ->ignoreVCSIgnored(true); // <--- Ignores vendor/ and cache/ Commits ------- 9491393 [Finder] Ignore paths from .gitignore #26714
- Loading branch information
Showing
8 changed files
with
297 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Finder; | ||
|
||
/** | ||
* Gitignore matches against text. | ||
* | ||
* @author Ahmed Abdou <mail@ahmd.io> | ||
*/ | ||
class Gitignore | ||
{ | ||
/** | ||
* Returns a regexp which is the equivalent of the gitignore pattern. | ||
* | ||
* @param string $gitignoreFileContent | ||
* | ||
* @return string The regexp | ||
*/ | ||
public static function toRegex(string $gitignoreFileContent): string | ||
{ | ||
$gitignoreFileContent = preg_replace('/^[^\\\\]*#.*/', '', $gitignoreFileContent); | ||
$gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent); | ||
$gitignoreLines = array_map('trim', $gitignoreLines); | ||
$gitignoreLines = array_filter($gitignoreLines); | ||
|
||
$ignoreLinesPositive = array_filter($gitignoreLines, function (string $line) { | ||
return !preg_match('/^!/', $line); | ||
}); | ||
|
||
$ignoreLinesNegative = array_filter($gitignoreLines, function (string $line) { | ||
return preg_match('/^!/', $line); | ||
}); | ||
|
||
$ignoreLinesNegative = array_map(function (string $line) { | ||
return preg_replace('/^!(.*)/', '${1}', $line); | ||
}, $ignoreLinesNegative); | ||
$ignoreLinesNegative = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesNegative); | ||
|
||
$ignoreLinesPositive = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesPositive); | ||
if (empty($ignoreLinesPositive)) { | ||
return '/^$/'; | ||
} | ||
|
||
if (empty($ignoreLinesNegative)) { | ||
return sprintf('/%s/', implode('|', $ignoreLinesPositive)); | ||
} | ||
|
||
return sprintf('/(?=^(?:(?!(%s)).)*$)(%s)/', implode('|', $ignoreLinesNegative), implode('|', $ignoreLinesPositive)); | ||
} | ||
|
||
private static function getRegexFromGitignore(string $gitignorePattern): string | ||
{ | ||
$regex = '('; | ||
if (0 === strpos($gitignorePattern, '/')) { | ||
$gitignorePattern = substr($gitignorePattern, 1); | ||
$regex .= '^'; | ||
} else { | ||
$regex .= '(^|\/)'; | ||
} | ||
|
||
if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) { | ||
$gitignorePattern = substr($gitignorePattern, 0, -1); | ||
} | ||
|
||
$iMax = \strlen($gitignorePattern); | ||
for ($i = 0; $i < $iMax; ++$i) { | ||
$doubleChars = substr($gitignorePattern, $i, 2); | ||
if ('**' === $doubleChars) { | ||
$regex .= '.+'; | ||
++$i; | ||
continue; | ||
} | ||
|
||
$c = $gitignorePattern[$i]; | ||
switch ($c) { | ||
case '*': | ||
$regex .= '[^\/]+'; | ||
break; | ||
case '/': | ||
case '.': | ||
case ':': | ||
case '(': | ||
case ')': | ||
case '{': | ||
case '}': | ||
$regex .= '\\'.$c; | ||
break; | ||
default: | ||
$regex .= $c; | ||
} | ||
} | ||
|
||
$regex .= '($|\/)'; | ||
$regex .= ')'; | ||
|
||
return $regex; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<?php | ||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Finder\Tests; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Finder\Gitignore; | ||
|
||
class GitignoreTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provider | ||
* | ||
* @param string $patterns | ||
* @param array $matchingCases | ||
* @param array $nonMatchingCases | ||
*/ | ||
public function testCases(string $patterns, array $matchingCases, array $nonMatchingCases) | ||
{ | ||
$regex = Gitignore::toRegex($patterns); | ||
|
||
foreach ($matchingCases as $matchingCase) { | ||
$this->assertRegExp($regex, $matchingCase, sprintf('Failed asserting path [%s] matches gitignore patterns [%s] using regex [%s]', $matchingCase, $patterns, $regex)); | ||
} | ||
|
||
foreach ($nonMatchingCases as $nonMatchingCase) { | ||
$this->assertNotRegExp($regex, $nonMatchingCase, sprintf('Failed asserting path [%s] not matching gitignore patterns [%s] using regex [%s]', $nonMatchingCase, $patterns, $regex)); | ||
} | ||
} | ||
|
||
/** | ||
* @return array return is array of | ||
* [ | ||
* [ | ||
* '', // Git-ignore Pattern | ||
* [], // array of file paths matching | ||
* [], // array of file paths not matching | ||
* ], | ||
* ] | ||
*/ | ||
public function provider() | ||
{ | ||
return [ | ||
[ | ||
' | ||
* | ||
!/bin/bash | ||
', | ||
['bin/cat', 'abc/bin/cat'], | ||
['bin/bash'], | ||
], | ||
[ | ||
'fi#le.txt', | ||
[], | ||
['#file.txt'], | ||
], | ||
[ | ||
' | ||
/bin/ | ||
/usr/local/ | ||
!/bin/bash | ||
!/usr/local/bin/bash | ||
', | ||
['bin/cat'], | ||
['bin/bash'], | ||
], | ||
[ | ||
'*.py[co]', | ||
['file.pyc', 'file.pyc'], | ||
['filexpyc', 'file.pycx', 'file.py'], | ||
], | ||
[ | ||
'dir1/**/dir2/', | ||
['dir1/dirA/dir2/', 'dir1/dirA/dirB/dir2/'], | ||
[], | ||
], | ||
[ | ||
'dir1/*/dir2/', | ||
['dir1/dirA/dir2/'], | ||
['dir1/dirA/dirB/dir2/'], | ||
], | ||
[ | ||
'/*.php', | ||
['file.php'], | ||
['app/file.php'], | ||
], | ||
[ | ||
'\#file.txt', | ||
['#file.txt'], | ||
[], | ||
], | ||
[ | ||
'*.php', | ||
['app/file.php', 'file.php'], | ||
['file.phps', 'file.phps', 'filephps'], | ||
], | ||
[ | ||
'app/cache/', | ||
['app/cache/file.txt', 'app/cache/dir1/dir2/file.txt', 'a/app/cache/file.txt'], | ||
[], | ||
], | ||
[ | ||
' | ||
#IamComment | ||
/app/cache/', | ||
['app/cache/file.txt', 'app/cache/subdir/ile.txt'], | ||
['a/app/cache/file.txt'], | ||
], | ||
]; | ||
} | ||
} |
Oops, something went wrong.