@@ -27,42 +27,56 @@ public static function toRegex(string $gitignoreFileContent): string
27
27
{
28
28
$ gitignoreFileContent = preg_replace ('/^[^ \\\r\n]*#.*/m ' , '' , $ gitignoreFileContent );
29
29
$ gitignoreLines = preg_split ('/\r\n|\r|\n/ ' , $ gitignoreFileContent );
30
- $ gitignoreLines = array_map ('trim ' , $ gitignoreLines );
31
- $ gitignoreLines = array_filter ($ gitignoreLines );
32
30
33
- $ ignoreLinesPositive = array_filter ( $ gitignoreLines , function ( string $ line ) {
34
- return ! preg_match ( ' /^!/ ' , $ line ) ;
35
- });
36
-
37
- $ ignoreLinesNegative = array_filter ( $ gitignoreLines , function ( string $ line ) {
38
- return preg_match ( ' /^!/ ' , $ line ) ;
39
- });
31
+ $ positives = [];
32
+ $ negatives = [] ;
33
+ foreach ( $ gitignoreLines as $ i => $ line ) {
34
+ $ line = trim ( $ line );
35
+ if ( '' === $ line ) {
36
+ continue ;
37
+ }
40
38
41
- $ ignoreLinesNegative = array_map (function (string $ line ) {
42
- return preg_replace ('/^!(.*)/ ' , '${1} ' , $ line );
43
- }, $ ignoreLinesNegative );
44
- $ ignoreLinesNegative = array_map ([__CLASS__ , 'getRegexFromGitignore ' ], $ ignoreLinesNegative );
39
+ if (1 === preg_match ('/^!/ ' , $ line )) {
40
+ $ positives [$ i ] = null ;
41
+ $ negatives [$ i ] = self ::getRegexFromGitignore (preg_replace ('/^!(.*)/ ' , '${1} ' , $ line ), true );
45
42
46
- $ ignoreLinesPositive = array_map ([__CLASS__ , 'getRegexFromGitignore ' ], $ ignoreLinesPositive );
47
- if (empty ($ ignoreLinesPositive )) {
48
- return '/^$/ ' ;
43
+ continue ;
44
+ }
45
+ $ negatives [$ i ] = null ;
46
+ $ positives [$ i ] = self ::getRegexFromGitignore ($ line );
49
47
}
50
48
51
- if (empty ($ ignoreLinesNegative )) {
52
- return sprintf ('/%s/ ' , implode ('| ' , $ ignoreLinesPositive ));
49
+ $ index = 0 ;
50
+ $ patterns = [];
51
+ foreach ($ positives as $ pattern ) {
52
+ if (null === $ pattern ) {
53
+ continue ;
54
+ }
55
+
56
+ $ negativesAfter = array_filter (\array_slice ($ negatives , ++$ index ));
57
+ if ($ negativesAfter !== []) {
58
+ $ pattern .= sprintf ('(?<!%s) ' , implode ('| ' , $ negativesAfter ));
59
+ }
60
+
61
+ $ patterns [] = $ pattern ;
53
62
}
54
63
55
- return sprintf ('/(?=^(?:(?!( %s)).)*$)(%s) / ' , implode ('| ' , $ ignoreLinesNegative ), implode ( ' | ' , $ ignoreLinesPositive ));
64
+ return sprintf ('/^(( %s))$ / ' , implode (')|( ' , $ patterns ));
56
65
}
57
66
58
- private static function getRegexFromGitignore (string $ gitignorePattern ): string
67
+ private static function getRegexFromGitignore (string $ gitignorePattern, bool $ negative = false ): string
59
68
{
60
- $ regex = '( ' ;
61
- if (0 === strpos ($ gitignorePattern , '/ ' )) {
62
- $ gitignorePattern = substr ($ gitignorePattern , 1 );
69
+ $ regex = '' ;
70
+ $ isRelativePath = false ;
71
+ // 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
72
+ $ slashPosition = strpos ($ gitignorePattern , '/ ' );
73
+ if (false !== $ slashPosition && \strlen ($ gitignorePattern ) - 1 !== $ slashPosition ) {
74
+ if (0 === $ slashPosition ) {
75
+ $ gitignorePattern = substr ($ gitignorePattern , 1 );
76
+ }
77
+
78
+ $ isRelativePath = true ;
63
79
$ regex .= '^ ' ;
64
- } else {
65
- $ regex .= '(^|\/) ' ;
66
80
}
67
81
68
82
if ('/ ' === $ gitignorePattern [\strlen ($ gitignorePattern ) - 1 ]) {
@@ -71,17 +85,29 @@ private static function getRegexFromGitignore(string $gitignorePattern): string
71
85
72
86
$ iMax = \strlen ($ gitignorePattern );
73
87
for ($ i = 0 ; $ i < $ iMax ; ++$ i ) {
88
+ $ tripleChars = substr ($ gitignorePattern , $ i , 3 );
89
+ if ('**/ ' === $ tripleChars || '/** ' === $ tripleChars ) {
90
+ $ regex .= '.* ' ;
91
+ $ i += 2 ;
92
+ continue ;
93
+ }
94
+
74
95
$ doubleChars = substr ($ gitignorePattern , $ i , 2 );
75
96
if ('** ' === $ doubleChars ) {
76
- $ regex .= '.+ ' ;
97
+ $ regex .= '.* ' ;
98
+ ++$ i ;
99
+ continue ;
100
+ }
101
+ if ('*/ ' === $ doubleChars ) {
102
+ $ regex .= '[^\/]*\/?[^\/]* ' ;
77
103
++$ i ;
78
104
continue ;
79
105
}
80
106
81
107
$ c = $ gitignorePattern [$ i ];
82
108
switch ($ c ) {
83
109
case '* ' :
84
- $ regex .= '[^\/]+ ' ;
110
+ $ regex .= $ isRelativePath ? '[^\/]* ' : ' [^\/]*\/?[^\/]* ' ;
85
111
break ;
86
112
case '/ ' :
87
113
case '. ' :
@@ -97,9 +123,11 @@ private static function getRegexFromGitignore(string $gitignorePattern): string
97
123
}
98
124
}
99
125
100
- $ regex .= '($|\/) ' ;
101
- $ regex .= ') ' ;
126
+ if ($ negative ) {
127
+ // a lookbehind assertion has to be a fixed width (it can not have nested '|' statements)
128
+ return sprintf ('%s$|%s\/$ ' , $ regex , $ regex );
129
+ }
102
130
103
- return $ regex ;
131
+ return ' (?> ' . $ regex. ' ($|\/.*)) ' ;
104
132
}
105
133
}
<
10000
/td>
0 commit comments