8000 feature #43150 [Finder] Add recursive .gitignore files support (julie… · symfony/symfony@c6a0be2 · GitHub
[go: up one dir, main page]

Skip to content

Commit c6a0be2

Browse files
committed
feature #43150 [Finder] Add recursive .gitignore files support (julienfalque)
This PR was merged into the 5.4 branch. Discussion ---------- [Finder] Add recursive .gitignore files support | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | TODO This PR extends `Finder`'s `.gitignore` support to nested files. Note: as a side effect, a `.gitignore` file is not required at the top level anymore. Actually, if no `.gitignore` file is found at all, no errors will be triggered. Commits ------- 5152336 [Finder] Add recursive .gitignore files support
2 parents f56b471 + 5152336 commit c6a0be2

14 files changed

+359
-46
lines changed

src/Symfony/Component/Finder/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Deprecate `Comparator::setTarget()` and `Comparator::setOperator()`
88
* Add a constructor to `Comparator` that allows setting target and operator
99
* Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified
10+
* Add recursive .gitignore files support
1011

1112
5.0.0
1213
-----

src/Symfony/Component/Finder/Finder.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -709,14 +709,6 @@ private function searchInDirectory(string $dir): \Iterator
709709
$notPaths[] = '#(^|/)\..+(/|$)#';
710710
}
711711

712-
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
713-
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
714-
if (!is_readable($gitignoreFilePath)) {
715-
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
716-
}
717-
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
718-
}
719-
720712
$minDepth = 0;
721713
$maxDepth = \PHP_INT_MAX;
722714

@@ -785,6 +777,10 @@ private function searchInDirectory(string $dir): \Iterator
785777
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
786778
}
787779

780+
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
781+
$iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
782+
}
783+
788784
return $iterator;
789785
}
790786

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Finder\Iterator;
13+
14+
use Symfony\Component\Finder\Gitignore;
15+
16+
final class VcsIgnoredFilterIterator extends \FilterIterator
17+
{
18+
/**
19+
* @var string
20+
*/
21+
private $baseDir;
22+
23+
/**
24+
* @var array<string, string|null>
25+
*/
26+
private $gitignoreFilesCache = [];
27+
28+
public function __construct(\Iterator $iterator, string $baseDir)
29+
{
30+
$this->baseDir = $baseDir;
31+
32+
parent::__construct($iterator);
33+
}
34+
35+
public function accept(): bool
36+
{
37+
$file = $this->current();
38+
39+
$fileRealPath = $file->getRealPath();
40+
if ($file->isDir()) {
41+
$fileRealPath .= '/';
42+
}
43+
44+
$parentDirectory = $fileRealPath;
45+
46+
do {
47+
$parentDirectory = \dirname($parentDirectory);
48+
$relativeFilePath = substr($fileRealPath, \strlen($parentDirectory) + 1);
49+
50+
$regex = $this->readGitignoreFile("{$parentDirectory}/.gitignore");
51+
52+
if (null !== $regex && preg_match($regex, $relativeFilePath)) {
53+
return false;
54+
}
55+
} while ($parentDirectory !== $this->baseDir);
56+
57+
return true;
58+
}
59+
60+
private function readGitignoreFile(string $path): ?string
61+
{
62+
if (\array_key_exists($path, $this->gitignoreFilesCache)) {
63+
return $this->gitignoreFilesCache[$path];
64+
}
65+
66+
if (!file_exists($path)) {
67+
return null;
68+
}
69+
70+
if (!is_file($path) || !is_readable($path)) {
71+
throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable.");
72+
}
73+
74+
return $this->gitignoreFilesCache[$path] = Gitignore::toRegex(file_get_contents($path));
75+
}
76+
}

src/Symfony/Component/Finder/Tests/FinderTest.php

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ public function testIgnoreVCS()
348348
$finder = $this->buildFinder();
349349
$this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
350350
$this->assertIterator($this->toAbsolute([
351-
'.gitignore',
352351
'.git',
353352
'foo',
354353
'foo/bar.tmp',
@@ -375,7 +374,6 @@ public function testIgnoreVCS()
375374
$finder = $this->buildFinder();
376375
$finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
377376
$this->assertIterator($this->toAbsolute([
378-
'.gitignore',
379377
'.git',
380378
'foo',
381379
'foo/bar.tmp',
@@ -402,7 +400,6 @@ public function testIgnoreVCS()
402400
$finder = $this->buildFinder();
403401
$this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
404402
$this->assertIterator($this->toAbsolute([
405-
'.gitignore',
406403
'foo',
407404
'foo/bar.tmp',
408405
'test.php',
@@ -435,16 +432,15 @@ public function testIgnoreVCSIgnored()
435432
->ignoreDotFiles(true)
436433
->ignoreVCSIgnored(true)
437434
);
438-
$this->assertIterator($this->toAbsolute([
439-
'foo',
440-
'foo/bar.tmp',
441-
'test.py',
442-
'toto',
443-
'foo bar',
444-
'qux',
445-
'qux/baz_100_1.py',
446-
'qux/baz_1_2.py',
447-
]), $finder->in(self::$tmpDir)->getIterator());
435+
436+
copy(__DIR__.'/Fixtures/gitignore/b.txt', __DIR__.'/Fixtures/gitignore/a.txt');
437+
copy(__DIR__.'/Fixtures/gitignore/dir/a.txt', __DIR__.'/Fixtures/gitignore/dir/b.txt');
438+
439+
$this->assertIterator($this->toAbsoluteFixtures([
440+
'gitignore/b.txt',
441+
'gitignore/dir',
442+
'gitignore/dir/a.txt',
443+
]), $finder->in(__DIR__.'/Fixtures/gitignore')->getIterator());
448444
}
449445

450446
public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
@@ -454,7 +450,6 @@ public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
454450
$finder->ignoreDotFiles(false);
455451

456452
$this->assertIterator($this->toAbsolute([
457-
'.gitignore',
458453
'foo',
459454
'foo/bar.tmp',
460455
'qux',
@@ -478,7 +473,6 @@ public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
478473

479474
$finder->ignoreVCS(false);
480475
$this->assertIterator($this->toAbsolute([
481-
'.gitignore',
482476
'.git',
483477
'foo',
484478
'foo/bar.tmp',
@@ -508,7 +502,6 @@ public function testIgnoreDotFiles()
508502
$finder = $this->buildFinder();
509503
$this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
510504
$this->assertIterator($this->toAbsolute([
511-
'.gitignore',
512505
'.git',
513506
'.bar',
514507
'.foo',
@@ -535,7 +528,6 @@ public function testIgnoreDotFiles()
535528
$finder = $this->buildFinder();
536529
$finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
537530
$this->assertIterator($this->toAbsolute([
538-
'.gitignore',
539531
'.git',
540532
'.bar',
541533
'.foo',
@@ -605,7 +597,6 @@ public function testIgnoreDotFilesCanBeDisabledAfterFirstIteration()
605597

606598
$finder->ignoreDotFiles(false);
607599
$this->assertIterator($this->toAbsolute([
608-
'.gitignore',
609600
'foo',
610601
'foo/bar.tmp',
611602
'qux',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/a.txt

src/Symfony/Component/Finder/Tests/Fixtures/gitignore/b.txt

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/b.txt

src/Symfony/Component/Finder/Tests/Fixtures/gitignore/dir/a.txt

Whitespace-only changes.

src/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public function testAccept($minDepth, $maxDepth, $expected)
3333
public function getAcceptData()
3434
{
3535
$lessThan1 = [
36-
'.gitignore',
3736
'.git',
3837
'test.py',
3938
'foo',
@@ -52,7 +51,6 @@ public function getAcceptData()
5251
];
5352

5453
$lessThanOrEqualTo1 = [
55-
'.gitignore',
5654
'.git',
5755
'test.py',
5856
'foo',

src/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public function testAccept($directories, $expected)
3131
public function getAcceptData()
3232
{
3333
$foo = [
34-
'.gitignore',
3534
'.bar',
3635
'.foo',
3736
'.foo/.bar',
@@ -54,7 +53,6 @@ public function getAcceptData()
5453
];
5554

5655
$fo = [
57-
'.gitignore',
5856
'.bar',
5957
'.foo',
6058
'.foo/.bar',
@@ -79,7 +77,6 @@ public function getAcceptData()
7977
];
8078

8179
$toto = [
82-
'.gitignore',
8380
'.bar',
8481
'.foo',
8582
'.foo/.bar',

src/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,3 @@ public function getAcceptData()
3939
];
4040
}
4141
}
42-
43-
class InnerNameIterator extends \ArrayIterator
44-
{
45-
public function current(): \SplFileInfo
46-
{
47-
return new \SplFileInfo(parent::current());
48-
}
49-
50-
public function getFilename()
51-
{
52-
return parent::current();
53-
}
54-
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Finder\Tests\Iterator;
13+
14+
class InnerNameIterator extends \ArrayIterator
15+
{
16+
public function current(): \SplFileInfo
17+
{
18+
return new \SplFileInfo(parent::current());
19+
}
20+
21+
public function getFilename()
22+
{
23+
return parent::current();
24+
}
25+
}

src/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ public static function setUpBeforeClass(): void
6363
file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800));
6464
file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000));
6565

66-
file_put_contents(self::toAbsolute('.gitignore'), '*.php');
67-
6866
touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
6967
touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));
7068
}

0 commit comments

Comments
 (0)
0