Browse Source

Merge pull request #4962 from curry684/issue-4955

Improve handling of removing junctions in Windows path repositories
Jordi Boggiano 9 years ago
parent
commit
3753ccf25d
2 changed files with 31 additions and 3 deletions
  1. 21 0
      src/Composer/Downloader/PathDownloader.php
  2. 10 3
      src/Composer/Util/Filesystem.php

+ 21 - 0
src/Composer/Downloader/PathDownloader.php

@@ -101,4 +101,25 @@ class PathDownloader extends FileDownloader
 
         $this->io->writeError('');
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function remove(PackageInterface $package, $path)
+    {
+        /**
+         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
+         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which
+         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.
+         */
+        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {
+            $this->io->writeError("  - Removing junction for <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
+            if (!$this->filesystem->removeJunction($path)) {
+                $this->io->writeError("<warn>Could not remove junction at " . $path . " - is another process locking it?</warn>");
+                throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName());
+            }
+        } else {
+            parent::remove($package, $path);
+        }
+    }
 }

+ 10 - 3
src/Composer/Util/Filesystem.php

@@ -618,10 +618,17 @@ class Filesystem
         if (!is_dir($junction) || is_link($junction)) {
             return false;
         }
-        // Junctions have no link stat but are otherwise indistinguishable from real directories
+        /**
+         * According to MSDN at https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx we can detect a junction now
+         * using the 'mode' value from stat: "The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit
+         * is set if path specifies an ordinary file or a device." We have just tested for a directory above, so if
+         * we have a directory that isn't one according to lstat(...) we must have a junction.
+         *
+         * #define	_S_IFDIR	0x4000
+         * #define	_S_IFREG	0x8000
+         */
         $stat = lstat($junction);
-
-        return ($stat['mode'] === 0);
+        return !($stat['mode'] & 0xC000);
     }
 
     /**