From 0eeef349084d8ac31c9a04ef8dc763ea4dbc1fa6 Mon Sep 17 00:00:00 2001 From: Benjamin Jorand Date: Mon, 8 Dec 2008 09:56:31 +0100 Subject: [PATCH] Added the Mercurial repository serving synchronization. This fixes ticket 79. --- INSTALL.mdtext | 7 +- doc/syncmercurial.mdtext | 64 +++++++++ scripts/SyncMercurial.sh | 10 ++ src/IDF/Plugin/SyncMercurial.php | 217 +++++++++++++++++++++++++++++++ src/IDF/relations.php | 8 ++ 5 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 doc/syncmercurial.mdtext create mode 100644 scripts/SyncMercurial.sh create mode 100644 src/IDF/Plugin/SyncMercurial.php diff --git a/INSTALL.mdtext b/INSTALL.mdtext index 9b07b0e..728c701 100644 --- a/INSTALL.mdtext +++ b/INSTALL.mdtext @@ -101,9 +101,12 @@ To upgrade: $ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d -u $ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d -## Subversion Repository Synchronization +## Repository Synchronization -The documentation is available in the `doc/syncsvn.mdtext` file. +The documentation is available in the `doc` folder. + +* Subversion: `doc/syncsvn.mdtext`. +* Mercurial: `doc/syncmercurial.mdtext`. ## For the Apache Webserver Users diff --git a/doc/syncmercurial.mdtext b/doc/syncmercurial.mdtext new file mode 100644 index 0000000..5871522 --- /dev/null +++ b/doc/syncmercurial.mdtext @@ -0,0 +1,64 @@ +# Plugin SyncMercurial by Benjamin Jorand + +The SyncMercurial plugin allows the direct creation and synchronisation of +mercurial repositories with the InDefero database. The repositories will be +published by hgwebdir.cgi using HTTP. It also handles private repositories. + +SyncMercurial is adapted from SyncSvn by Baptiste Michaud. + +## To Contact the Author + + Benjamin Jorand + +## Apache configuration + +The simple way to share Mercurial repositories is to publish them +using HTTP and `hgwebdir.cgi`. + +It first requires a config file called hgweb.config in the same +directory where you put hgwebdir.cgi (for example, +`/home/indefero/scripts`): + + [collections] + /home/indefero/repositories/mercurial/ = /home/indefero/repositories/mercurial/ + +Then configure a vhost this way : + + ScriptAliasMatch ^/hg(.*) /home/indefero/scripts/hgwebdir.cgi$1 + + Options +ExecCGI + AuthName "Restricted" + AuthType Basic + AuthUserFile /home/indefero/auth/.htpasswd + + Require valid-user + + + +Enable the authentification for private repositories : + + Include /home/indefero/scripts/private_indefero.conf + +## InDefero configuration + +First, you need to install the File_Passwd PEAR package: + + $ sudo pear install File_Passwd + +Then, based on the paths provided in the Apache configuration, you +need to put the following lines in your configuration file: + + $cfg['idf_plugin_syncmercurial_passwd_file'] = '/home/indefero/auth/.htpasswd'; + $cfg['idf_plugin_syncmercurial_path'] = '/home/indefero/repositories/mercurial'; + $cfg['idf_plugin_syncmercurial_private_include'] = '/home/indefero/scripts/private_indefero.conf'; + $cfg['idf_plugin_syncmercurial_private_notify'] = '/home/indefero/tmp/notify.tmp'; + $cfg['idf_plugin_syncmercurial_private_url'] = '/hg/%s'; + +## Cron configuration + +As InDefero modifies the private_indefero.conf, apache needs to be reloaded. +Each time this file is modified, a temporary file is created. + + */5 * * * * /bin/sh /home/indefero/src/cron/SyncMercurial.sh + +Edit this script and add correct values to `private_notify` and `reload_cmd`. diff --git a/scripts/SyncMercurial.sh b/scripts/SyncMercurial.sh new file mode 100644 index 0000000..888fe15 --- /dev/null +++ b/scripts/SyncMercurial.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +private_notify="/home/indefero/tmp/notify.tmp" +reload_cmd="/usr/sbin/apachectl -k graceful" + +if [ -e $private_notify ]; then + $reload_cmd + rm -f $private_notify +fi + diff --git a/src/IDF/Plugin/SyncMercurial.php b/src/IDF/Plugin/SyncMercurial.php new file mode 100644 index 0000000..d599bfa --- /dev/null +++ b/src/IDF/Plugin/SyncMercurial.php @@ -0,0 +1,217 @@ +processMercurialCreate($params['project']); + break; + case 'IDF_Project::membershipsUpdated': + $plug->processSyncAuthz($params['project']); + break; + case 'Pluf_User::passwordUpdated': + $plug->processSyncPasswd($params['user']); + break; + } + } + + /** + * Run hg init command to create the corresponding Mercurial + * repository. + * + * @param IDF_Project + * @return bool Success + */ + function processMercurialCreate($project) + { + $shortname = $project->shortname; + + if (false===($mercurial_path=Pluf::f('idf_plugin_syncmercurial_path',false))) { + throw new Pluf_Exception_SettingError("'idf_plugin_syncmercurial_path' must be defined in your configuration file."); + } + + if (file_exists($mercurial_path.'/'.$shortname)) { + throw new Exception(sprintf(__('The repository %s already exists.'), + $mercurial_path.'/'.$shortname)); + } + $return = 0; + $output = array(); + $cmd = sprintf('hg init %s', + escapeshellarg($mercurial_path.'/'.$shortname)); + $ll = exec($cmd, $output, $return); + return ($return == 0); + } + + /** + * Synchronise an user's password. + * + * @param Pluf_User + */ + function processSyncPasswd($user) + { + $passwd_file = Pluf::f('idf_plugin_syncmercurial_passwd_file'); + if (!file_exists($passwd_file) or !is_writable($passwd_file)) { + return false; + } + $ht = new File_Passwd_Authbasic($passwd_file); + $ht->load(); + $ht->setMode(FILE_PASSWD_SHA); + if ($ht->userExists($user->login)) { + $ht->changePasswd($user->login, $this->getMercurialPass($user)); + } else { + $ht->addUser($user->login, $this->getMercurialPass($user)); + } + $ht->save(); + return true; + } + + /** + * Synchronize the hgrc file and the passwd file for the project. + * + * @param IDF_Project + */ + function processSyncAuthz($project) + { + $this->SyncAccess($project); + $this->generateProjectPasswd($project); + } + + /** + * Get the repository password for the user + */ + function getMercurialPass($user){ + return substr(sha1($user->password.Pluf::f('secret_key')), 0, 8); + } + + /** + * For a particular project: update all passwd information + */ + function generateProjectPasswd($project) + { + $passwd_file = Pluf::f('idf_plugin_syncmercurial_passwd_file'); + if (!file_exists($passwd_file) or !is_writable($passwd_file)) { + throw new Exception (sprintf(__('%s does not exist or is not writable.'), $passwd_file)); + } + $ht = new File_Passwd_Authbasic($passwd_file); + $ht->setMode(FILE_PASSWD_SHA); + $ht->load(); + $mem = $project->getMembershipData(); + $members = array_merge((array)$mem['members'], (array)$mem['owners'], + (array)$mem['authorized']); + foreach($members as $user) { + if ($ht->userExists($user->login)) { + $ht->changePasswd($user->login, $this->getMercurialPass($user)); + } else { + $ht->addUser($user->login, $this->getMercurialPass($user)); + } + } + $ht->save(); + } + + /** + * Generate the hgrc file + */ + function SyncAccess($project) + { + $shortname = $project->shortname; + $hgrc_file = Pluf::f('idf_plugin_syncmercurial_path').sprintf('/%s/.hg/hgrc', $shortname); + + // Get allow_push list + $allow_push = ''; + $mem = $project->getMembershipData(); + foreach ($mem['owners'] as $v) { + $allow_push .= $v->login.' '; + } + foreach ($mem['members'] as $v) { + $allow_push .= $v->login.' '; + } + + // Generate hgrc content + if (is_file($hgrc_file)) { + $tmp_content = parse_ini_file($hgrc_file, true); + $tmp_content['web']['allow_push'] = $allow_push; + } + else { + $tmp_content = Pluf::f('idf_plugin_syncmercurial_hgrc'); + $tmp_content['web']['allow_push'] = $allow_push; + } + $fcontent = ''; + foreach ($tmp_content as $key => $elem){ + $fcontent .= '['.$key."]\n"; + foreach ($elem as $key2 => $elem2){ + $fcontent .= $key2.' = '.$elem2."\n"; + } + } + file_put_contents($hgrc_file, $fcontent, LOCK_EX); + + // Generate private repository config file + $private_file = Pluf::f('idf_plugin_syncmercurial_private_include'); + $notify_file = Pluf::f('idf_plugin_syncmercurial_private_notify'); + $fcontent = ''; + foreach (Pluf::factory('IDF_Project')->getList() as $project) { + $conf = new IDF_Conf(); + $conf->setProject($project); + if ($project->private == true){ + $mem = $project->getMembershipData(); + $user = ''; + foreach ($mem['owners'] as $v) { + $user .= $v->login.' '; + } + foreach ($mem['members'] as $v) { + $user .= $v->login.' '; + } + foreach ($mem['authorized'] as $v) { + $user .= $v->login.' '; + } + $fcontent .= 'shortname).'>'."\n"; + $fcontent .= 'AuthType Basic'."\n"; + $fcontent .= 'AuthName "Restricted"'."\n"; + $fcontent .= sprintf('AuthUserFile %s', Pluf::f('idf_plugin_syncmercurial_passwd_file'))."\n"; + $fcontent .= sprintf('Require user %s', $user)."\n"; + $fcontent .= ''."\n\n"; + } + } + file_put_contents($private_file, $fcontent, LOCK_EX); + file_put_contents($notify_file, ' ', LOCK_EX); + return true; + } +} diff --git a/src/IDF/relations.php b/src/IDF/relations.php index fdb412c..4e0ef39 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -52,4 +52,12 @@ Pluf_Signal::connect('IDF_Project::created', Pluf_Signal::connect('Pluf_User::passwordUpdated', array('IDF_Plugin_SyncSvn', 'entry')); +# +# Mercurial synchronization +Pluf_Signal::connect('IDF_Project::membershipsUpdated', + array('IDF_Plugin_SyncMercurial', 'entry')); +Pluf_Signal::connect('IDF_Project::created', + array('IDF_Plugin_SyncMercurial', 'entry')); +Pluf_Signal::connect('Pluf_User::passwordUpdated', + array('IDF_Plugin_SyncMercurial', 'entry')); return $m;