[Finder] Ignore paths from .gitignore #26714
This commit is contained in:
parent
d2e9a7051f
commit
9491393dc2
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
4.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore
|
||||||
|
|
||||||
4.2.0
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ class Finder implements \IteratorAggregate, \Countable
|
|||||||
{
|
{
|
||||||
const IGNORE_VCS_FILES = 1;
|
const IGNORE_VCS_FILES = 1;
|
||||||
const IGNORE_DOT_FILES = 2;
|
const IGNORE_DOT_FILES = 2;
|
||||||
|
const IGNORE_VCS_IGNORED_FILES = 4;
|
||||||
|
|
||||||
private $mode = 0;
|
private $mode = 0;
|
||||||
private $names = [];
|
private $names = [];
|
||||||
@ -373,6 +374,24 @@ class Finder implements \IteratorAggregate, \Countable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
|
||||||
|
*
|
||||||
|
* This option is disabled by default.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
|
||||||
|
{
|
||||||
|
if ($ignoreVCSIgnored) {
|
||||||
|
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
|
||||||
|
} else {
|
||||||
|
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds VCS patterns.
|
* Adds VCS patterns.
|
||||||
*
|
*
|
||||||
@ -685,6 +704,14 @@ class Finder implements \IteratorAggregate, \Countable
|
|||||||
$notPaths[] = '#(^|/)\..+(/|$)#';
|
$notPaths[] = '#(^|/)\..+(/|$)#';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
|
||||||
|
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
|
||||||
|
if (!is_readable($gitignoreFilePath)) {
|
||||||
|
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
|
||||||
|
}
|
||||||
|
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
|
||||||
|
}
|
||||||
|
|
||||||
$minDepth = 0;
|
$minDepth = 0;
|
||||||
$maxDepth = PHP_INT_MAX;
|
$maxDepth = PHP_INT_MAX;
|
||||||
|
|
||||||
|
107
src/Symfony/Component/Finder/Gitignore.php
Normal file
107
src/Symfony/Component/Finder/Gitignore.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -347,6 +347,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
$this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
|
$this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
@ -373,6 +374,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
$finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
|
$finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
@ -399,6 +401,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
$this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
|
$this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
'test.php',
|
'test.php',
|
||||||
@ -421,6 +424,28 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
]), $finder->in(self::$tmpDir)->getIterator());
|
]), $finder->in(self::$tmpDir)->getIterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIgnoreVCSIgnored()
|
||||||
|
{
|
||||||
|
$finder = $this->buildFinder();
|
||||||
|
$this->assertSame(
|
||||||
|
$finder,
|
||||||
|
$finder
|
||||||
|
->ignoreVCS(true)
|
||||||
|
->ignoreDotFiles(true)
|
||||||
|
->ignoreVCSIgnored(true)
|
||||||
|
);
|
||||||
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'foo',
|
||||||
|
'foo/bar.tmp',
|
||||||
|
'test.py',
|
||||||
|
'toto',
|
||||||
|
'foo bar',
|
||||||
|
'qux',
|
||||||
|
'qux/baz_100_1.py',
|
||||||
|
'qux/baz_1_2.py',
|
||||||
|
]), $finder->in(self::$tmpDir)->getIterator());
|
||||||
|
}
|
||||||
|
|
||||||
public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
|
public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
|
||||||
{
|
{
|
||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
@ -428,6 +453,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder->ignoreDotFiles(false);
|
$finder->ignoreDotFiles(false);
|
||||||
|
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
'qux',
|
'qux',
|
||||||
@ -450,7 +476,9 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
]), $finder->getIterator());
|
]), $finder->getIterator());
|
||||||
|
|
||||||
$finder->ignoreVCS(false);
|
$finder->ignoreVCS(false);
|
||||||
$this->assertIterator($this->toAbsolute(['.git',
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
|
'.git',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
'qux',
|
'qux',
|
||||||
@ -479,6 +507,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
$this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
|
$this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'.bar',
|
'.bar',
|
||||||
'.foo',
|
'.foo',
|
||||||
@ -505,6 +534,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
$finder = $this->buildFinder();
|
$finder = $this->buildFinder();
|
||||||
$finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
|
$finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'.bar',
|
'.bar',
|
||||||
'.foo',
|
'.foo',
|
||||||
@ -574,6 +604,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
|
|
||||||
$finder->ignoreDotFiles(false);
|
$finder->ignoreDotFiles(false);
|
||||||
$this->assertIterator($this->toAbsolute([
|
$this->assertIterator($this->toAbsolute([
|
||||||
|
'.gitignore',
|
||||||
'foo',
|
'foo',
|
||||||
'foo/bar.tmp',
|
'foo/bar.tmp',
|
||||||
'qux',
|
'qux',
|
||||||
@ -842,6 +873,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
|||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
self::$tmpDir.\DIRECTORY_SEPARATOR.'test.php',
|
self::$tmpDir.\DIRECTORY_SEPARATOR.'test.php',
|
||||||
|
__DIR__.\DIRECTORY_SEPARATOR.'GitignoreTest.php',
|
||||||
__DIR__.\DIRECTORY_SEPARATOR.'FinderTest.php',
|
__DIR__.\DIRECTORY_SEPARATOR.'FinderTest.php',
|
||||||
__DIR__.\DIRECTORY_SEPARATOR.'GlobTest.php',
|
__DIR__.\DIRECTORY_SEPARATOR.'GlobTest.php',
|
||||||
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_0_1.php',
|
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_0_1.php',
|
||||||
|
118
src/Symfony/Component/Finder/Tests/GitignoreTest.php
Normal file
118
src/Symfony/Component/Finder/Tests/GitignoreTest.php
Normal file
@ -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'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ class DepthRangeFilterIteratorTest extends RealIteratorTestCase
|
|||||||
public function getAcceptData()
|
public function getAcceptData()
|
||||||
{
|
{
|
||||||
$lessThan1 = [
|
$lessThan1 = [
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'test.py',
|
'test.py',
|
||||||
'foo',
|
'foo',
|
||||||
@ -51,6 +52,7 @@ class DepthRangeFilterIteratorTest extends RealIteratorTestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$lessThanOrEqualTo1 = [
|
$lessThanOrEqualTo1 = [
|
||||||
|
'.gitignore',
|
||||||
'.git',
|
'.git',
|
||||||
'test.py',
|
'test.py',
|
||||||
'foo',
|
'foo',
|
||||||
|
@ -31,6 +31,7 @@ class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
|
|||||||
public function getAcceptData()
|
public function getAcceptData()
|
||||||
{
|
{
|
||||||
$foo = [
|
$foo = [
|
||||||
|
'.gitignore',
|
||||||
'.bar',
|
'.bar',
|
||||||
'.foo',
|
'.foo',
|
||||||
'.foo/.bar',
|
'.foo/.bar',
|
||||||
@ -53,6 +54,7 @@ class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$fo = [
|
$fo = [
|
||||||
|
'.gitignore',
|
||||||
'.bar',
|
'.bar',
|
||||||
'.foo',
|
'.foo',
|
||||||
'.foo/.bar',
|
'.foo/.bar',
|
||||||
@ -77,6 +79,7 @@ class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$toto = [
|
$toto = [
|
||||||
|
'.gitignore',
|
||||||
'.bar',
|
'.bar',
|
||||||
'.foo',
|
'.foo',
|
||||||
'.foo/.bar',
|
'.foo/.bar',
|
||||||
|
@ -63,6 +63,8 @@ abstract class RealIteratorTestCase extends IteratorTestCase
|
|||||||
file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800));
|
file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800));
|
||||||
file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000));
|
file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000));
|
||||||
|
|
||||||
|
file_put_contents(self::toAbsolute('.gitignore'), '*.php');
|
||||||
|
|
||||||
touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
|
touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
|
||||||
touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));
|
touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user