/** * Update the current remote server with the array of files provided. * * @param array $files 2-dimensional array with 2 indices: 'upload' and 'delete' * Each of these contains an array of filenames and paths (relative to repository root) */ public function push($files, $localRevision = null) { if (empty($localRevision)) { // We will write this in the server $localRevision = $this->currentRevision(); } $initialBranch = $this->currentBranch(); // If revision is not HEAD, the current one, it means this is a rollback. // So, we have to revert the files the the state they were in that revision. if ($this->revision != 'HEAD') { $this->cli->out(' Rolling back working copy'); // BUG: This does NOT work correctly for submodules & subsubmodules (and leaves them in an incorrect state) // It technically should do a submodule update in the parent, not a checkout inside the submodule $this->git->command('checkout ' . $this->revision, $this->repo); } $filesToDelete = $files['delete']; // Add deleted directories to the list of files to delete. Git does not handle this. $dirsToDelete = []; if (count($filesToDelete) > 0) { $dirsToDelete = $this->hasDeletedDirectories($filesToDelete); } $filesToUpload = $files['upload']; unset($files); // No longer needed // Upload Files if (count($filesToUpload) > 0) { foreach ($filesToUpload as $fileNo => $file) { if ($this->currentSubmoduleName) { $file = $this->currentSubmoduleName . '/' . $file; } // Make sure the folder exists in the FTP server. $dir = explode('/', dirname($file)); $path = ''; $ret = true; // Skip mkdir if dir is basedir if ($dir[0] !== '.') { // Loop through each folder in the path /a/b/c/d.txt to ensure that it exists // @TODO Can be improved by using: $filesystem->write('path/to/file.txt', 'contents'); for ($i = 0, $count = count($dir); $i < $count; ++$i) { $path .= $dir[$i] . '/'; if (!isset($pathsThatExist[$path])) { if (!$this->connection->has($path)) { $this->connection->createDir($path); $this->cli->out(" + Created directory '{$path}'."); $pathsThatExist[$path] = true; } else { $pathsThatExist[$path] = true; } } } } $filePath = $this->repo . '/' . ($this->currentSubmoduleName ? str_replace($this->currentSubmoduleName . '/', '', $file) : $file); $data = @file_get_contents($filePath); // It can happen the path is wrong, especially with included files. if ($data === false) { $this->cli->error(' ! File not found - please check path: ' . $filePath); continue; } $remoteFile = $file; $uploaded = $this->connection->put($remoteFile, $data); if (!$uploaded) { $this->cli->error(" ! Failed to upload {$file}."); } else { $this->deploymentSize += filesize($this->repo . '/' . ($this->currentSubmoduleName ? str_replace($this->currentSubmoduleName . '/', '', $file) : $file)); } $numberOfFilesToUpdate = count($filesToUpload); $fileNo = str_pad(++$fileNo, strlen($numberOfFilesToUpdate), ' ', STR_PAD_LEFT); $this->cli->lightGreen(" ^ {$fileNo} of {$numberOfFilesToUpdate} <white>{$file}"); } } // Delete files if (count($filesToDelete) > 0) { foreach ($filesToDelete as $fileNo => $file) { if ($this->currentSubmoduleName) { $file = $this->currentSubmoduleName . '/' . $file; } $numberOfFilesToDelete = count($filesToDelete); $fileNo = str_pad(++$fileNo, strlen($numberOfFilesToDelete), ' ', STR_PAD_LEFT); if ($this->connection->has($file)) { $this->connection->delete($file); $this->cli->out("<red> × {$fileNo} of {$numberOfFilesToDelete} <white>{$file}"); } else { $this->cli->out("<red> ! {$fileNo} of {$numberOfFilesToDelete} <white>{$file} not found"); } } } // Delete Directories if (count($dirsToDelete) > 0) { foreach ($dirsToDelete as $dirNo => $dir) { if ($this->currentSubmoduleName) { $dir = $this->currentSubmoduleName . '/' . $dir; } $numberOfdirsToDelete = count($dirsToDelete); $dirNo = str_pad(++$dirNo, strlen($numberOfdirsToDelete), ' ', STR_PAD_LEFT); if ($this->connection->has($dir)) { $this->connection->deleteDir($dir); $this->cli->out("<red> × {$dirNo} of {$numberOfdirsToDelete} <white>{$dir}"); } else { $this->cli->out("<red> ! {$dirNo} of {$numberOfdirsToDelete} <white>{$dir} not found"); } } } if (count($filesToUpload) > 0 or count($filesToDelete) > 0) { $this->setRevision($localRevision); } else { $this->cli->gray()->out(' No files to upload or delete.'); } // If $this->revision is not HEAD, it means the rollback command was provided // The working copy was rolled back earlier to run the deployment, and we // now want to return the working copy back to its original state. if ($this->revision != 'HEAD') { $this->git->command('checkout ' . ($initialBranch ?: 'master')); } $this->log('[SHA: ' . $localRevision . '] Deployment to server: "' . $this->currentlyDeploying . '" from branch "' . $initialBranch . '". ' . count($filesToUpload) . ' files uploaded; ' . count($filesToDelete) . ' files deleted.'); }