Gitignore.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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. * @param string $gitignoreFileContent
  24. */
  25. public static function toRegex($gitignoreFileContent)
  26. {
  27. return self::buildRegex($gitignoreFileContent, false);
  28. }
  29. /**
  30. * @param string $gitignoreFileContent
  31. */
  32. public static function toRegexMatchingNegatedPatterns($gitignoreFileContent)
  33. {
  34. return self::buildRegex($gitignoreFileContent, true);
  35. }
  36. /**
  37. * @param string $gitignoreFileContent
  38. * @param bool $inverted
  39. */
  40. private static function buildRegex($gitignoreFileContent, $inverted)
  41. {
  42. $gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
  43. $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);
  44. $res = self::lineToRegex('');
  45. foreach ($gitignoreLines as $line) {
  46. $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);
  47. if (strncmp($line, '!', strlen('!')) === 0) {
  48. $line = substr($line, 1);
  49. $isNegative = true;
  50. } else {
  51. $isNegative = false;
  52. }
  53. if ('' !== $line) {
  54. if ($isNegative xor $inverted) {
  55. $res = '(?!'.self::lineToRegex($line).'$)'.$res;
  56. } else {
  57. $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
  58. }
  59. }
  60. }
  61. return '~^(?:'.$res.')~s';
  62. }
  63. /**
  64. * @param string $gitignoreLine
  65. */
  66. private static function lineToRegex($gitignoreLine)
  67. {
  68. if ('' === $gitignoreLine) {
  69. return '$f'; // always false
  70. }
  71. $slashPos = strpos($gitignoreLine, '/');
  72. if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
  73. if (0 === $slashPos) {
  74. $gitignoreLine = substr($gitignoreLine, 1);
  75. }
  76. $isAbsolute = true;
  77. } else {
  78. $isAbsolute = false;
  79. }
  80. $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
  81. $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches) : string {
  82. return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']';
  83. }, $regex);
  84. $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
  85. $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
  86. $regex = preg_replace('~\\\\\?~', '[^/]', $regex);
  87. return ($isAbsolute ? '' : '(?:[^/]+/)*')
  88. .$regex
  89. .(substr_compare($gitignoreLine, '/', -strlen('/')) !== 0 ? '(?:$|/)' : '');
  90. }
  91. }