public function executeSingleCommand(\PGD\Commands\Command $cmd, $addlDataType = '')
 {
     if (trim($addlDataType) !== '') {
         $addlDataType = " {$addlDataType}";
     }
     $ret = $cmd->execute();
     $this->getOutputter()->write($ret->outputAsString(), "output{$addlDataType}");
 }
    public function performDeploy(\PGD\RepositoryInfo $repositoryInfo)
    {
        ob_start();
        $this->getBrowserOutput()->sendContentType();
        $this->getBrowserOutput()->writeRaw('<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="robots" content="noindex">
	<title>Permafrost Git Repository Deploy</title>
  <link href="permafrost-deploy.css" rel="stylesheet">
</head>
<body>
<pre>
');
        $this->getBrowserOutput()->writeQueue();
        $this->getBrowserOutput()->writeRaw('
<div class="output inline highlight bold">Checking the environment ...</div>

');
        //running as a local script, not from an httpd
        if (!isset($_SERVER['SERVER_NAME'])) {
            $_SERVER['SERVER_NAME'] = 'localhost';
        }
        if (is_dir($repositoryInfo->tempDirectory)) {
            if (!chdir($repositoryInfo->tempDirectory)) {
                $this->getBrowserOutput()->write(sprintf("`chdir()` to temporary/staging directory '%s' failed.", $repositoryInfo->tempDirectory), "error");
                die;
            }
        } else {
            $this->getBrowserOutput()->write(sprintf("Temporary/staging directory '%s' not found.", $repositoryInfo->tempDirectory), "error");
            die;
        }
        $this->getTimer()->start();
        $this->getBrowserOutput()->write("Running as ", "output inline highlight");
        $this->getCommandRunner()->executeSingleCommand(new \PGD\Commands\Command("whoami", true), "inline highlight bold");
        $this->getBrowserOutput()->writeRaw(".\n\n");
        $this->getBrowserOutput()->write(sprintf("Running on host %s", $_SERVER['SERVER_NAME']), "output inline highlight");
        $this->getBrowserOutput()->writeRaw(".\n\n");
        $this->getRequiredBinaries()->addRequiredBinaries(array('git', 'rsync'));
        if ($repositoryInfo->backup) {
            $this->getRequiredBinaries()->addRequiredBinary('tar');
            if (!is_dir($repositoryInfo->backupDirectory) || !is_writable($repositoryInfo->backupDirectory)) {
                $this->getBrowserOutput()->shortcut('error', 'Backup directory not found or not writable.');
                die;
            }
        }
        if ($repositoryInfo->useComposer) {
            $this->getRequiredBinaries()->addRequiredBinary('composer --no-ansi');
        }
        $binreq = $this->getRequiredBinaries()->checkRequirements(true);
        if (!$binreq->getResult()) {
            $missingBinary = "";
            $d = $binreq->getData();
            if (!$d[count($d) - 1]->getExists()) {
                $missingBinary = $d[count($d) - 1]->getName();
            }
            $this->getBrowserOutput()->writeEOL();
            $this->getBrowserOutput()->write("Required binary missing: `{$missingBinary}`", 'error');
            $this->getBrowserOutput()->writeEOL();
            die;
        }
        foreach ($binreq->getData() as $appInfo) {
            $this->getBrowserOutput()->shortcut('output', $appInfo->getPath() . ": " . $appInfo->getVersion());
        }
        $this->getBrowserOutput()->write(PHP_EOL . "Environment OK." . PHP_EOL, "output highlight");
        $remote_repo_masked = $repositoryInfo->remoteRepository;
        $remote_repo_masked = preg_replace('/https:\\/\\/[a-zA-Z0-9]{1,}\\:[^@]{1,}@/i', 'https://***@', $remote_repo_masked);
        $this->getBrowserOutput()->write("\nDeploying {$remote_repo_masked} `{$repositoryInfo->branch}`\nto        {$repositoryInfo->targetDirectory} ...\n", "output highlight");
        $this->getBrowserOutput()->writeEOL();
        //-------------------- finished environment check --------------------
        if (!is_dir($repositoryInfo->tempDirectory)) {
            //temp directory doesn't exist, so clone the remote repository into it.
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('git clone --depth=1 --branch %s %s %s', true, $repositoryInfo->branch, $repositoryInfo->remoteRepository, $repositoryInfo->tempDirectory));
        } else {
            // temp directory exists and hopefully already contains the correct remote origin
            // so we'll fetch the changes and reset the contents.
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('git --git-dir="%s.git" --work-tree="%s" fetch origin %s', true, $repositoryInfo->tempDirectory, $repositoryInfo->tempDirectory, $repositoryInfo->branch));
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('git --git-dir="%s.git" --work-tree="%s" reset --hard FETCH_HEAD', true, $repositoryInfo->tempDirectory, $repositoryInfo->tempDirectory));
        }
        // Update the submodules
        $this->getCommandRunner()->addCommand(new \PGD\Commands\Command('git submodule update --init --recursive'));
        // Describe the deployed version
        if ($repositoryInfo->versionFile !== '') {
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('git --git-dir="%s.git" --work-tree="%s" describe --always > %s', false, $repositoryInfo->tempDirectory, $repositoryInfo->tempDirectory, $repositoryInfo->versionFile));
        }
        // Backup the TARGET_DIR
        // without the BACKUP_DIR for the case when it's inside the TARGET_DIR
        if ($repositoryInfo->backup) {
            $backupFileDateFmt = 'YmdHis';
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt("tar --exclude='%s*' -czf %s/%s-%s-%s.tar.gz %s*", true, $repositoryInfo->backupDirectory, $repositoryInfo->backupDirectory, basename($repositoryInfo->targetDirectory), md5($repositoryInfo->targetDirectory), date($backupFileDateFmt), $repositoryInfo->targetDirectory));
        }
        // Invoke composer
        if ($repositoryInfo->useComposer) {
            $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('composer --no-ansi --no-interaction --no-progress --working-dir=%s install %s', true, $repositoryInfo->tempDirectory, $repositoryInfo->composerOptions));
            if ($repositoryInfo->composerHome !== false && $repositoryInfo->composerHome !== '' && is_dir($repositoryInfo->composerHome)) {
                putenv('COMPOSER_HOME=' . $repositoryInfo->composerHome);
            }
        }
        // ==================================================[ Deployment ]===
        // Compile exclude parameters
        $exclude = '';
        foreach (unserialize($repositoryInfo->getExcludedDirectories()) as $exc) {
            $exclude .= " --exclude={$exc}";
        }
        // Deployment command
        $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmt('rsync -rltgoDzvO %s %s %s %s', true, $repositoryInfo->tempDirectory, $repositoryInfo->targetDirectory, $repositoryInfo->deleteFiles ? '--delete-after' : '', $exclude));
        // =======================================[ Post-Deployment steps ]===
        // Remove the TMP_DIR (depends on CLEAN_UP)
        if ($repositoryInfo->cleanUp) {
            //make sure the directory we want to delete exists, and is not /.
            if (is_dir($repositoryInfo->tempDirectory)) {
                if (count(explode(DIRECTORY_SEPARATOR, $repositoryInfo->tempDirectory)) > 1) {
                    $this->getCommandRunner()->addCommand(\PGD\Commands\Command::createFmtNamed('rm -rf %s', 'cleanup', true, $repositoryInfo->tempDirectory), 'cleanup');
                }
            }
        }
        // =======================================[ Run the command steps ]===
        $cr = $this->getCommandRunner();
        $execResults = $this->getCommandRunner()->execute('', PHP_EOL);
        $this->getBrowserOutput()->flush();
        $all_return_codes = 0;
        foreach ($execResults as $er) {
            $all_return_codes = $all_return_codes & $er->returnCode;
        }
        if ($repositoryInfo->cleanUp) {
            $execResult = $this->getCommandRunner()->getCommandByName('cleanup')->execute();
            printf('
    
Cleaning up temporary files ...
    
<i class="prompt"></i><span class="command">%s</span>
<div class="output">%s</div>
', htmlentities(trim($this->getCommandRunner()->getCommandByName('cleanup')->getCommand())), htmlentities(trim($execResult->outputAsString())));
        }
        if ($all_return_codes !== 0) {
            error_log(sprintf('Deployment error on %s using %s!', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost', __FILE__));
        }
        //-------------------- deploy finished --------------------
        $this->getTimer()->stop();
        $this->getBrowserOutput()->writeEOL();
        $this->getBrowserOutput()->write('Done.' . PHP_EOL, "output highlight");
        $this->getBrowserOutput()->write(sprintf("Deploy took %s seconds." . PHP_EOL, $this->getTimer()->result(true)), "output highlight");
        $this->getBrowserOutput()->writeRaw('</pre></body></html>');
    }