diff --git a/PHPCI/BuildFactory.php b/PHPCI/BuildFactory.php index 4825047d..ecc37c43 100644 --- a/PHPCI/BuildFactory.php +++ b/PHPCI/BuildFactory.php @@ -61,6 +61,9 @@ class BuildFactory case 'hg': $type = 'MercurialBuild'; break; + case 'svn': + $type = 'SubversionBuild'; + break; } $type = '\\PHPCI\\Model\\Build\\' . $type; diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 8457a7bb..f2f63b26 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -311,10 +311,11 @@ class ProjectController extends PHPCI\Controller 'remote' => Lang::get('remote'), 'local' => Lang::get('local'), 'hg' => Lang::get('hg'), + 'svn' => Lang::get('svn'), ); $field = Form\Element\Select::create('type', Lang::get('where_hosted'), true); - $field->setPattern('^(github|bitbucket|gitlab|remote|local|hg)'); + $field->setPattern('^(github|bitbucket|gitlab|remote|local|hg|svn)'); $field->setOptions($options); $field->setClass('form-control')->setContainerClass('form-group'); $form->addField($field); diff --git a/PHPCI/Languages/lang.en.php b/PHPCI/Languages/lang.en.php index da767727..ec2306e0 100644 --- a/PHPCI/Languages/lang.en.php +++ b/PHPCI/Languages/lang.en.php @@ -101,6 +101,7 @@ PHPCI', 'remote' => 'Remote URL', 'local' => 'Local Path', 'hg' => 'Mercurial', + 'svn' => 'Subversion', 'where_hosted' => 'Where is your project hosted?', 'choose_github' => 'Choose a GitHub repository:', diff --git a/PHPCI/Model/Build/SubversionBuild.php b/PHPCI/Model/Build/SubversionBuild.php new file mode 100644 index 00000000..af884933 --- /dev/null +++ b/PHPCI/Model/Build/SubversionBuild.php @@ -0,0 +1,182 @@ + + * @package PHPCI + * @subpackage Core + */ +class SubversionBuild extends Build +{ + protected $svnCommand = 'svn export -q --non-interactive '; + + /** + * Get the URL to be used to clone this remote repository. + */ + protected function getCloneUrl() + { + $url = $this->getProject()->getReference(); + + if (substr($url, -1) != '/') { + $url .= '/'; + } + + $branch = $this->getBranch(); + + if (empty($branch) || $branch == 'trunk') { + $url .= 'trunk'; + } else { + $url .= 'branches/' . $branch; + } + + return $url; + } + + /** + * @param Builder $builder + * + * @return void + */ + protected function extendSvnCommandFromConfig(Builder $builder) + { + $cmd = $this->svnCommand; + + $svn = $builder->getConfig('svn'); + if ($svn) { + foreach ($svn as $key => $value) { + $cmd .= " --$key $value "; + } + } + + $depth = $builder->getConfig('clone_depth'); + + if (!is_null($depth)) { + $cmd .= ' --depth ' . intval($depth) . ' '; + } + + $this->svnCommand = $cmd; + } + + /** + * Create a working copy by cloning, copying, or similar. + */ + public function createWorkingCopy(Builder $builder, $buildPath) + { + $this->handleConfig($builder, $buildPath); + + $this->extendSvnCommandFromConfig($builder); + + $key = trim($this->getProject()->getSshPrivateKey()); + + if (!empty($key)) { + $success = $this->cloneBySsh($builder, $buildPath); + } else { + $success = $this->cloneByHttp($builder, $buildPath); + } + + if (!$success) { + $builder->logFailure('Failed to export remote subversion repository.'); + return false; + } + + return $this->handleConfig($builder, $buildPath); + } + + /** + * Use an HTTP-based svn export. + */ + protected function cloneByHttp(Builder $builder, $cloneTo) + { + $cmd = $this->svnCommand; + + if ($this->getCommitId() != 'Manual') { + $cmd .= ' -r %s %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getCommitId(), $this->getCloneUrl(), $cloneTo); + } else { + $cmd .= ' %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getCloneUrl(), $cloneTo); + } + + return $success; + } + + /** + * Use an SSH-based svn export. + */ + protected function cloneBySsh(Builder $builder, $cloneTo) + { + $cmd = $this->svnCommand . ' %s "%s"'; + + if (!IS_WIN) { + $keyFile = $this->writeSshKey($cloneTo); + $sshWrapper = $this->writeSshWrapper($cloneTo, $keyFile); + $cmd = 'export SVN_SSH="' . $sshWrapper . '" && ' . $cmd; + } + + $success = $builder->executeCommand($cmd, $this->getCloneUrl(), $cloneTo); + + if (!IS_WIN) { + // Remove the key file and svn wrapper: + unlink($keyFile); + unlink($sshWrapper); + } + + return $success; + } + + /** + * Create an SSH key file on disk for this build. + * @param $cloneTo + * @return string + */ + protected function writeSshKey($cloneTo) + { + $keyPath = dirname($cloneTo . '/temp'); + $keyFile = $keyPath . '.key'; + + // Write the contents of this project's svn key to the file: + file_put_contents($keyFile, $this->getProject()->getSshPrivateKey()); + chmod($keyFile, 0600); + + // Return the filename: + return $keyFile; + } + + /** + * Create an SSH wrapper script for Svn to use, to disable host key checking, etc. + * @param $cloneTo + * @param $keyFile + * @return string + */ + protected function writeSshWrapper($cloneTo, $keyFile) + { + $path = dirname($cloneTo . '/temp'); + $wrapperFile = $path . '.sh'; + + $sshFlags = '-o CheckHostIP=no -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o PasswordAuthentication=no'; + + // Write out the wrapper script for this build: + $script = <<