Gitignore.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Finder;
  11. /**
  12. * Gitignore matches against text.
  13. *
  14. * @author Michael Voříšek <vorismi3@fel.cvut.cz>
  15. * @author Ahmed Abdou <mail@ahmd.io>
  16. */
  17. class Gitignore
  18. {
  19. /**
  20. * Returns a regexp which is the equivalent of the gitignore pattern.
  21. *
  22. * Format specification: https://git-scm.com/docs/gitignore#_pattern_format
  23. */
  24. public static function toRegex(string $gitignoreFileContent): string
  25. {
  26. return self::buildRegex($gitignoreFileContent, false);
  27. }
  28. public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string
  29. {
  30. return self::buildRegex($gitignoreFileContent, true);
  31. }
  32. private static function buildRegex(string $gitignoreFileContent, bool $inverted): string
  33. {
  34. $gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
  35. $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);
  36. $res = self::lineToRegex('');
  37. foreach ($gitignoreLines as $line) {
  38. $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);
  39. if ('!' === substr($line, 0, 1)) {
  40. $line = substr($line, 1);
  41. $isNegative = true;
  42. } else {
  43. $isNegative = false;
  44. }
  45. if ('' !== $line) {
  46. if ($isNegative xor $inverted) {
  47. $res = '(?!'.self::lineToRegex($line).'$)'.$res;
  48. } else {
  49. $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
  50. }
  51. }
  52. }
  53. return '~^(?:'.$res.')~s';
  54. }
  55. private static function lineToRegex(string $gitignoreLine): string
  56. {
  57. if ('' === $gitignoreLine) {
  58. return '$f'; // always false
  59. }
  60. $slashPos = strpos($gitignoreLine, '/');
  61. if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
  62. if (0 === $slashPos) {
  63. $gitignoreLine = substr($gitignoreLine, 1);
  64. }
  65. $isAbsolute = true;
  66. } else {
  67. $isAbsolute = false;
  68. }
  69. $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
  70. $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string {
  71. return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']';
  72. }, $regex);
  73. $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
  74. $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
  75. $regex = preg_replace('~\\\\\?~', '[^/]', $regex);
  76. return ($isAbsolute ? '' : '(?:[^/]+/)*')
  77. .$regex
  78. .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
  79. }
  80. }