Merge branch 'master' of git://projects.ceondo.com/indefero
This commit is contained in:
commit
f4dbabe8de
@ -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
|
||||
|
23
scripts/mtn-post-push
Executable file
23
scripts/mtn-post-push
Executable file
@ -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 <http://monotone.ca/docs/Hooks.html#Hooks>.)
|
||||
#
|
||||
|
||||
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
|
63
scripts/mtnpostpush.php
Normal file
63
scripts/mtnpostpush.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008-2010 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 ***** */
|
||||
|
||||
/**
|
||||
* This script will send the notifications after a push in your
|
||||
* repository.
|
||||
*/
|
||||
|
||||
require dirname(__FILE__).'/../src/IDF/conf/path.php';
|
||||
require 'Pluf.php';
|
||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||
|
||||
/**
|
||||
* [signal]
|
||||
*
|
||||
* mtnpostpush.php::run
|
||||
*
|
||||
* [sender]
|
||||
*
|
||||
* mtnpostpush.php
|
||||
*
|
||||
* [description]
|
||||
*
|
||||
* This signal allows an application to perform a set of tasks
|
||||
* after a push to a monotone repository.
|
||||
*
|
||||
* [parameters]
|
||||
*
|
||||
* array('project' => '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);
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
249
src/IDF/Plugin/SyncMonotone.php
Normal file
249
src/IDF/Plugin/SyncMonotone.php
Normal file
@ -0,0 +1,249 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2010 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 ***** */
|
||||
|
||||
/**
|
||||
* This classes is a plugin which allows to synchronise access rights
|
||||
* between indefero and monotone usher setups.
|
||||
*/
|
||||
class IDF_Plugin_SyncMonotone
|
||||
{
|
||||
/**
|
||||
* Entry point of the plugin.
|
||||
*/
|
||||
static public function entry($signal, &$params)
|
||||
{
|
||||
$plug = new IDF_Plugin_SyncMonotone();
|
||||
switch ($signal) {
|
||||
case 'IDF_Project::created':
|
||||
$plug->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)
|
||||
));
|
||||
}
|
||||
}
|
71
src/IDF/Plugin/SyncMonotone/monotonerc.tpl
Normal file
71
src/IDF/Plugin/SyncMonotone/monotonerc.tpl
Normal file
@ -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
|
@ -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')
|
||||
|
162
src/IDF/Scm/Monotone/BasicIO.php
Normal file
162
src/IDF/Scm/Monotone/BasicIO.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2010 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 ***** */
|
||||
|
||||
/**
|
||||
* Utility class to parse and compile basic_io stanzas
|
||||
*
|
||||
* @author Thomas Keller <me@thomaskeller.biz>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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/<projectname> as root directory
|
||||
# - The database is placed in as /var/monotone/<projectname>/database.mtn
|
||||
# - The server key is put into /var/monotone/<projectname>/keys and
|
||||
# is named "<projectname>-server@<host>", 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';
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -14,7 +14,7 @@
|
||||
<td>{$server.name}</td>
|
||||
<td>{$server.status}</td>
|
||||
<td>
|
||||
{if preg_match("/ACTIVE|RUNNING|SLEEPING/", $server.status)}
|
||||
{if preg_match("/ACTIVE|WAITING|RUNNING|SLEEPING/", $server.status)}
|
||||
<a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'stop')}">
|
||||
{trans 'stop'}</a>
|
||||
{elseif $server.status == "STOPPED"}
|
||||
|
@ -12,12 +12,12 @@
|
||||
{foreach $changes as $change}
|
||||
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $change.scm_id)}
|
||||
<tr class="log">
|
||||
<td><a href="{$url}">{$change.creation_dtime|dateago:"wihtout"}</a></td>
|
||||
<td><a href="{$url}">{$change.creation_dtime|dateago:"without"}</a></td>
|
||||
<td>{issuetext $change.summary, $request}{if $change.fullmessage}<br /><br />{issuetext $change.fullmessage, $request, true, false, true, true, true}{/if}</td>
|
||||
</tr>
|
||||
<tr class="extra">
|
||||
<td colspan="2">
|
||||
<div class="helptext right">{trans 'Commit'} <a href="{$url}" class="mono">{$change.scm_id}</a>,
|
||||
<div class="helptext right">{trans 'Commit'} <a href="{$url}" class="mono">{$change.scm_id}</a>,
|
||||
{trans 'by'} {showuser $change.get_author(), $request, $change.origauthor}
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user