Gitignore.php 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  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. $gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
  27. $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);
  28. $res = self::lineToRegex('');
  29. foreach ($gitignoreLines as $i => $line) {
  30. $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);
  31. if ('!' === substr($line, 0, 1)) {
  32. $line = substr($line, 1);
  33. $isNegative = true;
  34. } else {
  35. $isNegative = false;
  36. }
  37. if ('' !== $line) {
  38. if ($isNegative) {
  39. $res = '(?!'.self::lineToRegex($line).'$)'.$res;
  40. } else {
  41. $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
  42. }
  43. }
  44. }
  45. return '~^(?:'.$res.')~s';
  46. }
  47. private static function lineToRegex(string $gitignoreLine): string
  48. {
  49. if ('' === $gitignoreLine) {
  50. return '$f'; // always false
  51. }
  52. $slashPos = strpos($gitignoreLine, '/');
  53. if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
  54. if (0 === $slashPos) {
  55. $gitignoreLine = substr($gitignoreLine, 1);
  56. }
  57. $isAbsolute = true;
  58. } else {
  59. $isAbsolute = false;
  60. }
  61. $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
  62. $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string {
  63. return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']';
  64. }, $regex);
  65. $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
  66. $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
  67. $regex = preg_replace('~\\\\\?~', '[^/]', $regex);
  68. return ($isAbsolute ? '' : '(?:[^/]+/)*')
  69. .$regex
  70. .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
  71. }
  72. }