Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / drush / drush / src / Commands / core / RsyncCommands.php
diff --git a/vendor/drush/drush/src/Commands/core/RsyncCommands.php b/vendor/drush/drush/src/Commands/core/RsyncCommands.php
new file mode 100644 (file)
index 0000000..96bf9c4
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+namespace Drush\Commands\core;
+
+use Consolidation\AnnotatedCommand\CommandData;
+use Drush\Commands\DrushCommands;
+use Drush\Drush;
+use Drush\Exceptions\UserAbortException;
+use Drush\SiteAlias\HostPath;
+use Drush\SiteAlias\SiteAliasManagerAwareInterface;
+use Drush\SiteAlias\SiteAliasManagerAwareTrait;
+use Drush\Backend\BackendPathEvaluator;
+use Drush\Config\ConfigLocator;
+use Symfony\Component\Console\Event\ConsoleCommandEvent;
+
+class RsyncCommands extends DrushCommands implements SiteAliasManagerAwareInterface
+{
+    use SiteAliasManagerAwareTrait;
+
+    /**
+     * These are arguments after the aliases and paths have been evaluated.
+     * @see validate().
+     */
+    /** @var HostPath */
+    public $sourceEvaluatedPath;
+    /** @var HostPath */
+    public $targetEvaluatedPath;
+    /** @var BackendPathEvaluator */
+    protected $pathEvaluator;
+
+    public function __construct()
+    {
+        // TODO: once the BackendInvoke service exists, inject it here
+        // and use it to get the path evaluator
+        $this->pathEvaluator = new BackendPathEvaluator();
+    }
+
+    /**
+     * Rsync Drupal code or files to/from another server using ssh.
+     *
+     * @command core:rsync
+     * @param $source A site alias and optional path. See rsync documentation and example.site.yml.
+     * @param $target A site alias and optional path. See rsync documentation and example.site.yml.',
+     * @param $extra Additional parameters after the ssh statement.
+     * @optionset_ssh
+     * @option exclude-paths List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).
+     * @option include-paths List of paths to include, seperated by : (Unix-based systems) or ; (Windows).
+     * @option mode The unary flags to pass to rsync; --mode=rultz implies rsync -rultz.  Default is -akz.
+     * @usage drush rsync @dev @stage
+     *   Rsync Drupal root from Drush alias dev to the alias stage.
+     * @usage drush rsync ./ @stage:%files/img
+     *   Rsync all files in the current directory to the 'img' directory in the file storage folder on the Drush alias stage.
+     * @usage drush rsync @dev @stage -- --exclude=*.sql --delete
+     *   Rsync Drupal root from the Drush alias dev to the alias stage, excluding all .sql files and delete all files on the destination that are no longer on the source.
+     * @usage drush rsync @dev @stage --ssh-options="-o StrictHostKeyChecking=no" -- --delete
+     *   Customize how rsync connects with remote host via SSH. rsync options like --delete are placed after a --.
+     * @aliases rsync,core-rsync
+     * @topics docs:aliases
+     */
+    public function rsync($source, $target, array $extra, $options = ['exclude-paths' => self::REQ, 'include-paths' => self::REQ, 'mode' => 'akz'])
+    {
+        // Prompt for confirmation. This is destructive.
+        if (!\Drush\Drush::simulate()) {
+            $this->output()->writeln(dt("You will delete files in !target and replace with data from !source", ['!source' => $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash(), '!target' => $this->targetEvaluatedPath->fullyQualifiedPath()]));
+            if (!$this->io()->confirm(dt('Do you want to continue?'))) {
+                throw new UserAbortException();
+            }
+        }
+
+        $rsync_options = $this->rsyncOptions($options);
+        $parameters = array_merge([$rsync_options], $extra);
+        $parameters[] = $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash();
+        $parameters[] = $this->targetEvaluatedPath->fullyQualifiedPath();
+
+        $ssh_options = Drush::config()->get('ssh.options', '');
+        $exec = "rsync -e 'ssh $ssh_options'". ' '. implode(' ', array_filter($parameters));
+        $exec_result = drush_op_system($exec);
+
+        if ($exec_result == 0) {
+            drush_backend_set_result($this->targetEvaluatedPath->fullyQualifiedPath());
+        } else {
+            throw new \Exception(dt("Could not rsync from !source to !dest", ['!source' => $this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash(), '!dest' => $this->targetEvaluatedPath->fullyQualifiedPath()]));
+        }
+    }
+
+    public function rsyncOptions($options)
+    {
+        $verbose = $paths = '';
+        // Process --include-paths and --exclude-paths options the same way
+        foreach (['include', 'exclude'] as $include_exclude) {
+            // Get the option --include-paths or --exclude-paths and explode to an array of paths
+            // that we will translate into an --include or --exclude option to pass to rsync
+            $inc_ex_path = explode(PATH_SEPARATOR, @$options[$include_exclude . '-paths']);
+            foreach ($inc_ex_path as $one_path_to_inc_ex) {
+                if (!empty($one_path_to_inc_ex)) {
+                    $paths .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"';
+                }
+            }
+        }
+
+        $mode = '-'. $options['mode'];
+        if ($this->output()->isVerbose()) {
+            $mode .= 'v';
+            $verbose = ' --stats --progress';
+        }
+
+        return implode(' ', array_filter([$mode, $verbose, $paths]));
+    }
+
+    /**
+     * Evaluate the path aliases in the source and destination
+     * parameters. We do this in the pre-command-event so that
+     * we can set up the configuration object to include options
+     * from the source and target aliases, if any, so that these
+     * values may participate in configuration injection.
+     *
+     * @hook command-event core:rsync
+     * @param ConsoleCommandEvent $event
+     * @throws \Exception
+     * @return void
+     */
+    public function preCommandEvent(ConsoleCommandEvent $event)
+    {
+        $input = $event->getInput();
+        $this->sourceEvaluatedPath = $this->injectAliasPathParameterOptions($input, 'source');
+        $this->targetEvaluatedPath = $this->injectAliasPathParameterOptions($input, 'target');
+    }
+
+    protected function injectAliasPathParameterOptions($input, $parameterName)
+    {
+        // The Drush configuration object is a ConfigOverlay; fetch the alias
+        // context, that already has the options et. al. from the
+        // site-selection alias ('drush @site rsync ...'), @self.
+        $aliasConfigContext = $this->getConfig()->getContext(ConfigLocator::ALIAS_CONTEXT);
+        $manager = $this->siteAliasManager();
+
+        $aliasName = $input->getArgument($parameterName);
+        $evaluatedPath = HostPath::create($manager, $aliasName);
+        $this->pathEvaluator->evaluate($evaluatedPath);
+
+        $aliasRecord = $evaluatedPath->getAliasRecord();
+
+        // If the path is remote, then we will also inject the global
+        // options into the alias config context so that we pick up
+        // things like ssh-options.
+        if ($aliasRecord->isRemote()) {
+            $aliasConfigContext->combine($aliasRecord->export());
+        }
+
+        return $evaluatedPath;
+    }
+
+    /**
+     * Validate that passed aliases are valid.
+     *
+     * @hook validate core-rsync
+     * @param \Consolidation\AnnotatedCommand\CommandData $commandData
+     * @throws \Exception
+     * @return void
+     */
+    public function validate(CommandData $commandData)
+    {
+        if ($this->sourceEvaluatedPath->isRemote() && $this->targetEvaluatedPath->isRemote()) {
+            $msg = dt("Cannot specify two remote aliases. Instead, use one of the following alternate options:\n\n    `drush {source} rsync @self {target}`\n    `drush {source} rsync @self {fulltarget}\n\nUse the second form if the site alias definitions are not available at {source}.", ['source' => $source, 'target' => $target, 'fulltarget' => $this->targetEvaluatedPath->fullyQualifiedPath()]);
+            throw new \Exception($msg);
+        }
+    }
+}