在Linux上设置Subversion存储库

在Linux上设置Subversion存储库

在本教程中,我将介绍如何设置用于PHP JavaScript开发的子版本库。 我是Group-Office组件的主要开发人员Intermesh的所有者。

SVN存储库将由多个用户使用SSH密钥登录到服务器。 所有用户将使用svn系统用户,但我们将使用SSH密钥来识别用户。 将使用预提交钩子来验证PHP和Javascript以符合编码规则。

我们使用一个新鲜的最小的debian安装来做到这一点。

安装所需的软件包

在最小的Debian服务器上安装所需的软件:

$ apt-get install subversion php-codesniffer php5-cli

设置颠覆系统用户和隧道

我们将只创建一个将读取和写入SVN信息库的系统用户。 我们将使用SSH的authorized_keys文件将不同的SSH密钥隧道传送到svnserve命令。

以root身份登录到shell并键入以下命令:

$ adduser svn
$ mkdir /home/svn/.ssh
$ touch /home/svn/.ssh/authorized_keys
$ chown -R svn:svn /home/svn/.ssh

现在编辑/home/svn/.ssh/authorized_keys并为每个用户添加一个公共SSH密钥:

command="/usr/bin/svnserve -t -r /usr/local/svn --tunnel-user=repouser1",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty,from="*" ssh-rsa AAAAB3NzaC1yc2EAAAADAQAadsadadAAABAQCrJDzuDALKnKpv9xMaypcTcHHCfhaGuPEoVNyyZ2CrdwCI/GaB04A3YLjaJgdsdsadaB96Lpt5aoxunayx5n0OhRXZZZAioG8CX4Lvmddi/O2fSTU3qfe5iJ4fXUDMzCsadsadsadsadsadFpbTZnbLn6pMjM3KrX+SZlFuBY3zO/WB6i3vcnKpZ1N2wuWcTeOzFm5AMKJGMb4s7rMkA0WlzLZ0xMdzraJK2I43trZf8DUeFeydYRi29Dm2Sr5Jm1uuFt9E3N8VFaIdb7K3IK954lp8ks2ueEXcWLArVFcrAJ15AWS6fzLFQfJeqGaumHjGZTBPnrAg41KHRYXb9Oj65LquNFCrazN3laLIKh repouser1@Intermesh-2

设置存储库

我们将使用以下命令在“/ usr / local / svn”中创建一个存储库“testrepository”:

$ mkdir /usr/local/svn
$ svnadmin create /usr/local/svn/testrepository
$ chown svn:svn -R /usr/local/svn

您可能需要控制“/ usr / local / svn / testrepository / conf / authz”中的权限。 确保在svnserv.conf中启用了authz-db = authz。

存储库根目录的URL将是:“svn + ssh://svn@svn.example.com/testrepository”。

设置PHP预提交钩子

我们想在提交时测试我们的PHP语法和编码风格,以便安装php-codesniffer:

$ apt-get install php-codesniffer

创建文件/usr/local/svn/CommitTests.php

<?php




/**

* Subversion pre-commit hook script validating commit log message

*/




/**

* Class for performing various tests in subversion pre-commit hooks

*/

class CommitTests {




/**

* path to php binary

*/

public static $PHP = "/usr/bin/php";




/*

* path to phpcs library

*/

public static $PHPCS = "/usr/bin/phpcs";




/**

* Commit message string

* @var string

*/

protected $_logMessage;




/**

* Commit files list

* @var array

*/

protected $_commitList;




/**

* Changed files list

* @var array

*/

protected $_changedFiles;




/**

* Subversion repository path

* @var string

*/

protected $_repository;




/**

* Transaction number

* @var int

*/

protected $_transaction;




/**

* Class constructor

*

* @param string $repository

* @param string $transaction

* @param array $tests array of test names to run

*/

public function __construct($repository, $transaction, array $tests) {

$this->_repository = $repository;

$this->_transaction = $transaction;

exit($this->_runTests($tests));

}




/**

* Run subversion pre-commit tests

*

* @param array $tests array of test names to run

* @return int result code, 0 == all test passed, other value represents

* number of failed tests

*/

protected function _runTests(array $tests) {

$result = 0;

$messages = '';

foreach ($tests as $k => $v) {

if (is_numeric($k)) {

$test = $v;

$params = array();

} else {

$test = $k;

$params = $v;




if (!is_array($params)) {

throw new Exception('Test arguments should be in an array.');

}

}




$method = "_test$test";

$msg = '';

array_unshift($params, &$msg);

$result +=!call_user_func_array(array($this, $method), $params);

if ($msg) {

$messages .= " *) $msg\n";

}

}




if ($messages) {

$messages = rtrim($messages);

fwrite(STDERR, "----------------\n$messages\n----------------");

}




return $result;

}




/**

* Get commit log message

*

* @return string

*/

protected function _getLogMessage() {

if (null !== $this->_logMessage) {

return $this->_logMessage;

}




$output = null;

$cmd = "svnlook log -t '{$this->_transaction}' '{$this->_repository}'";

exec($cmd, $output);

$this->_logMessage = implode($output);

return $this->_logMessage;

}




/**

* Get content of file from current transaction

*

* @param string $file

* @return string

* @throws Exception

*/

protected function _getFileContent($file) {

$content = '';

$cmd = "svnlook cat -t '{$this->_transaction}' '{$this->_repository}' '$file' 2>&1";




// can't use exec() here because it will strip trailing spaces

$handle = popen($cmd, 'r');

while (!feof($handle)) {

$content .= fread($handle, 1024);

}

$return = pclose($handle);




if (0 != $return) {

throw new Exception($content, $return);

}




return $content;

}




/**

* Get svn properties for file

*

* @param string $file

* @return array

*/

protected function _getFileProps($file) {

$props = array();

$cmd = "svnlook proplist -t '{$this->_transaction}' '{$this->_repository}' '$file'";

$output = null;

exec($cmd, $output);




foreach ($output as $line) {

$propname = trim($line);

$cmd = "svnlook propget -t '{$this->_transaction}' '{$this->_repository}' $propname"

. " '$file'";

$output2 = null;

exec($cmd, $output2);

$propval = trim(implode($output2));




$props[] = "$propname=$propval";

}

return $props;

}




/**

* Get commit files list

*

* @return array filenames are keys and status letters are values

*/

protected function _getCommitList() {

if (null !== $this->_commitList) {

return $this->_commitList;

}




$output = null;

$cmd = "svnlook changed -t '{$this->_transaction}' '{$this->_repository}'";

exec($cmd, $output);




$list = array();

foreach ($output as $item) {

$pos = strpos($item, ' ');

$status = substr($item, 0, $pos);

$file = trim(substr($item, $pos));




$list[$file] = $status;

}




$this->_commitList = $list;

return $this->_commitList;

}




/**

* Get array of modified and added files

*

* @param array $filetypes array of file types used for filtering

* @return array

*/

protected function _getChangedFiles(array $filetypes = array()) {

if (null === $this->_changedFiles) {

$list = $this->_getCommitList();

$files = array();

foreach ($list as $file => $status) {

if ('D' == $status || substr($file, -1) == DIRECTORY_SEPARATOR) {

continue;

}




$files[] = $file;

}

$this->_changedFiles = $files;

}




$files = array();

foreach ($this->_changedFiles as $file) {

$extension = pathinfo($file, PATHINFO_EXTENSION);

$extension = strtolower($extension);

if ($filetypes && !in_array($extension, $filetypes)) {

continue;

}

$files[$file] = $extension;

}




return $files;

}




/**

* trialing comma's in objects or arrays break internet explorer.

*

* @param string $msg

* @return boolean

*/

protected function _testIECommaBug(&$msg) {




$files = $this->_getChangedFiles(array('js'));

foreach ($files as $file => $extension) {

$content = $this->_getFileContent($file);

if (preg_match('/,\s*[}\]]/s', $content)) {

$msg = "You have a trailing , in your javascript file $file. This will cause problems in IE";

return false;

}

}




return true;

}




/**

* Check if log message validates length rules

*

* @param string $msg error messages placeholder

* @param int $minlength minimum length of log message

* @return bool

*/

protected function _testLogMessageLength(&$msg, $minlength = 1) {

$length = strlen(trim($this->_getLogMessage()));

if ($length < $minlength) {

if ($minlength <= 1) {

$msg = "Log message should not be empty. Please specify descriptive log message.";

} else {

$msg = "You log message is too short ($length). It should be at least $minlength"

. " characters long.";

}

return false;

}




return true;

}




/**

* Check if tabs are used as indents instead of spaces

*

* @param string $msg error messages placeholder

* @param array $filetypes array of file types which should be tested

* @return bool

*/

protected function _testTabIndents(&$msg, array $filetypes = array()) {

$result = true;

$files = $this->_getChangedFiles($filetypes);

foreach ($files as $file => $extension) {

$content = $this->_getFileContent($file);




// check if file contains tabs

$m = null;

$tablines = preg_match('/^[ ]{2,}\}/m', $content, $m);




if ($tablines) {

$result = false;

$msg .= "\t[$file] Indents with spaces found\n";

}

}




if (!$result) {

$msg = rtrim($msg);

$msg = "You should use tabs instead of spaces for indents. Following files violate this rule:\n$msg";

}




return $result;

}




/**

* Check if there are trailing spaces in files

*

* @param string $msg error messages placeholder

* @param array $filetypes array of file types which should be tested

* @return bool

*/

protected function _testTrailingSpaces(&$msg, array $filetypes = array()) {

$result = true;

$files = $this->_getChangedFiles($filetypes);

foreach ($files as $file => $extension) {

$content = $this->_getFileContent($file);




// check if file contains trailing spaces

$m = null;

$spacelines = preg_match_all('/\\h$/m', $content, $m);




if ($spacelines) {

$result = false;

$msg .= "\t[$file] Trailing spaces found on $spacelines lines\n";

}

}




if (!$result) {

$msg = rtrim($msg);

$msg = "Trailing spaces are not allowed. Following files violate this rule:\n$msg";

}




return $result;

}




/**

* Check if files have required svn properties set

*

* @param string $msg error messages placeholder

* @param array $proprules svn properties rules

* @return bool

*/

protected function _testSvnProperties(&$msg, array $proprules = array()) {

$result = true;

$files = $this->_getChangedFiles(array_keys($proprules));

foreach ($files as $file => $extension) {

$props = $this->_getFileProps($file);

foreach ($proprules[$extension] as $proprule) {

if (!in_array($proprule, $props)) {

$result = false;

$msg .= "\t[$file]\n";

break;

}

}

}




if (!$result) {

$rules = '';

foreach ($proprules as $filetype => $rule) {

$rule = "*.$filetype = " . implode(',', $rule);

$rules .= "\t$rule\n";

}

$rules = rtrim($rules);




$msg = rtrim($msg);

$msg = "Some files are missing required svn properties.\n"

. "\t= Rules =\n$rules\n"

. "\t= Files violating these rules =\n$msg";

}




return $result;

}




/**

* Check if files violate eol rules

*

* @param string $msg error messages placeholder

* @param array $eolrules

* @return bool

* @throws Exception

*/

protected function _testEol(&$msg, array $eolrules = array()) {

$patterns = array(

'CR' => '(?:\\x0d(?!\\x0a))',

'LF' => '(?:(?<!\\x0d)\\x0a)',

'CRLF' => '(?:\\x0d\\x0a)',

);




$result = true;

$files = $this->_getChangedFiles(array_keys($eolrules));

foreach ($files as $file => $extension) {

$content = $this->_getFileContent($file);

$rules = explode(',', $eolrules[$extension]);




// create reqular expression for checking eol violations

foreach ($rules as $eol) {

$eol = strtoupper(trim($eol));

if (!isset($patterns[$eol])) {

throw new Exception("Unknown EOL: $eol");

}

unset($patterns[$eol]);

}




$m = null;

$pattern = '/' . implode('|', $patterns) . '/';

$badlines = preg_match_all($pattern, $content, $m);




if ($badlines) {

$result = false;

$msg .= "\t[$file] Not allowed EOL characters found on $badlines lines\n";

}

}




if (!$result) {

$msg = rtrim($msg);

$msg = "There is some files violating EOL rules:\n$msg";

}




return $result;

}




/**

* Tests if the committed files pass PHP syntax checking

*

* @param string $msg error messages placeholder

* @param array $filetypes array of file types which should be tested

* @return bool

*/

protected function _testPHPSyntax(&$msg, array $filetypes = array()) {

$result = true;

$files = $this->_getChangedFiles($filetypes);

$tempDir = sys_get_temp_dir();

foreach ($files as $file => $extension) {

$content = $this->_getFileContent($file);

$tempfile = tempnam($tempDir, "stax_");

file_put_contents($tempfile, $content);

$tempfile = realpath($tempfile); //sort out the formatting of the filename

$output = shell_exec(self::$PHP . ' -l "' . $tempfile . '"');

//try to find a parse error text and chop it off

$syntaxErrorMsg = preg_replace("/Errors parsing.*$/", "", $output, -1, $count);

if ($count > 0) { //found errors

$result = false;

$syntaxErrorMsg = str_replace($tempfile, $file, $syntaxErrorMsg); //replace temp filename with real filename

$msg .= "\t[$file] PHP Syntax error in file. Message: $syntaxErrorMsg\n";

}

unlink($tempfile);

}

return $result;

}




/**

* extension -> standard

*/

protected function _testPHPCodeSniffer(&$msg, array $csrules = array()) {

$result = true;

$files = $this->_getChangedFiles(array_keys($csrules));

$tempDir = sys_get_temp_dir();

foreach ($files as $file => $extension) {




$explodedFileName = explode('.', $file);

$length = count($explodedFileName);




if ($length > 2) {

if (in_array($explodedFileName[$length - 1], array('php'))

&& in_array($explodedFileName[$length - 2], array('tpl', 'min'))

) {

continue;

}

}




if (!in_array($extension, array_keys($csrules))) {

continue;

}




$standard = strtolower($csrules[$extension]);




$content = $this->_getFileContent($file);

$tempfile = tempnam($tempDir, "stax_");

file_put_contents($tempfile, $content);

$tempfile = realpath($tempfile);




switch ($standard) {

case 'intermesh':

$output = shell_exec(

self::$PHPCS . ' -n --standard=/usr/share/php/PHP/CodeSniffer/Standards/Intermesh/ruleset.xml '

. $tempfile

);

break;

default:

$output = shell_exec(self::$PHPCS . ' -n --standard=PEAR ' . $tempfile);

}




$count = preg_match_all("/ERROR/", $output, $matches);




if ($count > 1) {

$result = false;

$syntaxErrorMsg = str_replace($tempfile, $file, $output);

$msg .= "\t[$file] PHP CodeSmoffer error in file. Message: $syntaxErrorMsg\n";

}

unlink($tempfile);

}

return $result;

}




}

创建文件:

/ usr / local / svn / testrepository / hooks / pre-commit

#!/usr/bin/php

<?php

require '/usr/local/svn/CommitTests.php';

putenv('PATH=/usr/bin');




new CommitTests($argv[1], $argv[2], array(

'LogMessageLength' => array(3),

'TabIndents' => array(array('php', 'php4', 'php5')),

//'TrailingSpaces' => array(array('php', 'php4', 'php5', 'ini')),

'SvnProperties' => array(array(

'php' => array('svn:keywords=Author Id Revision', 'svn:eol-style=LF'),

'php4' => array('svn:keywords=Author Id Revision', 'svn:eol-style=LF'),

'php5' => array('svn:keywords=Author Id Revision', 'svn:eol-style=LF'),

'html' => array('svn:eol-style=LF'),

'js' => array('svn:eol-style=LF'),

)),

'IECommaBug'=>array(),

// 'PHPCodeSniffer'=>array(array(

// 'php' => '',

// )),

'PHPSyntax'=>array(array('php')),

));

设置正确的文件权限。

$ chown -R svn:svn /usr/local/svn
$ chmod u+x /usr/local/svn/testrepository/hooks/pre-commit

Subversion客户端配置

我们的预提交钩子需要在文件上设置某些svn属性。 我们可以修改客户端自动执行此操作。 如果您使用Linux,您可以在主目录中编辑“.subversion / config”,并添加或取消注释:

enable-auto-props = yes

在[auto-props]部分文件中添加:

*.php = svn:keywords=Author Id Revision;svn:eol-style=LF

*.js = svn:keywords=Author Id Revision;svn:eol-style=LF

这样可以确保所有文件都具有Linux样式的换行符。 作者,编号和版本标签对于添加到文档块很有用。

设置现有文件所需的属性

修改现有存储库上的预提交钩子时,可能还没有设置这些属性的文件。 您可以在命令行中更改属性,如下所示:

$ svn propset svn:keywords “Author Id Revision” Example.php
$ svn propset svn: eol-style “LF” Example.php

使用第二个命令可能会遇到错误。 该文件可能有不同的换行符或不同的换行符的混合。 在这种情况下,您可以使用dos2unix将它们转换为linux换行符:

$ apt-get install dos2unix
$ dos2unix Example.php

存储库结构

我将描述我们使用我们的存储库的方式。 您可能需要采用不同的方法,但可能会帮助您。 在根我们创建:

- trunk
- branches
- stable-1.0
- stable-1.1
- tags
- stable-release-1.0.1
- stable-release-1.1.1

干线是一切开始的原始发展路线。 在某些时候,现在是发布稳定版本的时候了。 然后我们在“branches”文件夹中创建一个trunk的副本。 这就是我们所说的稳定分支。 以下命令将这样做:

$ svn cp svn+ssh://svn@svn.example.com/testrepository/trunk svn+ssh://svn@svn.example.com/testrepository/branches/stable-1.0 -m “Creating stable 1.0 branch from trunk”

现在我们可以做一些更改来发布一个版本或者一些bug修复。 当我们觉得代码准备发布时,我们可以创建一个“标签”。

$ svn cp svn+ssh://svn@svn.example.com/testrepository/branches/stable-1.0 svn+ssh://svn@svn.example.com/testrepository/tags/stable-release-1.0.1 -m “Creating stable release 1.0.1”

标签永远不会被修改!

当bug报告进来时,我们会将它们修复到stable-1.0分支中,并在更新发布时创建一个新的标签。

合并

稳定1.0中的错误修复应该也可以返回到中继。 我们可以通过将分支更改合并到主干中来实现。

进入中继线工作副本或签出副本:

$ svn co svn+ssh://svn@svn.example.com/testrepository/trunk

然后进入工作副本并输入合并命令:

$ svn merge svn+ssh://svn@svn.example.com/testrepository/branches/stable-1.0

这将对树干应用所有更改。 您可能需要解决一些冲突。 最终你可以提交合并:

$ svn commit -m 'merged revision 1 to 3 from stable-1.0' branch.

结束

这应该让你开始使用你自己的subversion版本库。 我希望这是有帮助的!

赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏