Hello community,

here is the log from the commit of package platformsh-cli for openSUSE:Factory 
checked in at 2017-10-19 19:33:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/platformsh-cli (Old)
 and      /work/SRC/openSUSE:Factory/.platformsh-cli.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "platformsh-cli"

Thu Oct 19 19:33:02 2017 rev:21 rq:534961 version:3.21.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/platformsh-cli/platformsh-cli.changes    
2017-10-17 01:52:13.711248171 +0200
+++ /work/SRC/openSUSE:Factory/.platformsh-cli.new/platformsh-cli.changes       
2017-10-19 19:33:04.702233062 +0200
@@ -1,0 +2,20 @@
+Wed Oct 18 13:52:28 UTC 2017 - [email protected]
+
+- Update to version 3.21.0:
+  * Fix installer bug writing multiple times to config
+  * Fix installer bug writing multiple times to config
+  * Use full namespace name in list command
+  * add mount:list (lists) command
+  * add mount:pull (mpull) and mount:push (mpush) commands
+  * mount commands: use selected environment
+  * mount commands: various improvements
+  * Add a confirmation question and another interactive default for --target 
and --source
+  * Rename mount commands
+  * mount commands: restore --refresh option, add --delete option
+  * List mounts as a table
+  * Support logging in with a username (#636)
+  * Fix: service dependencies of Filesystem did not get output class, 
initialized too early
+  * Switch default output to ConsoleOutput
+  * Release v3.21.0
+
+-------------------------------------------------------------------

Old:
----
  platformsh-cli-3.20.5.tar.xz

New:
----
  platformsh-cli-3.21.0.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ platformsh-cli.spec ++++++
--- /var/tmp/diff_new_pack.4PVYHL/_old  2017-10-19 19:33:05.378201435 +0200
+++ /var/tmp/diff_new_pack.4PVYHL/_new  2017-10-19 19:33:05.378201435 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           platformsh-cli
-Version:        3.20.5
+Version:        3.21.0
 Release:        0
 Summary:        Tool for managing Platform.sh services from the command line
 # See licenses.txt for dependency licenses.

++++++ _service ++++++
--- /var/tmp/diff_new_pack.4PVYHL/_old  2017-10-19 19:33:05.406200125 +0200
+++ /var/tmp/diff_new_pack.4PVYHL/_new  2017-10-19 19:33:05.406200125 +0200
@@ -2,7 +2,7 @@
   <service name="tar_scm" mode="disabled">
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
-    <param name="revision">refs/tags/v3.20.5</param>
+    <param name="revision">refs/tags/v3.21.0</param>
     <param name="url">git://github.com/platformsh/platformsh-cli.git</param>
     <param name="scm">git</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.4PVYHL/_old  2017-10-19 19:33:05.422199376 +0200
+++ /var/tmp/diff_new_pack.4PVYHL/_new  2017-10-19 19:33:05.422199376 +0200
@@ -1,6 +1,6 @@
 <servicedata>
   <service name="tar_scm">
     <param name="url">git://github.com/platformsh/platformsh-cli.git</param>
-    <param 
name="changesrevision">f8e216ec75f1a2f6fdf3e4765c60b624fda06ea3</param>
+    <param 
name="changesrevision">0bbbd4c416e3474f27488107cb8a49b6ddeb9100</param>
   </service>
 </servicedata>

++++++ platformsh-cli-3.20.5.tar.xz -> platformsh-cli-3.21.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/README.md 
new/platformsh-cli-3.21.0/README.md
--- old/platformsh-cli-3.20.5/README.md 2017-10-13 10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/README.md 2017-10-18 10:41:52.000000000 +0200
@@ -126,6 +126,10 @@
   local:build (build)                       Build the current project locally
   local:dir (dir)                           Find the local project root
   local:drush-aliases (drush-aliases)       Find the project's Drush aliases
+mount
+  mount:download                            Download files from a mount, using 
rsync
+  mount:list (mounts)                       Get a list of mounts
+  mount:upload                              Upload files to a mount, using 
rsync
 project
   project:delete                            Delete a project
   project:get (get)                         Clone a project locally
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/config.yaml 
new/platformsh-cli-3.21.0/config.yaml
--- old/platformsh-cli-3.20.5/config.yaml       2017-10-13 10:57:00.000000000 
+0200
+++ new/platformsh-cli-3.21.0/config.yaml       2017-10-18 10:41:52.000000000 
+0200
@@ -1,7 +1,7 @@
 # Metadata about the CLI application itself.
 application:
   name: 'Platform.sh CLI'
-  version: '3.20.5'
+  version: '3.21.0'
   executable: 'platform'
   package_name: 'platformsh/cli'
   installer_url: 'https://platform.sh/cli/installer'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/dist/installer.php 
new/platformsh-cli-3.21.0/dist/installer.php
--- old/platformsh-cli-3.20.5/dist/installer.php        2017-10-13 
10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/dist/installer.php        2017-10-18 
10:41:52.000000000 +0200
@@ -293,7 +293,7 @@
         if (!$currentShellConfig = file_get_contents($shellConfigFile)) {
             return false;
         }
-        if (strpos($key, $currentShellConfig) !== false) {
+        if (strpos($currentShellConfig, $key) !== false) {
             return true;
         }
         $newShellConfig = rtrim($currentShellConfig, PHP_EOL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/services.yaml 
new/platformsh-cli-3.21.0/services.yaml
--- old/platformsh-cli-3.20.5/services.yaml     2017-10-13 10:57:00.000000000 
+0200
+++ new/platformsh-cli-3.21.0/services.yaml     2017-10-18 10:41:52.000000000 
+0200
@@ -13,9 +13,6 @@
         class:     '\Platformsh\Cli\Service\CacheFactory'
     config:
         class:     '\Platformsh\Cli\Service\Config'
-        calls:
-          - method: setFs
-            arguments: ['@fs']
     drush:
         class:     '\Platformsh\Cli\Service\Drush'
         arguments: ['@config', '@shell']
@@ -35,7 +32,7 @@
         class:     '\Platformsh\Cli\Local\LocalProject'
         arguments: ['@config', '@git']
     output:
-        class:     '\Symfony\Component\Console\Output\NullOutput'
+        class:     '\Symfony\Component\Console\Output\ConsoleOutput'
     property_formatter:
         class:     '\Platformsh\Cli\Service\PropertyFormatter'
         arguments: ['@input']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/src/Application.php 
new/platformsh-cli-3.21.0/src/Application.php
--- old/platformsh-cli-3.20.5/src/Application.php       2017-10-13 
10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/src/Application.php       2017-10-18 
10:41:52.000000000 +0200
@@ -134,6 +134,9 @@
         $commands[] = new Command\Local\LocalCleanCommand();
         $commands[] = new Command\Local\LocalDrushAliasesCommand();
         $commands[] = new Command\Local\LocalDirCommand();
+        $commands[] = new Command\Mount\MountListCommand();
+        $commands[] = new Command\Mount\MountDownloadCommand();
+        $commands[] = new Command\Mount\MountUploadCommand();
         $commands[] = new Command\Project\ProjectCurlCommand();
         $commands[] = new Command\Project\ProjectCreateCommand();
         $commands[] = new Command\Project\ProjectDeleteCommand();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Command/Auth/LoginCommand.php 
new/platformsh-cli-3.21.0/src/Command/Auth/LoginCommand.php
--- old/platformsh-cli-3.20.5/src/Command/Auth/LoginCommand.php 2017-10-13 
10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/src/Command/Auth/LoginCommand.php 2017-10-18 
10:41:52.000000000 +0200
@@ -48,9 +48,13 @@
         $cache->flushAll();
 
         $info = $this->api()->getClient(false)->getAccountInfo();
-        if (isset($info['mail'])) {
+        if (isset($info['username'], $info['mail'])) {
             $this->stdErr->writeln('');
-            $this->stdErr->writeln('You are logged in as <info>' . 
$info['mail'] . '</info>.');
+            $this->stdErr->writeln(sprintf(
+                'You are logged in as <info>%s</info> (%s).',
+                $info['username'],
+                $info['mail']
+            ));
         }
     }
 
@@ -59,18 +63,8 @@
         /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
         $questionHelper = $this->getService('question_helper');
 
-        $question = new Question('Your email address: ');
-        $question->setValidator(
-            function ($answer) {
-                if (empty($answer) || !filter_var($answer, 
FILTER_VALIDATE_EMAIL)) {
-                    throw new \RuntimeException(
-                        'Please provide a valid email address.'
-                    );
-                }
-
-                return $answer;
-            }
-        );
+        $question = new Question('Your email address or username: ');
+        $question->setValidator([$this, 'validateUsernameOrEmail']);
         $question->setMaxAttempts(5);
         $email = $questionHelper->ask($input, $output, $question);
 
@@ -139,4 +133,35 @@
             }
         }
     }
+
+    /**
+     * Validation callback for the username or email address.
+     *
+     * @param string $username
+     *
+     * @return string
+     */
+    public function validateUsernameOrEmail($username)
+    {
+        $username = trim($username);
+        if (!strlen($username) || (!filter_var($username, 
FILTER_VALIDATE_EMAIL) && !$this->validateUsername($username))) {
+            throw new \RuntimeException(
+                'Please enter a valid email address or username.'
+            );
+        }
+
+        return $username;
+    }
+
+    /**
+     * Validate a username.
+     *
+     * @param string $username
+     *
+     * @return bool
+     */
+    protected function validateUsername($username)
+    {
+        return preg_match('/^[a-z0-9][a-z0-9-]{0,30}[a-z0-9]$/', $username) 
=== 1;
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Command/Mount/MountCommandBase.php 
new/platformsh-cli-3.21.0/src/Command/Mount/MountCommandBase.php
--- old/platformsh-cli-3.20.5/src/Command/Mount/MountCommandBase.php    
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.21.0/src/Command/Mount/MountCommandBase.php    
2017-10-18 10:41:52.000000000 +0200
@@ -0,0 +1,184 @@
+<?php
+
+namespace Platformsh\Cli\Command\Mount;
+
+use Platformsh\Cli\Command\CommandBase;
+use Symfony\Component\Console\Output\OutputInterface;
+
+abstract class MountCommandBase extends CommandBase
+{
+
+    /**
+     * Get the remote application config.
+     *
+     * @param string $sshUrl
+     * @param bool   $refresh
+     *
+     * @return array
+     */
+    protected function getAppConfig($sshUrl, $refresh = true)
+    {
+        /** @var \Platformsh\Cli\Service\RemoteEnvVars $envVarService */
+        $envVarService = $this->getService('remote_env_vars');
+
+        $result = $envVarService->getEnvVar('APPLICATION', $sshUrl, $refresh);
+
+        return (array) json_decode(base64_decode($result), true);
+    }
+
+    /**
+     * Format the mounts as an array of options for a ChoiceQuestion.
+     *
+     * @param array $mounts
+     *
+     * @return array
+     */
+    protected function getMountsAsOptions(array $mounts)
+    {
+        $options = [];
+        foreach ($mounts as $path => $id) {
+            $normalized = $this->normalizeMountPath($path);
+            $options[$normalized] = sprintf('<question>%s</question>: %s', 
$normalized, trim($id, '/'));
+        }
+
+        return $options;
+    }
+
+    /**
+     * Get the path under '.platform/local/shared' for a mount.
+     *
+     * @param string $path
+     * @param array  $mounts
+     *
+     * @return string|false
+     */
+    protected function getSharedPath($path, array $mounts)
+    {
+        $normalized = $this->normalizeMountPath($path);
+        foreach ($mounts as $path => $uri) {
+            if ($this->normalizeMountPath($path) === $normalized
+                && preg_match('#^shared:files/(.+)$#', $uri, $matches)) {
+                return trim($matches[1], '/');
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Normalize a path to a mount.
+     *
+     * @param string $path
+     *
+     * @return string
+     */
+    protected function normalizeMountPath($path)
+    {
+        return trim(trim($path), '/');
+    }
+
+    /**
+     * Validate and normalize a path to a mount.
+     *
+     * @param string $inputPath
+     * @param array  $mounts
+     *
+     * @return string
+     *   The normalized mount path.
+     */
+    protected function validateMountPath($inputPath, array $mounts)
+    {
+        $normalized = trim(trim($inputPath), '/');
+        foreach (array_keys($mounts) as $path) {
+            if (trim(trim($path), '/') === $normalized) {
+                return $normalized;
+            }
+        }
+
+        throw new \InvalidArgumentException(sprintf('Mount not found: 
<error>%s</error>', $inputPath));
+    }
+
+    /**
+     * Validate a directory argument.
+     *
+     * @param string $directory
+     * @param bool   $writable
+     */
+    protected function validateDirectory($directory, $writable = false)
+    {
+        if (!is_dir($directory)) {
+            throw new \InvalidArgumentException(sprintf('Directory not found: 
%s', $directory));
+        } elseif (!is_readable($directory)) {
+            throw new \InvalidArgumentException(sprintf('Directory not 
readable: %s', $directory));
+        } elseif ($writable && !is_writable($directory)) {
+            throw new \InvalidArgumentException(sprintf('Directory not 
writable: %s', $directory));
+        }
+    }
+
+    /**
+     * Push the local contents to the chosen mount.
+     *
+     * @param string $sshUrl
+     * @param string $mountPath
+     * @param string $localPath
+     * @param bool   $up
+     * @param bool   $delete
+     */
+    protected function runSync($sshUrl, $mountPath, $localPath, $up, $delete = 
false)
+    {
+        /** @var \Platformsh\Cli\Service\Shell $shell */
+        $shell = $this->getService('shell');
+
+        $mountPathAbsolute = $this->getAppDir($sshUrl) . '/' . $mountPath;
+
+        if ($up) {
+            $this->stdErr->writeln(sprintf('Uploading files from 
<info>%s</info> to the remote mount <info>%s</info>', $localPath, 
$mountPathAbsolute));
+        } else {
+            $this->stdErr->writeln(sprintf('Downloading files from the remote 
mount <info>%s</info> to <info>%s</info>', $mountPathAbsolute, $localPath));
+        }
+
+        $params = ['rsync', '--archive', '--compress', '--human-readable'];
+
+        if ($this->stdErr->isVeryVerbose()) {
+            $params[] = '-vv';
+        } elseif (!$this->stdErr->isQuiet()) {
+            $params[] = '-v';
+        }
+
+        if ($up) {
+            $params[] = rtrim($localPath, '/') . '/';
+            $params[] = sprintf('%s:%s', $sshUrl, $mountPathAbsolute);
+        } else {
+            $params[] = sprintf('%s:%s/', $sshUrl, $mountPathAbsolute);
+            $params[] = $localPath;
+        }
+
+        if ($delete) {
+            $params[] = '--delete';
+        }
+
+        $start = microtime(true);
+        $shell->execute($params, null, true, false, [], null);
+
+        $this->stdErr->writeln(sprintf('  time: %ss', 
number_format(microtime(true) - $start, 2)), OutputInterface::VERBOSITY_NORMAL);
+
+        if ($up) {
+            $this->stdErr->writeln('The upload completed successfully.');
+        } else {
+            $this->stdErr->writeln('The download completed successfully.');
+        }
+    }
+
+    /**
+     * @param string $sshUrl
+     *
+     * @return string
+     */
+    private function getAppDir($sshUrl)
+    {
+        /** @var \Platformsh\Cli\Service\RemoteEnvVars $envVarService */
+        $envVarService = $this->getService('remote_env_vars');
+
+        return $envVarService->getEnvVar('APP_DIR', $sshUrl, false, 86400) ?: 
'/app';
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Command/Mount/MountDownloadCommand.php 
new/platformsh-cli-3.21.0/src/Command/Mount/MountDownloadCommand.php
--- old/platformsh-cli-3.20.5/src/Command/Mount/MountDownloadCommand.php        
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.21.0/src/Command/Mount/MountDownloadCommand.php        
2017-10-18 10:41:52.000000000 +0200
@@ -0,0 +1,120 @@
+<?php
+
+namespace Platformsh\Cli\Command\Mount;
+
+use Platformsh\Cli\Local\LocalApplication;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+
+class MountDownloadCommand extends MountCommandBase
+{
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('mount:download')
+            ->setDescription('Download files from a mount, using rsync')
+            ->addOption('mount', 'm', InputOption::VALUE_REQUIRED, 'The mount 
(as an app-relative path)')
+            ->addOption('target', null, InputOption::VALUE_REQUIRED, 'The 
directory to which files will be downloaded')
+            ->addOption('delete', null, InputOption::VALUE_NONE, 'Whether to 
delete extraneous files in the target directory')
+            ->addOption('refresh', null, InputOption::VALUE_NONE, 'Whether to 
refresh the cache');
+        $this->addProjectOption();
+        $this->addEnvironmentOption();
+        $this->addAppOption();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->validateInput($input);
+
+        $appName = $this->selectApp($input);
+        $sshUrl = $this->getSelectedEnvironment()
+            ->getSshUrl($appName);
+
+        $appConfig = $this->getAppConfig($sshUrl, (bool) 
$input->getOption('refresh'));
+
+        if (empty($appConfig['mounts'])) {
+            $this->stdErr->writeln(sprintf('The app "%s" doesn\'t define any 
mounts.', $appConfig['name']));
+
+            return 1;
+        }
+
+        /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
+        $questionHelper = $this->getService('question_helper');
+        /** @var \Platformsh\Cli\Service\Filesystem $fs */
+        $fs = $this->getService('fs');
+
+        if ($input->getOption('mount')) {
+            $mountPath = $this->validateMountPath($input->getOption('mount'), 
$appConfig['mounts']);
+        } elseif ($input->isInteractive()) {
+            $mountPath = $questionHelper->choose(
+                $this->getMountsAsOptions($appConfig['mounts']),
+                'Enter a number to choose a mount to download from:'
+            );
+        } else {
+            $this->stdErr->writeln('The <error>--mount</error> option must be 
specified (in non-interactive mode).');
+
+            return 1;
+        }
+
+        $target = null;
+        $defaultTarget = null;
+        if ($input->getOption('target')) {
+            $target = $input->getOption('target');
+        } elseif ($projectRoot = $this->getProjectRoot()) {
+            if ($sharedPath = $this->getSharedPath($mountPath, 
$appConfig['mounts'])) {
+                if (file_exists($projectRoot . '/' . 
$this->config()->get('local.shared_dir') . '/' . $sharedPath)) {
+                    $defaultTarget = $projectRoot . '/' . 
$this->config()->get('local.shared_dir') . '/' . $sharedPath;
+                }
+            }
+
+            $applications = LocalApplication::getApplications($projectRoot, 
$this->config());
+            $appPath = $projectRoot;
+            foreach ($applications as $path => $candidateApp) {
+                if ($candidateApp->getName() === $appName) {
+                    $appPath = $path;
+                    break;
+                }
+            }
+            if (is_dir($appPath . '/' . $mountPath)) {
+                $defaultTarget = $appPath . '/' . $mountPath;
+            }
+        }
+
+        if (empty($target) && $input->isInteractive()) {
+            $questionText = 'Target directory';
+            if ($defaultTarget !== null) {
+                $formattedDefaultTarget = 
$fs->formatPathForDisplay($defaultTarget);
+                $questionText .= ' <question>[' . $formattedDefaultTarget . 
']</question>';
+            }
+            $questionText .= ': ';
+            $target = $questionHelper->ask($input, $this->stdErr, new 
Question($questionText, $defaultTarget));
+        }
+
+        if (empty($target)) {
+            $this->stdErr->writeln('The target directory must be specified.');
+
+            return 1;
+        }
+
+        $this->validateDirectory($target, true);
+
+        $confirmText = "\nThis will <options=bold>add, replace, and delete</> 
files in the local directory '<comment>" . $fs->formatPathForDisplay($target) . 
"</comment>'."
+            . "\n\nAre you sure you want to continue?";
+        if (!$questionHelper->confirm($confirmText)) {
+            return 1;
+        }
+
+        $this->runSync($sshUrl, $mountPath, $target, false, (bool) 
$input->getOption('delete'));
+
+        return 0;
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Command/Mount/MountListCommand.php 
new/platformsh-cli-3.21.0/src/Command/Mount/MountListCommand.php
--- old/platformsh-cli-3.20.5/src/Command/Mount/MountListCommand.php    
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.21.0/src/Command/Mount/MountListCommand.php    
2017-10-18 10:41:52.000000000 +0200
@@ -0,0 +1,70 @@
+<?php
+
+namespace Platformsh\Cli\Command\Mount;
+
+use Platformsh\Cli\Service\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class MountListCommand extends MountCommandBase
+{
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('mount:list')
+            ->setAliases(['mounts'])
+            ->setDescription('Get a list of mounts')
+            ->addOption('paths', null, InputOption::VALUE_NONE, 'Output the 
mount paths only (one per line)')
+            ->addOption('refresh', null, InputOption::VALUE_NONE, 'Whether to 
refresh the cache');
+        Table::configureInput($this->getDefinition());
+        $this->addProjectOption();
+        $this->addEnvironmentOption();
+        $this->addAppOption();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->validateInput($input);
+
+        $sshUrl = $this->getSelectedEnvironment()
+            ->getSshUrl($this->selectApp($input));
+
+        $appConfig = $this->getAppConfig($sshUrl, (bool) 
$input->getOption('refresh'));
+
+        $mounts = $appConfig['mounts'];
+        if (empty($mounts)) {
+            $this->stdErr->writeln(sprintf('The app "%s" doesn\'t define any 
mounts.', $appConfig['name']));
+
+            return 0;
+        }
+
+        if ($input->getOption('paths')) {
+            foreach (array_keys($mounts) as $path) {
+                $output->writeln($this->normalizeMountPath($path));
+            }
+
+            return 0;
+        }
+
+        $header = ['Path', 'Definition'];
+        $rows = [];
+        foreach ($mounts as $path => $definition) {
+            $rows[] = [$this->normalizeMountPath($path), $definition];
+        }
+
+        /** @var \Platformsh\Cli\Service\Table $table */
+        $table = $this->getService('table');
+        $this->stdErr->writeln(sprintf('Mounts in the app <info>%s</info> 
(environment <info>%s</info>):', $appConfig['name'], 
$this->getSelectedEnvironment()->id));
+        $table->render($rows, $header);
+
+        return 0;
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Command/Mount/MountUploadCommand.php 
new/platformsh-cli-3.21.0/src/Command/Mount/MountUploadCommand.php
--- old/platformsh-cli-3.20.5/src/Command/Mount/MountUploadCommand.php  
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.21.0/src/Command/Mount/MountUploadCommand.php  
2017-10-18 10:41:52.000000000 +0200
@@ -0,0 +1,120 @@
+<?php
+
+namespace Platformsh\Cli\Command\Mount;
+
+use Platformsh\Cli\Local\LocalApplication;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+
+class MountUploadCommand extends MountCommandBase
+{
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('mount:upload')
+            ->setDescription('Upload files to a mount, using rsync')
+            ->addOption('source', null, InputOption::VALUE_REQUIRED, 'A 
directory containing files to upload')
+            ->addOption('mount', 'm', InputOption::VALUE_REQUIRED, 'The mount 
(as an app-relative path)')
+            ->addOption('delete', null, InputOption::VALUE_NONE, 'Whether to 
delete extraneous files in the mount')
+            ->addOption('refresh', null, InputOption::VALUE_NONE, 'Whether to 
refresh the cache');
+        $this->addProjectOption();
+        $this->addEnvironmentOption();
+        $this->addAppOption();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->validateInput($input);
+        $appName = $this->selectApp($input);
+
+        $sshUrl = $this->getSelectedEnvironment()
+            ->getSshUrl($appName);
+
+        $appConfig = $this->getAppConfig($sshUrl, (bool) 
$input->getOption('refresh'));
+
+        if (empty($appConfig['mounts'])) {
+            $this->stdErr->writeln(sprintf('The app "%s" doesn\'t define any 
mounts.', $appConfig['name']));
+
+            return 1;
+        }
+
+        /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
+        $questionHelper = $this->getService('question_helper');
+        /** @var \Platformsh\Cli\Service\Filesystem $fs */
+        $fs = $this->getService('fs');
+
+        if ($input->getOption('mount')) {
+            $mountPath = $this->validateMountPath($input->getOption('mount'), 
$appConfig['mounts']);
+        } elseif ($input->isInteractive()) {
+            $mountPath = $questionHelper->choose(
+                $this->getMountsAsOptions($appConfig['mounts']),
+                'Enter a number to choose a mount to upload to:'
+            );
+        } else {
+            $this->stdErr->writeln('The <error>--mount</error> option must be 
specified (in non-interactive mode).');
+
+            return 1;
+        }
+
+        $source = null;
+        $defaultSource = null;
+        if ($input->getOption('source')) {
+            $source = $input->getOption('source');
+        } elseif ($projectRoot = $this->getProjectRoot()) {
+            if ($sharedPath = $this->getSharedPath($mountPath, 
$appConfig['mounts'])) {
+                if (file_exists($projectRoot . '/' . 
$this->config()->get('local.shared_dir') . '/' . $sharedPath)) {
+                    $defaultSource = $projectRoot . '/' . 
$this->config()->get('local.shared_dir') . '/' . $sharedPath;
+                }
+            }
+
+            $applications = LocalApplication::getApplications($projectRoot, 
$this->config());
+            $appPath = $projectRoot;
+            foreach ($applications as $path => $candidateApp) {
+                if ($candidateApp->getName() === $appName) {
+                    $appPath = $path;
+                    break;
+                }
+            }
+            if (is_dir($appPath . '/' . $mountPath)) {
+                $defaultSource = $appPath . '/' . $mountPath;
+            }
+        }
+
+        if (empty($source) && $input->isInteractive()) {
+            $questionText = 'Source directory';
+            if ($defaultSource !== null) {
+                $formattedDefaultSource = 
$fs->formatPathForDisplay($defaultSource);
+                $questionText .= ' <question>[' . $formattedDefaultSource . 
']</question>';
+            }
+            $questionText .= ': ';
+            $source = $questionHelper->ask($input, $this->stdErr, new 
Question($questionText, $defaultSource));
+        }
+
+        if (empty($source)) {
+            $this->stdErr->writeln('The source directory must be specified.');
+
+            return 1;
+        }
+
+        $this->validateDirectory($source);
+
+        $confirmText = "\nThis will <options=bold>add, replace, and delete</> 
files in the remote mount '<info>$mountPath</info>'."
+            . "\n\nAre you sure you want to continue?";
+        if (!$questionHelper->confirm($confirmText)) {
+            return 1;
+        }
+
+        $this->runSync($sshUrl, $mountPath, $source, true, (bool) 
$input->getOption('delete'));
+
+        return 0;
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.20.5/src/Console/CustomTextDescriptor.php 
new/platformsh-cli-3.21.0/src/Console/CustomTextDescriptor.php
--- old/platformsh-cli-3.20.5/src/Console/CustomTextDescriptor.php      
2017-10-13 10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/src/Console/CustomTextDescriptor.php      
2017-10-18 10:41:52.000000000 +0200
@@ -103,7 +103,7 @@
 
             if ($describedNamespace) {
                 $this->writeText(
-                    sprintf("<comment>Available commands for the \"%s\" 
namespace:</comment>", $describedNamespace),
+                    sprintf("<comment>Available commands for the \"%s\" 
namespace:</comment>", $application->findNamespace($describedNamespace)),
                     $options
                 );
             } else {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/src/Service/Filesystem.php 
new/platformsh-cli-3.21.0/src/Service/Filesystem.php
--- old/platformsh-cli-3.20.5/src/Service/Filesystem.php        2017-10-13 
10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/src/Service/Filesystem.php        2017-10-18 
10:41:52.000000000 +0200
@@ -252,6 +252,23 @@
     }
 
     /**
+     * Format a path for display (use the relative path if it's simpler).
+     *
+     * @param string $path
+     *
+     * @return string
+     */
+    public function formatPathForDisplay($path)
+    {
+        $relative = $this->makePathRelative($path, getcwd());
+        if (strpos($relative, '../..') === false && strlen($relative) < 
strlen($path)) {
+            return $relative;
+        }
+
+        return rtrim(trim($path), '/');
+    }
+
+    /**
      * Check if a filename is in the blacklist.
      *
      * @param string   $filename
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.20.5/src/Service/Shell.php 
new/platformsh-cli-3.21.0/src/Service/Shell.php
--- old/platformsh-cli-3.20.5/src/Service/Shell.php     2017-10-13 
10:57:00.000000000 +0200
+++ new/platformsh-cli-3.21.0/src/Service/Shell.php     2017-10-18 
10:41:52.000000000 +0200
@@ -19,8 +19,6 @@
     /** @var OutputInterface */
     protected $stdErr;
 
-    protected $defaultTimeout = 3600;
-
     public function __construct(OutputInterface $output = null)
     {
         $this->setOutput($output ?: new NullOutput());
@@ -78,6 +76,7 @@
      * @param bool        $mustRun
      * @param bool        $quiet
      * @param array       $env
+     * @param int|null    $timeout
      *
      * @throws \Exception
      *   If $mustRun is enabled and the command fails.
@@ -86,11 +85,16 @@
      *   False if the command fails, true if it succeeds with no output, or a
      *   string if it succeeds with output.
      */
-    public function execute(array $args, $dir = null, $mustRun = false, $quiet 
= true, array $env = [])
+    public function execute(array $args, $dir = null, $mustRun = false, $quiet 
= true, array $env = [], $timeout = 3600)
     {
         $builder = new ProcessBuilder($args);
         $process = $builder->getProcess();
-        $process->setTimeout($this->defaultTimeout);
+
+        if ($timeout === null) {
+            set_time_limit(0);
+        }
+
+        $process->setTimeout($timeout);
 
         $this->stdErr->writeln(
             "Running command: <info>" . $process->getCommandLine() . "</info>",

++++++ platformsh-cli-vendor.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/autoload.php new/vendor/autoload.php
--- old/vendor/autoload.php     2017-10-13 16:15:40.586800666 +0200
+++ new/vendor/autoload.php     2017-10-18 15:52:30.650536485 +0200
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit8f904e37181a69f3b56037fd804558c1::getLoader();
+return ComposerAutoloaderInit94be79301dbcc61b3879e411153f86b2::getLoader();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/composer/autoload_real.php 
new/vendor/composer/autoload_real.php
--- old/vendor/composer/autoload_real.php       2017-10-13 16:15:40.586800666 
+0200
+++ new/vendor/composer/autoload_real.php       2017-10-18 15:52:30.650536485 
+0200
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit8f904e37181a69f3b56037fd804558c1
+class ComposerAutoloaderInit94be79301dbcc61b3879e411153f86b2
 {
     private static $loader;
 
@@ -19,15 +19,15 @@
             return self::$loader;
         }
 
-        
spl_autoload_register(array('ComposerAutoloaderInit8f904e37181a69f3b56037fd804558c1',
 'loadClassLoader'), true, true);
+        
spl_autoload_register(array('ComposerAutoloaderInit94be79301dbcc61b3879e411153f86b2',
 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        
spl_autoload_unregister(array('ComposerAutoloaderInit8f904e37181a69f3b56037fd804558c1',
 'loadClassLoader'));
+        
spl_autoload_unregister(array('ComposerAutoloaderInit94be79301dbcc61b3879e411153f86b2',
 'loadClassLoader'));
 
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') 
&& (!function_exists('zend_loader_file_encoded') || 
!zend_loader_file_encoded());
         if ($useStaticLoader) {
             require_once __DIR__ . '/autoload_static.php';
 
-            
call_user_func(\Composer\Autoload\ComposerStaticInit8f904e37181a69f3b56037fd804558c1::getInitializer($loader));
+            
call_user_func(\Composer\Autoload\ComposerStaticInit94be79301dbcc61b3879e411153f86b2::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -48,19 +48,19 @@
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = 
Composer\Autoload\ComposerStaticInit8f904e37181a69f3b56037fd804558c1::$files;
+            $includeFiles = 
Composer\Autoload\ComposerStaticInit94be79301dbcc61b3879e411153f86b2::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire8f904e37181a69f3b56037fd804558c1($fileIdentifier, 
$file);
+            composerRequire94be79301dbcc61b3879e411153f86b2($fileIdentifier, 
$file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire8f904e37181a69f3b56037fd804558c1($fileIdentifier, 
$file)
+function composerRequire94be79301dbcc61b3879e411153f86b2($fileIdentifier, 
$file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/composer/autoload_static.php 
new/vendor/composer/autoload_static.php
--- old/vendor/composer/autoload_static.php     2017-10-13 16:15:40.586800666 
+0200
+++ new/vendor/composer/autoload_static.php     2017-10-18 15:52:30.650536485 
+0200
@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit8f904e37181a69f3b56037fd804558c1
+class ComposerStaticInit94be79301dbcc61b3879e411153f86b2
 {
     public static $files = array (
         '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . 
'/symfony/polyfill-mbstring/bootstrap.php',
@@ -188,9 +188,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = 
ComposerStaticInit8f904e37181a69f3b56037fd804558c1::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = 
ComposerStaticInit8f904e37181a69f3b56037fd804558c1::$prefixDirsPsr4;
-            $loader->classMap = 
ComposerStaticInit8f904e37181a69f3b56037fd804558c1::$classMap;
+            $loader->prefixLengthsPsr4 = 
ComposerStaticInit94be79301dbcc61b3879e411153f86b2::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = 
ComposerStaticInit94be79301dbcc61b3879e411153f86b2::$prefixDirsPsr4;
+            $loader->classMap = 
ComposerStaticInit94be79301dbcc61b3879e411153f86b2::$classMap;
 
         }, null, ClassLoader::class);
     }


Reply via email to