diff --git a/doc/readme-monotone.mdtext b/doc/readme-monotone.mdtext index 2e54367..cbaf8ba 100644 --- a/doc/readme-monotone.mdtext +++ b/doc/readme-monotone.mdtext @@ -3,9 +3,8 @@ ## general This version of indefero contains an implementation of the monotone - automation interface. It needs at least monotone version 0.47 - (interface version 12.0) or newer, but as development continues, its - likely that this dependency has to be raised. + automation interface. It needs at least monotone version 0.99 + (interface version 13.0) or newer. To set up a new IDF project with monotone quickly, all you need to do is to create a new monotone database with diff --git a/scripts/mtn-post-push b/scripts/mtn-post-push new file mode 100755 index 0000000..46f5d3b --- /dev/null +++ b/scripts/mtn-post-push @@ -0,0 +1,23 @@ +#!/bin/sh +# +# This hook informs IDF that new revisions arrived in the database +# of the specified project. +# +# This hook is normally installed automatically at the creation of your +# repository if you have everything configured correctly. If you want +# to enable it later, you need to call it into your monotonerc file +# from the hook "note_netsync_end". (See chapter "Event Notifications +# and Triggers" on .) +# + +dir=$(dirname "$0") +res=$(cd "$dir" && /bin/pwd || "$dir") +SCRIPTDIR="$res/$(readlink $0)" +PHP_POST_PUSH=$SCRIPTDIR/mtnpostpush.php + +base=$(basename "$0") +TMPFILE=$(mktemp /tmp/${tempfoo}.XXXXXX) || exit 1 +while read rev; do echo $rev >> $TMPFILE; done + +echo php $PHP_POST_PUSH "$1" \< $TMPFILE \&\& rm -f $TMPFILE |\ + at now > /dev/null 2>&1 diff --git a/scripts/mtnpostpush.php b/scripts/mtnpostpush.php new file mode 100644 index 0000000..0fc9e02 --- /dev/null +++ b/scripts/mtnpostpush.php @@ -0,0 +1,63 @@ + 'name-of-the-project', + * 'revisions' => array('123abc...', '456def...', ...)); + * + */ + +fwrite(STDERR, "waiting for revisions on STDIN...\n"); +$stdin = file_get_contents('php://stdin'); + +$params = array('project' => $argv[1], + 'revisions' => explode("\n", chop($stdin))); +Pluf_Signal::send('mtnpostpush.php::run', 'mtnpostpush.php', $params); + + + diff --git a/src/IDF/Key.php b/src/IDF/Key.php index 0d31ba3..c9d7eff 100644 --- a/src/IDF/Key.php +++ b/src/IDF/Key.php @@ -84,7 +84,7 @@ class IDF_Key extends Pluf_Model return array('ssh', $m[2], $m[1]); } - throw new IDF_Exception('invalid or unknown key data detected'); + throw new Exception('invalid or unknown key data detected'); } /** @@ -123,7 +123,7 @@ class IDF_Key extends Pluf_Model { list($type, $keyName, $keyData) = $this->parseContent(); if ($type != 'mtn') - throw new IDF_Exception('key is not a monotone public key'); + throw new Exception('key is not a monotone public key'); return sha1($keyName.":".$keyData); } diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php index cee2270..424cc01 100644 --- a/src/IDF/Middleware.php +++ b/src/IDF/Middleware.php @@ -111,7 +111,7 @@ function IDF_Middleware_ContextPreProcessor($request) $request->project); $c = array_merge($c, $request->rights); } - $c['usherConfigured'] = Pluf::f("mtn_usher", null) !== null; + $c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null; return $c; } diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php new file mode 100644 index 0000000..ecbb301 --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone.php @@ -0,0 +1,249 @@ +processMonotoneCreate($params['project']); + break; + case 'mtnpostpush.php::run': + $plug->processSyncTimeline($params); + break; + } + } + + /** + * Four steps to setup a new monotone project: + * + * 1) run mtn db init to initialize a new database underknees + * 'mtn_repositories' + * 2) create a new server key in the same directory + * 3) write monotonerc for access control + * 4) add the database as new local server in the usher configuration + * 5) reload the running usher instance so it acknowledges the new + * server + * + * @param IDF_Project + */ + function processMonotoneCreate($project) + { + if ($project->getConf()->getVal('scm') != 'mtn') { + return; + } + + $projecttempl = Pluf::f('mtn_repositories', false); + if ($projecttempl === false) { + throw new IDF_Scm_Exception( + '"mtn_repositories" must be defined in your configuration file.' + ); + } + + $usher_config = Pluf::f('mtn_usher_conf', false); + if (!$usher_config || !is_writable($usher_config)) { + throw new IDF_Scm_Exception( + '"mtn_usher_conf" does not exist or is not writable.' + ); + } + + $mtnpostpush = realpath(dirname(__FILE__) . "/../../../scripts/mtn-post-push"); + if (!file_exists($mtnpostpush)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not find mtn-post-push script "%s".'), $mtnpostpush + )); + } + + $shortname = $project->shortname; + $projectpath = sprintf($projecttempl, $shortname); + if (file_exists($projectpath)) { + throw new IDF_Scm_Exception(sprintf( + __('The project path %s already exists.'), $projectpath + )); + } + + if (!mkdir($projectpath)) { + throw new IDF_Scm_Exception(sprintf( + __('The project path %s could not be created.'), $projectpath + )); + } + + // + // step 1) create a new database + // + $dbfile = $projectpath.'/database.mtn'; + $cmd = sprintf( + Pluf::f('mtn_path', 'mtn').' db init -d %s', + escapeshellarg($dbfile) + ); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + $output = $return = null; + $ll = exec($cmd, $output, $return); + if ($return != 0) { + throw new IDF_Scm_Exception(sprintf( + __('The database file %s could not be created.'), $dbfile + )); + } + + // + // step 2) create a server key + // + // try to parse the key's domain part from the remote_url's host + // name, otherwise fall back to the configured Apache server name + $server = $_SERVER['SERVER_NAME']; + $remote_url = Pluf::f('mtn_remote_url'); + if (($parsed = parse_url($remote_url)) !== false && + !empty($parsed['host'])) { + $server = $parsed['host']; + } + + $keyname = $shortname.'-server@'.$server; + $cmd = sprintf( + Pluf::f('mtn_path', 'mtn').' au generate_key --confdir=%s %s ""', + escapeshellarg($projectpath), + escapeshellarg($keyname) + ); + + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + $output = $return = null; + $ll = exec($cmd, $output, $return); + if ($return != 0) { + throw new IDF_Scm_Exception(sprintf( + __('The server key %s could not be created.'), $keyname + )); + } + + // + // step 3) write monotonerc for access control + // FIXME: netsync access control is still missing! + // + $monotonerc = file_get_contents(dirname(__FILE__) . "/SyncMonotone/monotonerc.tpl"); + $monotonerc = str_replace( + array("%%MTNPOSTPUSH%%", "%%PROJECT%%"), + array($mtnpostpush, $shortname), + $monotonerc + ); + + $rcfile = $projectpath.'/monotonerc'; + + if (!file_put_contents($rcfile, $monotonerc, LOCK_EX)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write mtn configuration file "%s"'), $rcfile + )); + } + + // + // step 4) read in and append the usher config with the new server + // + $usher_rc = file_get_contents($usher_config); + $parsed_config = array(); + try { + $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc); + } + catch (Exception $e) { + throw new IDF_Scm_Exception(sprintf( + __('Could not parse usher configuration in "%s": %s'), + $usher_config, $e->getMessage() + )); + } + + // ensure we haven't configured a server with this name already + foreach ($parsed_config as $stanzas) + { + foreach ($stanzas as $stanza_line) + { + if ($stanza_line['key'] == 'server' && + $stanza_line['values'][0] == $shortname) + { + throw new IDF_Scm_Exception(sprintf( + __('usher configuration already contains a server '. + 'entry named "%s"'), + $shortname + )); + } + } + } + + $new_server = array( + array('key' => 'server', 'values' => array($shortname)), + array('key' => 'local', 'values' => array( + '--confdir', $projectpath, + '-d', $dbfile + )), + ); + + $parsed_config[] = $new_server; + $usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config); + + // FIXME: more sanity - what happens on failing writes? we do not + // have a backup copy of usher.conf around... + if (!file_put_contents($usher_config, $usher_rc, LOCK_EX)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write usher configuration file "%s"'), $usher_config + )); + } + + // + // step 5) reload usher to pick up the new configuration + // + IDF_Scm_Monotone_Usher::reload(); + } + + /** + * Update the timeline after a push + * + */ + public function processSyncTimeline($params) + { + $pname = $params['project']; + try { + $project = IDF_Project::getOr404($pname); + } catch (Pluf_HTTP_Error404 $e) { + Pluf_Log::event(array( + 'IDF_Plugin_SyncMonotone::processSyncTimeline', + 'Project not found.', + array($pname, $params) + )); + return false; // Project not found + } + + Pluf_Log::debug(array( + 'IDF_Plugin_SyncMonotone::processSyncTimeline', + 'Project found', $pname, $project->id + )); + IDF_Scm::syncTimeline($project, true); + Pluf_Log::event(array( + 'IDF_Plugin_SyncMonotone::processSyncTimeline', + 'sync', array($pname, $project->id) + )); + } +} diff --git a/src/IDF/Plugin/SyncMonotone/monotonerc.tpl b/src/IDF/Plugin/SyncMonotone/monotonerc.tpl new file mode 100644 index 0000000..6c74813 --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/monotonerc.tpl @@ -0,0 +1,71 @@ +-- ***** BEGIN LICENSE BLOCK ***** +-- This file is part of InDefero, an open source project management application. +-- Copyright (C) 2008 Céondo Ltd and contributors. +-- +-- InDefero is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation; either version 2 of the License, or +-- (at your option) any later version. +-- +-- InDefero is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +-- +-- ***** END LICENSE BLOCK ***** + +-- +-- controls the access rights for remote_stdio which is used by IDFs frontend +-- +function get_remote_automate_permitted(key_identity, command, options) + local read_only_commands = { + "get_corresponding_path", "get_content_changed", "tags", "branches", + "common_ancestors", "packet_for_fdelta", "packet_for_fdata", + "packets_for_certs", "packet_for_rdata", "get_manifest_of", + "get_revision", "select", "graph", "children", "parents", "roots", + "leaves", "ancestry_difference", "toposort", "erase_ancestors", + "descendents", "ancestors", "heads", "get_file_of", "get_file", + "interface_version", "get_attributes", "content_diff", + "file_merge", "show_conflicts", "certs", "keys" + } + + for _,v in ipairs(read_only_commands) do + if (v == command[1]) then + return true + end + end + + return false +end + +_idf_revs = {} +function note_netsync_start(session_id) + _idf_revs[session_id] = {} +end + +function note_netsync_revision_received(new_id, revision, certs, session_id) + table.insert(_idf_revs[session_id], new_id) +end + +function note_netsync_end (session_id, ...) + if table.getn(_idf_revs[session_id]) == 0 then + return + end + + local pin,pout,pid = spawn_pipe("%%MTNPOSTPUSH%%", "%%PROJECT%%"); + if pid == -1 then + print("could execute %%MTNPOSTPUSH%%") + return + end + + for _,r in ipairs(_idf_revs[session_id]) do + pin:write(r .. "\n") + end + pin:close() + + wait(pid) +end diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index dd3679f..f8c898f 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -22,6 +22,7 @@ # ***** END LICENSE BLOCK ***** */ require_once(dirname(__FILE__) . "/Monotone/Stdio.php"); +require_once(dirname(__FILE__) . "/Monotone/BasicIO.php"); /** * Monotone scm class @@ -121,12 +122,6 @@ class IDF_Scm_Monotone extends IDF_Scm $branch = "*"; } - if (count($this->_resolveSelector("h:$branch")) == 0) { - throw new IDF_Scm_Exception( - "Branch $branch is empty" - ); - } - return $branch; } @@ -143,75 +138,6 @@ class IDF_Scm_Monotone extends IDF_Scm return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY); } - /** - * Parses monotone's basic_io format - * - * @param string $in - * @return array of arrays - */ - private static function _parseBasicIO($in) - { - $pos = 0; - $stanzas = array(); - - while ($pos < strlen($in)) { - $stanza = array(); - while ($pos < strlen($in)) { - if ($in[$pos] == "\n") break; - - $stanzaLine = array('key' => '', 'values' => array(), 'hash' => null); - while ($pos < strlen($in)) { - $ch = $in[$pos]; - if ($ch == '"' || $ch == '[') break; - ++$pos; - if ($ch == ' ') continue; - $stanzaLine['key'] .= $ch; - } - - if ($in[$pos] == '[') { - ++$pos; // opening square bracket - $stanzaLine['hash'] = substr($in, $pos, 40); - $pos += 40; - ++$pos; // closing square bracket - } - else - { - $valCount = 0; - while ($in[$pos] == '"') { - ++$pos; // opening quote - $stanzaLine['values'][$valCount] = ''; - while ($pos < strlen($in)) { - $ch = $in[$pos]; $pr = $in[$pos-1]; - if ($ch == '"' && $pr != '\\') break; - ++$pos; - $stanzaLine['values'][$valCount] .= $ch; - } - ++$pos; // closing quote - - if ($in[$pos] == ' ') { - ++$pos; // space - ++$valCount; - } - } - - for ($i = 0; $i <= $valCount; $i++) { - $stanzaLine['values'][$i] = str_replace( - array("\\\\", "\\\""), - array("\\", "\""), - $stanzaLine['values'][$i] - ); - } - } - - $stanza[] = $stanzaLine; - ++$pos; // newline - } - $stanzas[] = $stanza; - ++$pos; // newline - } - return $stanzas; - } - /** * Queries the certs for a given revision and returns them in an * associative array array("branch" => array("branch1", ...), ...) @@ -226,7 +152,7 @@ class IDF_Scm_Monotone extends IDF_Scm if (!array_key_exists($rev, $certCache)) { $out = $this->stdio->exec(array('certs', $rev)); - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); $certs = array(); foreach ($stanzas as $stanza) { $certname = null; @@ -290,7 +216,7 @@ class IDF_Scm_Monotone extends IDF_Scm 'get_content_changed', $startrev, $file )); - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); // FIXME: we only care about the first returned content mark // everything else seem to be very, very rare cases @@ -326,7 +252,7 @@ class IDF_Scm_Monotone extends IDF_Scm $out = $this->stdio->exec(array('tags')); $tags = array(); - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); foreach ($stanzas as $stanza) { $tagname = null; foreach ($stanza as $stanzaline) { @@ -375,7 +301,7 @@ class IDF_Scm_Monotone extends IDF_Scm )); $files = array(); - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); $folder = $folder == '/' || empty($folder) ? '' : $folder.'/'; foreach ($stanzas as $stanza) { @@ -525,7 +451,7 @@ class IDF_Scm_Monotone extends IDF_Scm )); $files = array(); - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); foreach ($stanzas as $stanza) { if ($stanza[0]['key'] == 'format_version') @@ -666,7 +592,7 @@ class IDF_Scm_Monotone extends IDF_Scm )); $newAndPatchedFiles = 0; - $stanzas = self::_parseBasicIO($out); + $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); foreach ($stanzas as $stanza) { if ($stanza[0]['key'] == 'patch' || $stanza[0]['key'] == 'add_file') diff --git a/src/IDF/Scm/Monotone/BasicIO.php b/src/IDF/Scm/Monotone/BasicIO.php new file mode 100644 index 0000000..0562c9b --- /dev/null +++ b/src/IDF/Scm/Monotone/BasicIO.php @@ -0,0 +1,162 @@ + + */ +class IDF_Scm_Monotone_BasicIO +{ + /** + * Parses monotone's basic_io format + * + * @param string $in + * @return array of arrays + */ + public static function parse($in) + { + $pos = 0; + $stanzas = array(); + + while ($pos < strlen($in)) { + $stanza = array(); + while ($pos < strlen($in)) { + if ($in[$pos] == "\n") break; + + $stanzaLine = array('key' => '', 'values' => array(), 'hash' => null); + while ($pos < strlen($in)) { + $ch = $in[$pos]; + if ($ch == '"' || $ch == '[') break; + ++$pos; + if ($ch == ' ') continue; + $stanzaLine['key'] .= $ch; + } + + if ($in[$pos] == '[') { + unset($stanzaLine['values']); + ++$pos; // opening square bracket + $stanzaLine['hash'] = substr($in, $pos, 40); + $pos += 40; + ++$pos; // closing square bracket + } + else + { + unset($stanzaLine['hash']); + $valCount = 0; + while ($in[$pos] == '"') { + ++$pos; // opening quote + $stanzaLine['values'][$valCount] = ''; + while ($pos < strlen($in)) { + $ch = $in[$pos]; $pr = $in[$pos-1]; + if ($ch == '"' && $pr != '\\') break; + ++$pos; + $stanzaLine['values'][$valCount] .= $ch; + } + ++$pos; // closing quote + + if ($in[$pos] == ' ') { + ++$pos; // space + ++$valCount; + } + } + + for ($i = 0; $i <= $valCount; $i++) { + $stanzaLine['values'][$i] = str_replace( + array("\\\\", "\\\""), + array("\\", "\""), + $stanzaLine['values'][$i] + ); + } + } + + $stanza[] = $stanzaLine; + ++$pos; // newline + } + $stanzas[] = $stanza; + ++$pos; // newline + } + return $stanzas; + } + + /** + * Compiles monotone's basicio format + * + * @param array $in Array of arrays + * @return string + */ + public static function compile($in) + { + $out = ""; + $first = true; + foreach ((array)$in as $sx => $stanza) { + if ($first) + $first = false; + else + $out .= "\n"; + + $maxkeylength = 0; + foreach ((array)$stanza as $lx => $line) { + if (!array_key_exists('key', $line)) { + throw new IDF_Scm_Exception( + '"key" not found in basicio stanza '.$sx.', line '.$lx + ); + } + $maxkeylength = max($maxkeylength, strlen($line['key'])); + } + + foreach ((array)$stanza as $lx => $line) { + $out .= str_pad($line['key'], $maxkeylength, ' ', STR_PAD_LEFT); + + if (array_key_exists('hash', $line)) { + $out .= ' ['.$line['hash'].']'; + } else + if (array_key_exists('values', $line)) { + if (!is_array($line['values']) || count($line['values']) == 0) { + throw new IDF_Scm_Exception( + '"values" must be an array of a size >= 1 '. + 'in basicio stanza '.$sx.', line '.$lx + ); + } + foreach ($line['values'] as $value) { + $out .= ' "'.str_replace( + array("\\", "\""), + array("\\\\", "\\\""), + $value).'"'; + } + } + else + { + throw new IDF_Scm_Exception( + 'neither "hash" nor "values" found in basicio '. + 'stanza '.$sx.', line '.$lx + ); + } + + $out .= "\n"; + } + } + return $out; + } +} + diff --git a/src/IDF/Scm/Monotone/Stdio.php b/src/IDF/Scm/Monotone/Stdio.php index 391133a..b447955 100644 --- a/src/IDF/Scm/Monotone/Stdio.php +++ b/src/IDF/Scm/Monotone/Stdio.php @@ -146,8 +146,9 @@ class IDF_Scm_Monotone_Stdio return false; $read = array($this->pipes[1], $this->pipes[2]); + $write = $except = null; $streamsChanged = stream_select( - $read, $write = null, $except = null, 0, 20000 + $read, $write, $except, 0, 20000 ); if ($streamsChanged === false) { diff --git a/src/IDF/Scm/Monotone/Usher.php b/src/IDF/Scm/Monotone/Usher.php index 9891555..5e55e08 100644 --- a/src/IDF/Scm/Monotone/Usher.php +++ b/src/IDF/Scm/Monotone/Usher.php @@ -21,6 +21,8 @@ # # ***** END LICENSE BLOCK ***** */ +require_once(dirname(__FILE__) . "/BasicIO.php"); + /** * Connects with the admininistrative interface of usher, * the monotone proxy. This class contains only static methods because @@ -190,40 +192,60 @@ class IDF_Scm_Monotone_Usher private static function _triggerCommand($cmd) { - $uc = Pluf::f('mtn_usher'); - if (empty($uc['host'])) { + $uc = Pluf::f('mtn_usher_conf', false); + if (!$uc || !is_readable($uc)) { + throw new IDF_Scm_Exception( + '"mtn_usher_conf" is not configured or not readable' + ); + } + + $parsed_config = + IDF_Scm_Monotone_BasicIO::parse(file_get_contents($uc)); + $host = $port = $user = $pass = null; + foreach ($parsed_config as $stanza) { + foreach ($stanza as $line) { + if ($line['key'] == 'adminaddr') { + list($host, $port) = explode(":", @$line['values'][0]); + break; + } + if ($line['key'] == 'userpass') { + $user = @$line['values'][0]; + $pass = @$line['values'][1]; + } + } + } + + if (empty($host)) { throw new IDF_Scm_Exception('usher host is empty'); } - if (!preg_match('/^\d+$/', $uc['port']) || - $uc['port'] == 0) + if (!preg_match('/^\d+$/', $port)) { throw new IDF_Scm_Exception('usher port is invalid'); } - if (empty($uc['user'])) { + if (empty($user)) { throw new IDF_Scm_Exception('usher user is empty'); } - if (empty($uc['pass'])) { + if (empty($pass)) { throw new IDF_Scm_Exception('usher pass is empty'); } - $sock = @fsockopen($uc['host'], $uc['port'], $errno, $errstr); + $sock = @fsockopen($host, $port, $errno, $errstr); if (!$sock) { throw new IDF_Scm_Exception( "could not connect to usher: $errstr ($errno)" ); } - fwrite($sock, 'USERPASS '.$uc['user'].' '.$uc['pass'].'\n'); + fwrite($sock, 'USERPASS '.$user.' '.$pass."\n"); if (feof($sock)) { throw new IDF_Scm_Exception( - 'usher closed the connection - probably wrong admin '. - 'username or password' + 'usher closed the connection - this should not happen' ); } - fwrite($sock, $cmd.'\n'); + fwrite($sock, $cmd."\n"); $out = ''; while (!feof($sock)) { $out .= fgets($sock); @@ -232,7 +254,7 @@ class IDF_Scm_Monotone_Usher $out = rtrim($out); if ($out == 'unknown command') { - throw new IDF_Scm_Exception("unknown command: $cmd"); + throw new IDF_Scm_Exception('unknown command: '.$cmd); } return $out; diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index 37d9ca9..18d79a2 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -181,7 +181,7 @@ class IDF_Views_Source $request_file)); return new Pluf_HTTP_Response_Redirect($url, 301); } - if (!$scm->isValidRevision($commit, $request_file)) { + if (!$scm->isValidRevision($commit)) { // Redirect to the first branch return new Pluf_HTTP_Response_Redirect($fburl); } diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 390ce36..9339a5a 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -73,14 +73,16 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git'; $cfg['svn_repositories'] = 'file:///home/svn/repositories/%s'; $cfg['svn_remote_url'] = 'http://localhost/svn/%s'; -# Path to the monotone binary +# Path to the monotone binary (you need mtn 0.99 or newer) $cfg['mtn_path'] = 'mtn'; # Additional options for the started monotone process -$cfg['mtn_opts'] = array('--no-workspace', '--norc'); +$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles', '--key='); # -# You can setup monotone for use with indefero in two ways: +# You can setup monotone for use with indefero in several ways. The +# two most-used should be: # # 1) One database for everything: +# # Set 'mtn_repositories' below to a fixed database path, such as # '/home/mtn/repositories/all_projects.mtn' # @@ -94,26 +96,39 @@ $cfg['mtn_opts'] = array('--no-workspace', '--norc'); # the database # # 2) One database for every project with 'usher': -# Set 'mtn_remote_url' below to a string which matches your setup. +# +# Download and configure 'usher' +# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher) +# which acts as proxy in front of all single project databases. +# Create a basic configuration file for it and add a secret admin +# username and password. Finally, point the below variable +# 'mtn_usher_conf' to this configuration file. +# +# Then set 'mtn_remote_url' below to a string which matches your setup. # Again, the '%s' placeholder will be expanded to the project's # short name. Note that 'mtn_remote_url' is used as internal # URI (to access the data for indefero) as well as external URI -# (for end users) at the same time. +# (for end users) at the same time. 'mtn_repositories' should then +# point to a directory where all project-related files (databases, +# keys, configurations) are kept, as these are automatically created +# on project creation by IDF. # -# Then download and configure 'usher' -# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher) -# which acts as proxy in front of all single project databases. -# Usher's server names should be mapped to the project's short names, -# so you end up with something like this for every project: +# Example: 'mtn_repositories' is configured to be '/var/monotone/%s' # -# server "project" -# local "-d" "/home/mtn/repositories/project.mtn" "*" +# - IDF tries to create /var/monotone/ as root directory +# - The database is placed in as /var/monotone//database.mtn +# - The server key is put into /var/monotone//keys and +# is named "-server@", where host is the host part +# of 'mtn_remote_url' # -# Alternatively if you assign every project a unique DNS such as -# 'project.my-hosting.biz', you can also configure it like this: +# therefor /var/monotone MUST be read/writable for the www user and all +# files which are created underknees MUST be read/writable by the user +# who is executing the usher instance! The best way to achieve this is with +# default (POSIX) ACLs on /var/monotone. # -# host "project.my-hosting.biz" -# local "-d" "/home/mtn/repositories/project.mtn" "*" +# +# You could also choose to setup usher by hand, i.e. with individual +# databases, in this case leave 'mtn_usher_conf' below commented out. # # Pro: - read and write access can be granted per project # - no database locking issues @@ -143,19 +158,11 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s'; $cfg['mtn_db_access'] = 'remote'; # # If configured, this allows basic control of a running usher process -# via the forge administration -# -# 'host' and 'port' must be set to the specific bits from usher's -# configured 'adminaddr', 'user' and 'pass' must match the values set for -# the configured 'userpass' combination -# -#$cfg['mtn_usher'] = array( -# 'host' => 'localhost', -# 'port' => 12345, -# 'user' => 'admin', -# 'pass' => 'admin', -#); +# via the forge administration. The variable must point to the full (writable) +# path of the usher configuration file which gets updated when new projects +# are added # +#$cfg['mtn_usher_conf'] = '/path/to/usher.conf'; # Mercurial repositories path #$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s'; diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 1d9b56f..b4f6a4c 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -386,7 +386,7 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#', 'model' => 'IDF_Views_Admin', 'method' => 'userUpdate'); -if (Pluf::f("mtn_usher", null) !== null) +if (Pluf::f("mtn_usher_conf", null) !== null) { $ctl[] = array('regex' => '#^/admin/usher/$#', 'base' => $base, diff --git a/src/IDF/relations.php b/src/IDF/relations.php index f5a5925..6734515 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -49,51 +49,58 @@ Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers' # -- Standard plugins, they will run only if configured -- # # Subversion synchronization -Pluf_Signal::connect('IDF_Project::membershipsUpdated', +Pluf_Signal::connect('IDF_Project::membershipsUpdated', array('IDF_Plugin_SyncSvn', 'entry')); -Pluf_Signal::connect('IDF_Project::created', +Pluf_Signal::connect('IDF_Project::created', array('IDF_Plugin_SyncSvn', 'entry')); -Pluf_Signal::connect('Pluf_User::passwordUpdated', +Pluf_Signal::connect('Pluf_User::passwordUpdated', array('IDF_Plugin_SyncSvn', 'entry')); -Pluf_Signal::connect('IDF_Project::preDelete', +Pluf_Signal::connect('IDF_Project::preDelete', array('IDF_Plugin_SyncSvn', 'entry')); -Pluf_Signal::connect('svnpostcommit.php::run', +Pluf_Signal::connect('svnpostcommit.php::run', array('IDF_Plugin_SyncSvn', 'entry')); # # Mercurial synchronization -Pluf_Signal::connect('IDF_Project::membershipsUpdated', +Pluf_Signal::connect('IDF_Project::membershipsUpdated', array('IDF_Plugin_SyncMercurial', 'entry')); -Pluf_Signal::connect('IDF_Project::created', +Pluf_Signal::connect('IDF_Project::created', array('IDF_Plugin_SyncMercurial', 'entry')); -Pluf_Signal::connect('Pluf_User::passwordUpdated', +Pluf_Signal::connect('Pluf_User::passwordUpdated', array('IDF_Plugin_SyncMercurial', 'entry')); -Pluf_Signal::connect('hgchangegroup.php::run', +Pluf_Signal::connect('hgchangegroup.php::run', array('IDF_Plugin_SyncMercurial', 'entry')); # # Git synchronization -Pluf_Signal::connect('IDF_Project::membershipsUpdated', +Pluf_Signal::connect('IDF_Project::membershipsUpdated', array('IDF_Plugin_SyncGit', 'entry')); -Pluf_Signal::connect('IDF_Key::postSave', +Pluf_Signal::connect('IDF_Key::postSave', array('IDF_Plugin_SyncGit', 'entry')); -Pluf_Signal::connect('IDF_Project::created', +Pluf_Signal::connect('IDF_Project::created', array('IDF_Plugin_SyncGit', 'entry')); -Pluf_Signal::connect('IDF_Key::preDelete', +Pluf_Signal::connect('IDF_Key::preDelete', array('IDF_Plugin_SyncGit', 'entry')); -Pluf_Signal::connect('gitpostupdate.php::run', +Pluf_Signal::connect('gitpostupdate.php::run', array('IDF_Plugin_SyncGit', 'entry')); +# +# monotone synchronization +Pluf_Signal::connect('IDF_Project::created', + array('IDF_Plugin_SyncMonotone', 'entry')); +Pluf_Signal::connect('phppostpush.php::run', + array('IDF_Plugin_SyncMonotone', 'entry')); + # # -- Processing of the webhook queue -- -Pluf_Signal::connect('queuecron.php::run', +Pluf_Signal::connect('queuecron.php::run', array('IDF_Queue', 'process')); # -# Processing of a given webhook, the hook can be configured -# directly in the configuration file if a different solution +# Processing of a given webhook, the hook can be configured +# directly in the configuration file if a different solution # is required. -Pluf_Signal::connect('IDF_Queue::processItem', - Pluf::f('idf_hook_process_item', +Pluf_Signal::connect('IDF_Queue::processItem', + Pluf::f('idf_hook_process_item', array('IDF_Webhook', 'process'))); return $m; diff --git a/src/IDF/templates/idf/gadmin/usher/index.html b/src/IDF/templates/idf/gadmin/usher/index.html index c81c134..fa6041b 100644 --- a/src/IDF/templates/idf/gadmin/usher/index.html +++ b/src/IDF/templates/idf/gadmin/usher/index.html @@ -14,7 +14,7 @@ {$server.name} {$server.status} - {if preg_match("/ACTIVE|RUNNING|SLEEPING/", $server.status)} + {if preg_match("/ACTIVE|WAITING|RUNNING|SLEEPING/", $server.status)} {trans 'stop'} {elseif $server.status == "STOPPED"} diff --git a/src/IDF/templates/idf/source/changelog.html b/src/IDF/templates/idf/source/changelog.html index 971d3ba..8c56074 100644 --- a/src/IDF/templates/idf/source/changelog.html +++ b/src/IDF/templates/idf/source/changelog.html @@ -12,12 +12,12 @@ {foreach $changes as $change} {aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $change.scm_id)} -{$change.creation_dtime|dateago:"wihtout"} +{$change.creation_dtime|dateago:"without"} {issuetext $change.summary, $request}{if $change.fullmessage}

{issuetext $change.fullmessage, $request, true, false, true, true, true}{/if} -
{trans 'Commit'} {$change.scm_id}, +
{trans 'Commit'} {$change.scm_id}, {trans 'by'} {showuser $change.get_author(), $request, $change.origauthor}