Skip to content

Commit

Permalink
feature #43239 [Finder] Look for gitignore patterns up to git root (j…
Browse files Browse the repository at this point in the history
…ulienfalque)

This PR was merged into the 6.1 branch.

Discussion
----------

[Finder] Look for gitignore patterns up to git root

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | yes-ish
| Deprecations? | no
| Tickets       | #43150 (comment)
| License       | MIT
| Doc PR        | -

Commits
-------

744392d [Finder] Look for gitignore patterns up to git root
  • Loading branch information
fabpot committed Apr 1, 2022
2 parents 8075e1d + 744392d commit 9fbe216
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 21 deletions.
41 changes: 31 additions & 10 deletions src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php
Expand Up @@ -40,6 +40,13 @@ public function __construct(\Iterator $iterator, string $baseDir)
{
$this->baseDir = $this->normalizePath($baseDir);

foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) {
if (@is_dir("{$parentDirectory}/.git")) {
$this->baseDir = $parentDirectory;
break;
}
}

parent::__construct($iterator);
}

Expand All @@ -64,7 +71,7 @@ private function isIgnored(string $fileRealPath): bool

$ignored = false;

foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) {
foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) {
if ($this->isIgnored($parentDirectory)) {
// rules in ignored directories are ignored, no need to check further.
break;
Expand Down Expand Up @@ -95,11 +102,11 @@ private function isIgnored(string $fileRealPath): bool
/**
* @return list<string>
*/
private function parentsDirectoryDownward(string $fileRealPath): array
private function parentDirectoriesUpwards(string $from): array
{
$parentDirectories = [];

$parentDirectory = $fileRealPath;
$parentDirectory = $from;

while (true) {
$newParentDirectory = \dirname($parentDirectory);
Expand All @@ -109,16 +116,30 @@ private function parentsDirectoryDownward(string $fileRealPath): array
break;
}

$parentDirectory = $newParentDirectory;
$parentDirectories[] = $parentDirectory = $newParentDirectory;
}

if (!str_starts_with($parentDirectory, $this->baseDir)) {
break;
}
return $parentDirectories;
}

$parentDirectories[] = $parentDirectory;
}
private function parentDirectoriesUpTo(string $from, string $upTo): array
{
return array_filter(
$this->parentDirectoriesUpwards($from),
static function (string $directory) use ($upTo): bool {
return str_starts_with($directory, $upTo);
}
);
}

return array_reverse($parentDirectories);
/**
* @return list<string>
*/
private function parentDirectoriesDownwards(string $fileRealPath): array
{
return array_reverse(
$this->parentDirectoriesUpTo($fileRealPath, $this->baseDir)
);
}

/**
Expand Down
65 changes: 58 additions & 7 deletions src/Symfony/Component/Finder/Tests/FinderTest.php
Expand Up @@ -433,18 +433,60 @@ public function testIgnoreVCSIgnored()
->ignoreVCSIgnored(true)
);

copy(__DIR__.'/Fixtures/gitignore/search_root/b.txt', __DIR__.'/Fixtures/gitignore/search_root/a.txt');
copy(__DIR__.'/Fixtures/gitignore/search_root/b.txt', __DIR__.'/Fixtures/gitignore/search_root/c.txt');
copy(__DIR__.'/Fixtures/gitignore/search_root/dir/a.txt', __DIR__.'/Fixtures/gitignore/search_root/dir/b.txt');
copy(__DIR__.'/Fixtures/gitignore/search_root/dir/a.txt', __DIR__.'/Fixtures/gitignore/search_root/dir/c.txt');
$this->assertIterator(self::toAbsolute([
'gitignore/search_root/b.txt',
'gitignore/search_root/dir',
'gitignore/search_root/dir/a.txt',
]), $finder->in(self::toAbsolute('gitignore/search_root'))->getIterator());
}

public function testIgnoreVCSIgnoredUpToFirstGitRepositoryRoot()
{
$finder = $this->buildFinder();
$this->assertSame(
$finder,
$finder
->ignoreVCS(true)
->ignoreDotFiles(true)
->ignoreVCSIgnored(true)
);

$this->assertIterator(self::toAbsolute([
'gitignore/git_root/search_root/b.txt',
'gitignore/git_root/search_root/c.txt',
'gitignore/git_root/search_root/dir',
'gitignore/git_root/search_root/dir/a.txt',
'gitignore/git_root/search_root/dir/c.txt',
]), $finder->in(self::toAbsolute('gitignore/git_root/search_root'))->getIterator());
}

/**
* @runInSeparateProcess
*/
public function testIgnoreVCSIgnoredWithOpenBasedir()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}

$finder = $this->buildFinder();
$this->assertSame(
$finder,
$finder
->ignoreVCS(true)
->ignoreDotFiles(true)
->ignoreVCSIgnored(true)
);

$this->assertIterator($this->toAbsoluteFixtures([
$this->iniSet('open_basedir', \dirname(__DIR__, 5).\PATH_SEPARATOR.self::toAbsolute('gitignore/search_root'));

$this->assertIterator(self::toAbsolute([
'gitignore/search_root/b.txt',
'gitignore/search_root/c.txt',
'gitignore/search_root/dir',
'gitignore/search_root/dir/a.txt',
'gitignore/search_root/dir/c.txt',
]), $finder->in(__DIR__.'/Fixtures/gitignore/search_root')->getIterator());
]), $finder->in(self::toAbsolute('gitignore/search_root'))->getIterator());
}

public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
Expand Down Expand Up @@ -1462,6 +1504,15 @@ public function testIgnoredAccessDeniedException()

protected function buildFinder()
{
return Finder::create();
return Finder::create()->exclude('gitignore');
}

protected function iniSet(string $varName, string $newValue): void
{
if ('open_basedir' === $varName && $deprecationsFile = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) {
$newValue .= \PATH_SEPARATOR.$deprecationsFile;
}

parent::iniSet('open_basedir', $newValue);
}
}
@@ -0,0 +1 @@
/a.txt
Empty file.
@@ -0,0 +1 @@
/b.txt
Empty file.
Expand Up @@ -11,6 +11,9 @@

namespace Symfony\Component\Finder\Tests\Iterator;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Tests\FinderTest;

abstract class RealIteratorTestCase extends IteratorTestCase
{
protected static $tmpDir;
Expand Down Expand Up @@ -44,6 +47,10 @@ public static function setUpBeforeClass(): void
'qux/baz_100_1.py',
];

if (FinderTest::class === static::class) {
self::$files[] = 'gitignore/';
}

self::$files = self::toAbsolute(self::$files);

if (is_dir(self::$tmpDir)) {
Expand All @@ -65,14 +72,39 @@ public static function setUpBeforeClass(): void

touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));

if (FinderTest::class === static::class) {
$fs = new Filesystem();
$fs->mirror(__DIR__.'/../Fixtures/gitignore', self::toAbsolute('gitignore'));

foreach ([
'gitignore/search_root/a.txt',
'gitignore/search_root/c.txt',
'gitignore/search_root/dir/b.txt',
'gitignore/search_root/dir/c.txt',
'gitignore/git_root/search_root/a.txt',
'gitignore/git_root/search_root/c.txt',
'gitignore/git_root/search_root/dir/b.txt',
'gitignore/git_root/search_root/dir/c.txt',
] as $file) {
$fs->touch(self::toAbsolute($file));
}

$fs->mkdir(self::toAbsolute('gitignore/git_root/.git'));
}
}

public static function tearDownAfterClass(): void
{
$paths = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(self::$tmpDir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
try {
$paths = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(self::$tmpDir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
} catch (\UnexpectedValueException $exception) {
// open_basedir restriction in effect
return;
}

foreach ($paths as $path) {
if ($path->isDir()) {
Expand Down
3 changes: 3 additions & 0 deletions src/Symfony/Component/Finder/composer.json
Expand Up @@ -18,6 +18,9 @@
"require": {
"php": ">=8.1"
},
"require-dev": {
"symfony/filesystem": "^6.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Finder\\": "" },
"exclude-from-classmap": [
Expand Down

0 comments on commit 9fbe216

Please sign in to comment.