Sfoglia il codice sorgente

Merge pull request #1341 from beberlei/GH-1339

Fallback to PHP early if proc_open not allowed.
Jordi Boggiano 13 anni fa
parent
commit
6f56568881

+ 1 - 1
src/Composer/Package/Locker.php

@@ -288,7 +288,7 @@ class Locker
             unset($spec['version_normalized']);
 
             if ($package->isDev()) {
-                if ('git' === $package->getSourceType() && $path = $this->installationManager->getInstallPath($package)) {
+                if ('git' === $package->getSourceType() && $path = $this->installationManager->getInstallPath($package) && function_exists('proc_open')) {
                     $sourceRef = $package->getSourceReference() ?: $package->getDistReference();
                     $process = new ProcessExecutor();
                     if (0 === $process->execute('git log -n1 --pretty=%ct '.escapeshellarg($sourceRef), $output, $path)) {

+ 76 - 0
src/Composer/Util/Filesystem.php

@@ -12,6 +12,9 @@
 
 namespace Composer\Util;
 
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
@@ -38,12 +41,25 @@ class Filesystem
         return false;
     }
 
+    /**
+     * Recursively remove a directory
+     *
+     * Uses the process component if proc_open is enabled on the PHP
+     * installation.
+     *
+     * @param string $directory
+     * @return bool
+     */
     public function removeDirectory($directory)
     {
         if (!is_dir($directory)) {
             return true;
         }
 
+        if (!function_exists('proc_open')) {
+            return $this->removeDirectoryPhp($directory);
+        }
+
         if (defined('PHP_WINDOWS_VERSION_BUILD')) {
             $cmd = sprintf('rmdir /S /Q %s', escapeshellarg(realpath($directory)));
         } else {
@@ -58,6 +74,32 @@ class Filesystem
         return $result && !is_dir($directory);
     }
 
+    /**
+     * Recursively delete directory using PHP iterators.
+     *
+     * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
+     * before directories, creating a single non-recursive loop
+     * to delete files/directories in the correct order.
+     *
+     * @param string $directory
+     * @return bool
+     */
+    public function removeDirectoryPhp($directory)
+    {
+        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
+        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
+
+        foreach ($ri as $file) {
+            if ($file->isDir()) {
+                rmdir($file->getPathname());
+            } else {
+                unlink($file->getPathname());
+            }
+        }
+
+        return rmdir($directory);
+    }
+
     public function ensureDirectoryExists($directory)
     {
         if (!is_dir($directory)) {
@@ -74,12 +116,46 @@ class Filesystem
         }
     }
 
+    /**
+     * Copy then delete is a non-atomic version of {@link rename}.
+     *
+     * Some systems can't rename and also dont have proc_open,
+     * which requires this solution.
+     *
+     * @param string $source
+     * @param string $target
+     */
+    public function copyThenRemove($source, $target)
+    {
+        $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
+        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
+
+        if ( !file_exists($target)) {
+            mkdir($target, 0777, true);
+        }
+
+        foreach ($ri as $file) {
+            $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
+            if ($file->isDir()) {
+                mkdir($targetPath);
+            } else {
+                copy($file->getPathname(), $targetPath);
+            }
+        }
+
+        $this->removeDirectoryPhp($source);
+    }
+
     public function rename($source, $target)
     {
         if (true === @rename($source, $target)) {
             return;
         }
 
+        if (!function_exists('proc_open')) {
+            return $this->copyThenRemove($source, $target);
+        }
+
         if (defined('PHP_WINDOWS_VERSION_BUILD')) {
             // Try to copy & delete - this is a workaround for random "Access denied" errors.
             $command = sprintf('xcopy %s %s /E /I /Q', escapeshellarg($source), escapeshellarg($target));

+ 15 - 0
tests/Composer/Test/Util/FilesystemTest.php

@@ -93,4 +93,19 @@ class FilesystemTest extends TestCase
             array('C:/Temp', 'c:\Temp\test', "test"),
         );
     }
+
+    /**
+     * @group GH-1339
+     */
+    public function testRemoveDirectoryPhp()
+    {
+        $tmp = sys_get_temp_dir();
+        @mkdir($tmp . "/composer_testdir/level1/level2", 0777, true);
+        file_put_contents($tmp . "/composer_testdir/level1/level2/hello.txt", "hello world");
+
+        $fs = new Filesystem;
+        $this->assertTrue($fs->removeDirectoryPhp($tmp . "/composer_testdir"));
+        $this->assertFalse(file_exists($tmp . "/composer_testdir/level1/level2/hello.txt"));
+    }
 }
+