From 82aaf43d5d612a04b3fd365e0ccbd0e0dd3b86d5 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 11:20:47 +0000 Subject: [PATCH 01/23] - initial work on a mtn-post-push script which updates IDF's timeline when new revisions arrive. this still needs some more tests, but its a start. - refactor out the monotonerc template from SyncMonotone.php and place it in a separate template file (access control hooks are still missing from there) --- scripts/mtn-post-push | 23 +++++++ scripts/mtnpostpush.php | 63 ++++++++++++++++++ src/IDF/Plugin/SyncMonotone.php | 74 ++++++++++++++-------- src/IDF/Plugin/SyncMonotone/monotonerc.tpl | 71 +++++++++++++++++++++ src/IDF/relations.php | 3 + 5 files changed, 209 insertions(+), 25 deletions(-) create mode 100755 scripts/mtn-post-push create mode 100644 scripts/mtnpostpush.php create mode 100644 src/IDF/Plugin/SyncMonotone/monotonerc.tpl 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/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index 4819db3..8ff4218 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -3,7 +3,7 @@ /* # ***** BEGIN LICENSE BLOCK ***** # This file is part of InDefero, an open source project management application. -# Copyright (C) 2008 Céondo Ltd and contributors. +# 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 @@ -37,6 +37,9 @@ class IDF_Plugin_SyncMonotone case 'IDF_Project::created': $plug->processMonotoneCreate($params['project']); break; + case 'mtnpostpush.php::run': + $plug->processSyncTimeline($params); + break; } } @@ -73,6 +76,13 @@ class IDF_Plugin_SyncMonotone ); } + $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)) { @@ -132,34 +142,19 @@ class IDF_Plugin_SyncMonotone // // step 3) write monotonerc for access control // FIXME: netsync access control is still missing! - // - $monotonerc =<<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/relations.php b/src/IDF/relations.php index bf4e32e..6734515 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -88,6 +88,9 @@ Pluf_Signal::connect('gitpostupdate.php::run', # 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', From e789263068a7e5fd9d2d67ad3a8099108178cdb3 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 12:58:52 +0000 Subject: [PATCH 02/23] - raise required mtn version to 0.99 - add '--key=' to the default mtn arguments in idf.php-dist --- doc/readme-monotone.mdtext | 5 ++--- src/IDF/conf/idf.php-dist | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) 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/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 57d3e95..9339a5a 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -73,10 +73,10 @@ $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 several ways. The # two most-used should be: From 6b4abac08ea0bec6c47cea052b1a1fb7faaa349e Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 13:46:05 +0000 Subject: [PATCH 03/23] - PHP doesn't like the $var = kind of initialization and won't return variable references properly, so revert that change again - since we're requiring 0.99 now, we also have to use au generate_key instead of au genkey --- src/IDF/Plugin/SyncMonotone.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index 8ff4218..ecbb301 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -106,7 +106,8 @@ class IDF_Plugin_SyncMonotone escapeshellarg($dbfile) ); $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - $ll = exec($cmd, $output = array(), $return = 0); + $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 @@ -127,12 +128,14 @@ class IDF_Plugin_SyncMonotone $keyname = $shortname.'-server@'.$server; $cmd = sprintf( - Pluf::f('mtn_path', 'mtn').' au genkey --confdir=%s %s ""', + Pluf::f('mtn_path', 'mtn').' au generate_key --confdir=%s %s ""', escapeshellarg($projectpath), escapeshellarg($keyname) ); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - $ll = exec($cmd, $output = array(), $return = 0); + $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 From a384c6093737782c8a880b936e94203152590200 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 20:14:03 +0000 Subject: [PATCH 04/23] fix a PHP notice --- src/IDF/Scm/Monotone/Stdio.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) { From e26a5c8cdfc07e5b3aa7044a0d878994761d0fcc Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 21:29:42 +0000 Subject: [PATCH 05/23] waiting usher instances should be stoppable as well --- src/IDF/templates/idf/gadmin/usher/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"} From 187365db76c3eb5a35f12ec636d9cc3028e8059d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 30 Aug 2010 21:50:25 +0000 Subject: [PATCH 06/23] IDF_Scm::isValidRevision() only ever takes one argument --- src/IDF/Views/Source.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 5f4e1da0c862e18515c5ddbaa6ccdb88852e7411 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Tue, 31 Aug 2010 21:07:31 +0000 Subject: [PATCH 07/23] while there _should_ be really a custom application exception, currently there is no such one --- src/IDF/Key.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); } From 5d263e78e00dd3d5569cc23f81c9279fdad81385 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Tue, 31 Aug 2010 21:17:09 +0000 Subject: [PATCH 08/23] its dateago:"without", not dateago:"wihtout" --- src/IDF/templates/idf/source/changelog.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}
From c5c7ebff04aef21ce8952e259b49c7448e0242bd Mon Sep 17 00:00:00 2001 From: Matthew Dawson Date: Wed, 1 Sep 2010 14:50:04 +0200 Subject: [PATCH 09/23] Improved the wiki links to have better descripting links. --- src/IDF/Template/Markdown.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/IDF/Template/Markdown.php b/src/IDF/Template/Markdown.php index feb8512..3054ca8 100644 --- a/src/IDF/Template/Markdown.php +++ b/src/IDF/Template/Markdown.php @@ -46,25 +46,32 @@ class IDF_Template_Markdown extends Pluf_Template_Tag array($this, 'callbackEmbeddedDoc'), $text); } + // Replace [Page]([[PageName]]) with corresponding link to the page, with link text being Page. + $text = preg_replace_callback('#\[([^\]]+)\]\(\[\[([A-Za-z0-9\-]+)\]\]\)#im', + array($this, 'callbackWikiPage'), + $text); // Replace [[PageName]] with corresponding link to the page. $text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im', - array($this, 'callbackWikiPage'), + array($this, 'callbackWikiPageNoName'), $text); $filter = new IDF_Template_MarkdownPrefilter(); echo $filter->go(Pluf_Text_MarkDown_parse($text)); } + function callbackWikiPageNoName($m) + { + $m[2] = $m[1]; //Set the link text to be the same as the page name. + return $this->callbackWikiPage($m); + } + function callbackWikiPage($m) { $sql = new Pluf_SQL('project=%s AND title=%s', - array($this->project->id, $m[1])); + array($this->project->id, $m[2])); $pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen())); - if ($pages->count() != 1 and !$this->request->rights['hasWikiAccess']) { - return $m[0]; - } if ($pages->count() != 1 and $this->request->rights['hasWikiAccess'] and !$this->request->user->isAnonymous()) { - return ' '.$m[1].''; + return ' '.$m[1].''; } if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) { return $m[1]; From b4f8cf8c50ef5f58e7271a0006f7c4ff56051190 Mon Sep 17 00:00:00 2001 From: Brenda Wallace Date: Wed, 1 Sep 2010 15:06:02 +0200 Subject: [PATCH 10/23] Corrected grammar in instructions. --- src/IDF/locale/cs/idf.po | 2 +- src/IDF/locale/de/idf.po | 2 +- src/IDF/locale/fr/idf.po | 2 +- src/IDF/locale/ru/idf.po | 2 +- src/IDF/locale/sl/idf.po | 2 +- src/IDF/templates/idf/review/create.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/IDF/locale/cs/idf.po b/src/IDF/locale/cs/idf.po index 8e9f055..28cead4 100644 --- a/src/IDF/locale/cs/idf.po +++ b/src/IDF/locale/cs/idf.po @@ -970,7 +970,7 @@ msgid "" "
    \n" "
  • A commit or revision of the current code in the repository from which you started your work.
  • \n" "
  • A patch describing your changes with respect to the reference commit.
  • \n" -"
  • Check your patch to not provide any password or confidential information!
  • \n" +"
  • Ensure your patch does not contain any passwords or confidential information!
  • \n" "
" msgstr "" diff --git a/src/IDF/locale/de/idf.po b/src/IDF/locale/de/idf.po index 0547f12..c558fad 100644 --- a/src/IDF/locale/de/idf.po +++ b/src/IDF/locale/de/idf.po @@ -970,7 +970,7 @@ msgid "" "
    \n" "
  • A commit or revision of the current code in the repository from which you started your work.
  • \n" "
  • A patch describing your changes with respect to the reference commit.
  • \n" -"
  • Check your patch to not provide any password or confidential information!
  • \n" +"
  • Ensure your patch does not contain any passwords or confidential information!
  • \n" "
" msgstr "" diff --git a/src/IDF/locale/fr/idf.po b/src/IDF/locale/fr/idf.po index 491fb6b..4be8ba9 100644 --- a/src/IDF/locale/fr/idf.po +++ b/src/IDF/locale/fr/idf.po @@ -1026,7 +1026,7 @@ msgid "" "
    \n" "
  • A commit or revision of the current code in the repository from which you started your work.
  • \n" "
  • A patch describing your changes with respect to the reference commit.
  • \n" -"
  • Check your patch to not provide any password or confidential information!
  • \n" +"
  • Ensure your patch does not contain any passwords or confidential information!
  • \n" "
" msgstr "" "

Pour démarrer une revue de code vous devez fournir :

\n" diff --git a/src/IDF/locale/ru/idf.po b/src/IDF/locale/ru/idf.po index 40de7a9..5d83baf 100644 --- a/src/IDF/locale/ru/idf.po +++ b/src/IDF/locale/ru/idf.po @@ -1019,7 +1019,7 @@ msgid "" "
    \n" "
  • A commit or revision of the current code in the repository from which you started your work.
  • \n" "
  • A patch describing your changes with respect to the reference commit.
  • \n" -"
  • Check your patch to not provide any password or confidential information!
  • \n" +"
  • Ensure your patch does not contain any passwords or confidential information!
  • \n" "
" msgstr "" diff --git a/src/IDF/locale/sl/idf.po b/src/IDF/locale/sl/idf.po index f826710..3d0fe69 100644 --- a/src/IDF/locale/sl/idf.po +++ b/src/IDF/locale/sl/idf.po @@ -997,7 +997,7 @@ msgid "" "
    \n" "
  • A commit or revision of the current code in the repository from which you started your work.
  • \n" "
  • A patch describing your changes with respect to the reference commit.
  • \n" -"
  • Check your patch to not provide any password or confidential information!
  • \n" +"
  • Ensure your patch does not contain any passwords or confidential information!
  • \n" "
" msgstr "" diff --git a/src/IDF/templates/idf/review/create.html b/src/IDF/templates/idf/review/create.html index 7275bbe..1a5a74e 100644 --- a/src/IDF/templates/idf/review/create.html +++ b/src/IDF/templates/idf/review/create.html @@ -57,7 +57,7 @@
  • A commit or revision of the current code in the repository from which you started your work.
  • A patch describing your changes with respect to the reference commit.
  • -
  • Check your patch to not provide any password or confidential information!
  • +
  • Ensure your patch does not contain any passwords or confidential information!
{/blocktrans}
{/block} From 21cdf60c31553261fc1e72b79f8631ee03bbf650 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Wed, 1 Sep 2010 13:13:52 +0000 Subject: [PATCH 11/23] Introduce a more subtle concept of validity when it comes to revision indentifiers in IDF - the SCM function isValidRevision has been replaced by a validateRevision() method which returns one of three states, valid, invalid or ambiguous. The source view can then act accordingly and display disambiguate view for the latter, so the user can select for which revision he actually wants to execute the requested action. Also, invalid revisions now lead to another separate view, telling the user that it is invalid / does not exist and pointing him optionally to the help page where he can read further how to access his repository to push the first changes into. (partially resolves issue 525) --- src/IDF/Scm.php | 29 ++- src/IDF/Scm/Git.php | 6 +- src/IDF/Scm/Mercurial.php | 9 +- src/IDF/Scm/Monotone.php | 49 ++++- src/IDF/Scm/Svn.php | 9 +- src/IDF/Views/Source.php | 208 +++++++++++------- src/IDF/conf/urls.php | 10 + src/IDF/templates/idf/source/base.html | 2 +- .../idf/source/disambiguate_revision.html | 33 +++ .../idf/source/invalid_revision.html | 17 ++ 10 files changed, 271 insertions(+), 101 deletions(-) create mode 100644 src/IDF/templates/idf/source/disambiguate_revision.html create mode 100644 src/IDF/templates/idf/source/invalid_revision.html diff --git a/src/IDF/Scm.php b/src/IDF/Scm.php index 94cf5ec..1496974 100644 --- a/src/IDF/Scm.php +++ b/src/IDF/Scm.php @@ -63,7 +63,7 @@ class IDF_Scm public $project = null; /** - * Cache storage. + * Cache storage. * * It must only be used to store data for the lifetime of the * object. For example if you need to get the list of branches in @@ -166,13 +166,28 @@ class IDF_Scm throw new Pluf_Exception_NotImplemented(); } + const REVISION_VALID = 0; + const REVISION_INVALID = 1; + const REVISION_AMBIGUOUS = 2; + /** - * Check if a revision or commit is valid. + * Check if a revision or commit is valid, invalid or ambiguous. * * @param string Revision or commit - * @return bool + * @return int One of REVISION_VALID, REVISION_INVALID or REVISION_AMBIGIOUS */ - public function isValidRevision($rev) + public function validateRevision($rev) + { + throw new Pluf_Exception_NotImplemented(); + } + + /** + * Returns an array of single commit objects for ambiguous commit identifiers + * + * @param string Ambiguous commit identifier + * @return array of objects + */ + public function disambiguateRevision($commit) { throw new Pluf_Exception_NotImplemented(); } @@ -217,7 +232,7 @@ class IDF_Scm * 'foo-branch' => 'branches/foo-branch',) * * - * @return array Branches + * @return array Branches */ public function getBranches() { @@ -282,7 +297,7 @@ class IDF_Scm * @param string Revision or commit * @param string Folder ('/') * @param string Branch (null) - * @return array + * @return array */ public function getTree($rev, $folder='/', $branch=null) { @@ -396,7 +411,7 @@ class IDF_Scm public static function syncTimeline($project, $force=false) { $cache = Pluf_Cache::factory(); - $key = 'IDF_Scm:'.$project->shortname.':lastsync'; + $key = 'IDF_Scm:'.$project->shortname.':lastsync'; if ($force or null === ($res=$cache->get($key))) { $scm = IDF_Scm::get($project); if ($scm->isAvailable()) { diff --git a/src/IDF/Scm/Git.php b/src/IDF/Scm/Git.php index 77bea39..4a3b308 100644 --- a/src/IDF/Scm/Git.php +++ b/src/IDF/Scm/Git.php @@ -296,10 +296,12 @@ class IDF_Scm_Git extends IDF_Scm } - public function isValidRevision($commit) + public function validateRevision($commit) { $type = $this->testHash($commit); - return ('commit' == $type || 'tag' == $type); + if ('commit' == $type || 'tag' == $type) + return IDF_Scm::REVISION_VALID; + return IDF_Scm::REVISION_INVALID; } /** diff --git a/src/IDF/Scm/Mercurial.php b/src/IDF/Scm/Mercurial.php index 9f34ab7..ac66464 100644 --- a/src/IDF/Scm/Mercurial.php +++ b/src/IDF/Scm/Mercurial.php @@ -87,14 +87,19 @@ class IDF_Scm_Mercurial extends IDF_Scm return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname); } - public function isValidRevision($rev) + public function validateRevision($rev) { $cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s', escapeshellarg($this->repo), escapeshellarg($rev)); $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; self::exec('IDF_Scm_Mercurial::isValidRevision', $cmd, $out, $ret); - return ($ret == 0) && (count($out) > 0); + + // FIXME: apparently a given hg revision can also be ambigious - + // handle this case here sometime + if ($ret == 0 && count($out) > 0) + return IDF_Scm::REVISION_VALID; + return IDF_Scm::REVISION_INVALID; } /** diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index f8c898f..eb44292 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -425,12 +425,53 @@ class IDF_Scm_Monotone extends IDF_Scm } /** - * @see IDF_Scm::isValidRevision() + * @see IDF_Scm::validateRevision() */ - public function isValidRevision($commit) + public function validateRevision($commit) { $revs = $this->_resolveSelector($commit); - return count($revs) == 1; + if (count($revs) == 0) + return IDF_Scm::REVISION_INVALID; + + if (count($revs) > 1) + return IDF_Scm::REVISION_AMBIGUOUS; + + return IDF_Scm::REVISION_VALID; + } + + /** + * @see IDF_Scm::disambiguateRevision + */ + public function disambiguateRevision($commit) + { + $revs = $this->_resolveSelector($commit); + + $out = array(); + foreach ($revs as $rev) + { + $certs = $this->_getCerts($rev); + + $log = array(); + $log['author'] = implode(', ', $certs['author']); + + $log['branch'] = implode(', ', $certs['branch']); + + $dates = array(); + foreach ($certs['date'] as $date) + $dates[] = date('Y-m-d H:i:s', strtotime($date)); + $log['date'] = implode(', ', $dates); + + $combinedChangelog = implode("\n---\n", $certs['changelog']); + $split = preg_split("/[\n\r]/", $combinedChangelog, 2); + $log['title'] = $split[0]; + $log['full_message'] = (isset($split[1])) ? trim($split[1]) : ''; + + $log['commit'] = $rev; + + $out[] = (object)$log; + } + + return $out; } /** @@ -630,7 +671,7 @@ class IDF_Scm_Monotone extends IDF_Scm --$n; $log = array(); - $log['author'] = implode(", ", $certs['author']); + $log['author'] = implode(', ', $certs['author']); $dates = array(); foreach ($certs['date'] as $date) diff --git a/src/IDF/Scm/Svn.php b/src/IDF/Scm/Svn.php index 6527bbf..fdbc340 100644 --- a/src/IDF/Scm/Svn.php +++ b/src/IDF/Scm/Svn.php @@ -138,7 +138,7 @@ class IDF_Scm_Svn extends IDF_Scm /** * Subversion revisions are either a number or 'HEAD'. */ - public function isValidRevision($rev) + public function validateRevision($rev) { if ($rev == 'HEAD') { return true; @@ -149,8 +149,11 @@ class IDF_Scm_Svn extends IDF_Scm escapeshellarg($this->repo), escapeshellarg($rev)); $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Svn::isValidRevision', $cmd, $out, $ret); - return (0 == $ret); + self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret); + + if ($ret == 0) + return IDF_Scm::REVISION_VALID; + return IDF_Scm::REVISION_INVALID; } diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index 18d79a2..dcebdab 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -59,30 +59,56 @@ class IDF_Views_Source $params, $request); } - public $changeLog_precond = array('IDF_Precondition::accessSource'); + /** + * Is displayed in case an invalid revision is requested + */ + public $invalidRevision_precond = array('IDF_Precondition::accessSource'); + public function invalidRevision($request, $match) + { + $title = sprintf(__('%s Invalid Revision'), (string) $request->project); + $commit = $match[2]; + $params = array( + 'page_title' => $title, + 'title' => $title, + 'commit' => $commit, + ); + return Pluf_Shortcuts_RenderToResponse('idf/source/invalid_revision.html', + $params, $request); + } + + /** + * Is displayed in case a revision identifier cannot be uniquely resolved + * to one single revision + */ + public $disambiguateRevision_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable'); + public function disambiguateRevision($request, $match) + { + $title = sprintf(__('%s Ambiguous Revision'), (string) $request->project); + $commit = $match[2]; + $redirect = $match[3]; + $scm = IDF_Scm::get($request->project); + $revisions = $scm->disambiguateRevision($commit); + $params = array( + 'page_title' => $title, + 'title' => $title, + 'commit' => $commit, + 'revisions' => $revisions, + 'redirect' => $redirect, + ); + return Pluf_Shortcuts_RenderToResponse('idf/source/disambiguate_revision.html', + $params, $request); + } + + public $changeLog_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function changeLog($request, $match) { $scm = IDF_Scm::get($request->project); - if (!$scm->isAvailable()) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', - array($request->project->shortname)); - return new Pluf_HTTP_Response_Redirect($url); - } $branches = $scm->getBranches(); $commit = $match[2]; - if (!$scm->isValidRevision($commit)) { - if (count($branches) == 0) { - // Redirect to the project source help - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', - array($request->project->shortname)); - return new Pluf_HTTP_Response_Redirect($url); - } - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::changeLog', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); - } + $title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project, $this->getScmType($request)); $changes = $scm->getChangeLog($commit, 25); @@ -111,22 +137,17 @@ class IDF_Views_Source $request); } - public $treeBase_precond = array('IDF_Precondition::accessSource'); + public $treeBase_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function treeBase($request, $match) { $scm = IDF_Scm::get($request->project); - if (!$scm->isAvailable()) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', - array($request->project->shortname)); - return new Pluf_HTTP_Response_Redirect($url); - } $commit = $match[2]; + $cobject = $scm->getCommit($commit); if (!$cobject) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); + throw new Exception('could not retrieve commit object for '. $commit); } $title = sprintf(__('%1$s %2$s Source Tree'), $request->project, $this->getScmType($request)); @@ -159,20 +180,14 @@ class IDF_Views_Source $request); } - public $tree_precond = array('IDF_Precondition::accessSource'); + public $tree_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function tree($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; - if (!$scm->isAvailable()) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', - array($request->project->shortname)); - return new Pluf_HTTP_Response_Redirect($url); - } - $fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); $request_file = $match[3]; if (substr($request_file, -1) == '/') { $request_file = substr($request_file, 0, -1); @@ -181,13 +196,13 @@ class IDF_Views_Source $request_file)); return new Pluf_HTTP_Response_Redirect($url, 301); } - if (!$scm->isValidRevision($commit)) { - // Redirect to the first branch - return new Pluf_HTTP_Response_Redirect($fburl); - } + $request_file_info = $scm->getPathInfo($request_file, $commit); if (!$request_file_info) { - // Redirect to the first branch + // Redirect to the main branch + $fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', + array($request->project->shortname, + $scm->getMainBranch())); return new Pluf_HTTP_Response_Redirect($fburl); } $branches = $scm->getBranches(); @@ -277,26 +292,17 @@ class IDF_Views_Source return ''.implode(''.$sep.'', $out).''; } - public $commit_precond = array('IDF_Precondition::accessSource'); + public $commit_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function commit($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; - if (!$scm->isValidRevision($commit)) { - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); - } $large = $scm->isCommitLarge($commit); $cobject = $scm->getCommit($commit, !$large); if (!$cobject) { - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); + throw new Exception('could not retrieve commit object for '. $commit); } $title = sprintf(__('%s Commit Details'), (string) $request->project); $page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit); @@ -326,19 +332,17 @@ class IDF_Views_Source $request); } - public $downloadDiff_precond = array('IDF_Precondition::accessSource'); + public $downloadDiff_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function downloadDiff($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; - if (!$scm->isValidRevision($commit)) { - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); - } $cobject = $scm->getCommit($commit, true); + if (!$cobject) { + throw new Exception('could not retrieve commit object for '. $commit); + } $rep = new Pluf_HTTP_Response($cobject->changes, 'text/plain'); $rep->headers['Content-Disposition'] = 'attachment; filename="'.$commit.'.diff"'; return $rep; @@ -394,19 +398,14 @@ class IDF_Views_Source * Get a given file at a given commit. * */ - public $getFile_precond = array('IDF_Precondition::accessSource'); + public $getFile_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function getFile($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $request_file = $match[3]; - if (!$scm->isValidRevision($commit)) { - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); - } $request_file_info = $scm->getPathInfo($request_file, $commit); if (!$request_file_info or $request_file_info->type == 'tree') { // Redirect to the first branch @@ -427,18 +426,13 @@ class IDF_Views_Source * Get a zip archive of the current commit. * */ - public $download_precond = array('IDF_Precondition::accessSource'); + public $download_precond = array('IDF_Precondition::accessSource', + 'IDF_Views_Source_Precondition::scmAvailable', + 'IDF_Views_Source_Precondition::revisionValid'); public function download($request, $match) { $commit = trim($match[2]); $scm = IDF_Scm::get($request->project); - if (!$scm->isValidRevision($commit)) { - // Redirect to the first branch - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', - array($request->project->shortname, - $scm->getMainBranch())); - return new Pluf_HTTP_Response_Redirect($url); - } $base = $request->project->shortname.'-'.$commit; $cmd = $scm->getArchiveCommand($commit, $base.'/'); $rep = new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip'); @@ -447,7 +441,6 @@ class IDF_Views_Source return $rep; } - /** * Find the mime type of a requested file. * @@ -495,7 +488,6 @@ class IDF_Views_Source return $res; } - /** * Find the mime type of a file. * @@ -610,3 +602,55 @@ function IDF_Views_Source_ShortenString($string, $length) substr($string, -($length - $preflen - mb_strlen($ellipse))); } +class IDF_Views_Source_Precondition +{ + /** + * Ensures that the configured SCM for the project is available + * + * @param $request + * @return true | Pluf_HTTP_Response_Redirect + */ + static public function scmAvailable($request) + { + $scm = IDF_Scm::get($request->project); + if (!$scm->isAvailable()) { + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', + array($request->project->shortname)); + return new Pluf_HTTP_Response_Redirect($url); + } + return true; + } + + /** + * Validates the revision given in the URL path and acts accordingly + * + * @param $request + * @return true | Pluf_HTTP_Response_Redirect + * @throws Exception + */ + static public function revisionValid($request) + { + list($url_info, $url_matches) = $request->view; + list(, $project, $commit) = $url_matches; + + $scm = IDF_Scm::get($request->project); + $res = $scm->validateRevision($commit); + switch ($res) { + case IDF_Scm::REVISION_VALID: + return true; + case IDF_Scm::REVISION_INVALID: + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::invalidRevision', + array($request->project->shortname, $commit)); + return new Pluf_HTTP_Response_Redirect($url); + case IDF_Scm::REVISION_AMBIGUOUS: + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::disambiguateRevision', + array($request->project->shortname, + $commit, + $url_info['model'].'::'.$url_info['method'])); + return new Pluf_HTTP_Response_Redirect($url); + default: + throw new Exception('unknown validation result: '. $res); + } + } +} + diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index b4f6a4c..c01e565 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -148,6 +148,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#', 'model' => 'IDF_Views_Source', 'method' => 'help'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/invalid/([^/]+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Source', + 'method' => 'invalidRevision'); + +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/disambiguate/([^/]+)/from/([^/]+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Source', + 'method' => 'disambiguateRevision'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([^/]+)/$#', 'base' => $base, 'model' => 'IDF_Views_Source', diff --git a/src/IDF/templates/idf/source/base.html b/src/IDF/templates/idf/source/base.html index 322f6ce..0526095 100644 --- a/src/IDF/templates/idf/source/base.html +++ b/src/IDF/templates/idf/source/base.html @@ -1,7 +1,7 @@ {extends "idf/base.html"} {block tabsource} class="active"{/block} {block subtabs} -{if !$inHelp and (in_array($commit, $tree_in) or (in_array($commit, $tags_in)))}{assign $currentCommit = $commit}{else}{assign $currentCommit = $project.getScmRoot()}{/if} +{if !$inHelp and !$inError and (in_array($commit, $tree_in) or (in_array($commit, $tags_in)))}{assign $currentCommit = $commit}{else}{assign $currentCommit = $project.getScmRoot()}{/if}
{trans 'Source Tree'} | {trans 'Change Log'} diff --git a/src/IDF/templates/idf/source/disambiguate_revision.html b/src/IDF/templates/idf/source/disambiguate_revision.html new file mode 100644 index 0000000..85c3d61 --- /dev/null +++ b/src/IDF/templates/idf/source/disambiguate_revision.html @@ -0,0 +1,33 @@ +{extends "idf/source/base.html"} +{block docclass}yui-t2{assign $inError=true}{/block} +{block body} + +

{blocktrans}The revision identifier {$commit} is ambiguous and can be +expanded to multiple valid revisions - please choose one:{/blocktrans}

+ + + + + + + + + + + + +{foreach $revisions as $revision} +{aurl 'url', $redirect, array($project.shortname, $revision.commit)} + + + + + + + + +{/foreach} + +
{trans 'Title'}{trans 'Author'}{trans 'Date'}{trans 'Branch'}{trans 'Revision'}
{$revision.title}{$revision.author}{$revision.date}{$revision.branch}{$revision.commit}
+{/block} + diff --git a/src/IDF/templates/idf/source/invalid_revision.html b/src/IDF/templates/idf/source/invalid_revision.html new file mode 100644 index 0000000..4c0e966 --- /dev/null +++ b/src/IDF/templates/idf/source/invalid_revision.html @@ -0,0 +1,17 @@ +{extends "idf/source/base.html"} +{block docclass}yui-t2{assign $inError=true}{/block} +{block body} + +

{blocktrans}The revision {$commit} is not valid or does not exist +in this repository.{/blocktrans}

+ +{if $isOwner or $isMember} +{aurl 'url', 'IDF_Views_Source::help', array($project.shortname)} +

{blocktrans}If this is a new repository, the reason for this error +could be that you have not committed and / or pushed any change so far. +In this case please take a look at the Help page +how to access your repository.{/blocktrans}

+{/if} + +{/block} + From 132c4f6c89e7c56c72dce9f76b0350cad107719b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Wed, 1 Sep 2010 15:17:24 +0200 Subject: [PATCH 12/23] Fixed ticket 481, problem with registration link. --- INSTALL.mdtext | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/INSTALL.mdtext b/INSTALL.mdtext index 947323d..d0f6c01 100644 --- a/INSTALL.mdtext +++ b/INSTALL.mdtext @@ -212,4 +212,14 @@ If you access a Subversion server with a self-signed certificate, you may have problems as your certificate is not trusted, check the [procedure provided here][svnfix] to solve the problem. -[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358 \ No newline at end of file +[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358 + +## If the registration links are not working + +If You have standard instalaction of PHP ie in Debian, php.ini sets +mbstring.func_overload to value "2" for overloading str* +functions. You need to prevent the overload as it does not make sense +anyway (magic in the background is bad!). +See the [corresponding ticket][reglink]. + +[reglink]: http://projects.ceondo.com/p/indefero/issues/481/ \ No newline at end of file From 439014b0b134e3d44636968a4bb8bf031d27df7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Thu, 2 Sep 2010 14:16:41 +0200 Subject: [PATCH 13/23] Fixed ticket 479, project editing inconsistency. --- src/IDF/Form/Admin/ProjectCreate.php | 9 +++++++++ src/IDF/Form/Admin/ProjectUpdate.php | 9 +++++++++ src/IDF/templates/idf/gadmin/projects/create.html | 8 ++++++++ src/IDF/templates/idf/gadmin/projects/update.html | 8 ++++++++ 4 files changed, 34 insertions(+) diff --git a/src/IDF/Form/Admin/ProjectCreate.php b/src/IDF/Form/Admin/ProjectCreate.php index e2414e9..d297f14 100644 --- a/src/IDF/Form/Admin/ProjectCreate.php +++ b/src/IDF/Form/Admin/ProjectCreate.php @@ -64,6 +64,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form 'help_text' => __('It must be unique for each project and composed only of letters, digits and dash (-) like "my-project".'), )); + $this->fields['shortdesc'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('short description'), + 'help_text' => __('A one line description of the project.'), + 'initial' => '', + 'widget_attrs' => array('size' => '35'), + )); + $this->fields['scm'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Repository type'), @@ -272,6 +280,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form $project = new IDF_Project(); $project->name = $this->cleaned_data['name']; $project->shortname = $this->cleaned_data['shortname']; + $project->shortdesc = $this->cleaned_data['shortdesc']; if ($this->cleaned_data['template'] != '--') { // Find the template project diff --git a/src/IDF/Form/Admin/ProjectUpdate.php b/src/IDF/Form/Admin/ProjectUpdate.php index cd9995e..4157851 100644 --- a/src/IDF/Form/Admin/ProjectUpdate.php +++ b/src/IDF/Form/Admin/ProjectUpdate.php @@ -43,6 +43,14 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form 'initial' => $this->project->name, )); + $this->fields['shortdesc'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('short description'), + 'help_text' => __('A one line description of the project.'), + 'initial' => $this->project->shortdesc, + 'widget_attrs' => array('size' => '35'), + )); + $this->fields['owners'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('Project owners'), @@ -80,6 +88,7 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form $this->cleaned_data); $this->project->membershipsUpdated(); $this->project->name = $this->cleaned_data['name']; + $this->project->shortdesc = $this->cleaned_data['shortdesc']; $this->project->update(); } } diff --git a/src/IDF/templates/idf/gadmin/projects/create.html b/src/IDF/templates/idf/gadmin/projects/create.html index 1c1cddf..a2f70b8 100644 --- a/src/IDF/templates/idf/gadmin/projects/create.html +++ b/src/IDF/templates/idf/gadmin/projects/create.html @@ -29,6 +29,14 @@ +{$form.f.shortdesc.labelTag}: + +{if $form.f.shortdesc.errors}{$form.f.shortdesc.fieldErrors}{/if} +{$form.f.shortdesc|unsafe}
+{$form.f.shortdesc.help_text} + + + {$form.f.scm.labelTag}: {if $form.f.scm.errors}{$form.f.scm.fieldErrors}{/if} {$form.f.scm|unsafe} diff --git a/src/IDF/templates/idf/gadmin/projects/update.html b/src/IDF/templates/idf/gadmin/projects/update.html index 5442e72..e6a40eb 100644 --- a/src/IDF/templates/idf/gadmin/projects/update.html +++ b/src/IDF/templates/idf/gadmin/projects/update.html @@ -18,6 +18,14 @@ +{$form.f.shortdesc.labelTag}: + +{if $form.f.shortdesc.errors}{$form.f.shortdesc.fieldErrors}{/if} +{$form.f.shortdesc|unsafe}
+{$form.f.shortdesc.help_text} + + + {$form.f.owners.labelTag}: {if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if} From 85df9e5ab2fb6789f34d01b5625da4f31f91bf67 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 2 Sep 2010 12:22:59 +0000 Subject: [PATCH 14/23] move IDF_View_Source_Precondition into a separate class file --- src/IDF/Views/Source.php | 53 ------------------- src/IDF/Views/Source/Precondition.php | 74 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 src/IDF/Views/Source/Precondition.php diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index dcebdab..2d1b3f8 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -601,56 +601,3 @@ function IDF_Views_Source_ShortenString($string, $length) return substr($string, 0, $preflen).$ellipse. substr($string, -($length - $preflen - mb_strlen($ellipse))); } - -class IDF_Views_Source_Precondition -{ - /** - * Ensures that the configured SCM for the project is available - * - * @param $request - * @return true | Pluf_HTTP_Response_Redirect - */ - static public function scmAvailable($request) - { - $scm = IDF_Scm::get($request->project); - if (!$scm->isAvailable()) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', - array($request->project->shortname)); - return new Pluf_HTTP_Response_Redirect($url); - } - return true; - } - - /** - * Validates the revision given in the URL path and acts accordingly - * - * @param $request - * @return true | Pluf_HTTP_Response_Redirect - * @throws Exception - */ - static public function revisionValid($request) - { - list($url_info, $url_matches) = $request->view; - list(, $project, $commit) = $url_matches; - - $scm = IDF_Scm::get($request->project); - $res = $scm->validateRevision($commit); - switch ($res) { - case IDF_Scm::REVISION_VALID: - return true; - case IDF_Scm::REVISION_INVALID: - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::invalidRevision', - array($request->project->shortname, $commit)); - return new Pluf_HTTP_Response_Redirect($url); - case IDF_Scm::REVISION_AMBIGUOUS: - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::disambiguateRevision', - array($request->project->shortname, - $commit, - $url_info['model'].'::'.$url_info['method'])); - return new Pluf_HTTP_Response_Redirect($url); - default: - throw new Exception('unknown validation result: '. $res); - } - } -} - diff --git a/src/IDF/Views/Source/Precondition.php b/src/IDF/Views/Source/Precondition.php new file mode 100644 index 0000000..39b6ab4 --- /dev/null +++ b/src/IDF/Views/Source/Precondition.php @@ -0,0 +1,74 @@ +project); + if (!$scm->isAvailable()) { + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help', + array($request->project->shortname)); + return new Pluf_HTTP_Response_Redirect($url); + } + return true; + } + + /** + * Validates the revision given in the URL path and acts accordingly + * + * @param $request + * @return true | Pluf_HTTP_Response_Redirect + * @throws Exception + */ + static public function revisionValid($request) + { + list($url_info, $url_matches) = $request->view; + list(, $project, $commit) = $url_matches; + + $scm = IDF_Scm::get($request->project); + $res = $scm->validateRevision($commit); + switch ($res) { + case IDF_Scm::REVISION_VALID: + return true; + case IDF_Scm::REVISION_INVALID: + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::invalidRevision', + array($request->project->shortname, $commit)); + return new Pluf_HTTP_Response_Redirect($url); + case IDF_Scm::REVISION_AMBIGUOUS: + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::disambiguateRevision', + array($request->project->shortname, + $commit, + $url_info['model'].'::'.$url_info['method'])); + return new Pluf_HTTP_Response_Redirect($url); + default: + throw new Exception('unknown validation result: '. $res); + } + } +} From a2297decfda60d4a8674845f3a9780f5836b34bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Thu, 2 Sep 2010 14:39:09 +0200 Subject: [PATCH 15/23] Fixed ticket 486, start to explain how to contribute. --- CONTRIBUTE.mdtext | 115 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 CONTRIBUTE.mdtext diff --git a/CONTRIBUTE.mdtext b/CONTRIBUTE.mdtext new file mode 100644 index 0000000..2aaa0e9 --- /dev/null +++ b/CONTRIBUTE.mdtext @@ -0,0 +1,115 @@ +[Indefero][idf] is not only a software you can use either hosted for +you or hosted by you, but also a free software you can contribute to. + +Here you will get how to contribute and what to contribute. + +[idf]: http://www.indefero.net + +# Quick Way on How to Contribute + +Simple contribution: + +1. Open a ticket with your idea. You can directly propose a patch if + you have it. + +2. Wait for it to be checked by the devs or meet us on the #indefero + channel on [FreeNode][freenode]. + +Bigger contribution: + +1. Fork Indefero where you want (fork from the develop branch). + +2. Code your change and document it. + +3. Open a ticket with a pull request and talk about it on IRC. + +# The General Contribution Workflow for Regular Contributors + +1. Fork Indefero from the **develop** branch. +2. Request a pull request if you do not have write access on the repository. +3. Merge your changes without fast forward in develop. This keeps track of + the history of the changes and makes understanding what is going on easy. +4. Merge your changes with fast forward **only if a single commit**. + +Indefero is composed of two main branches: + +1. **master**: this is the shipped branch, only a select number of people + can push into it. +2. **develop**: this is the development branch, all the people having write + access to the repository are welcomed to push in. + +**Note:** The branching model we use is [explained in details here][bmi]. You +**must** understand it to really contribute to the code base in an +efficient way. + +[bmi]: http://nvie.com/git-model "A successful Git branching model" + +# What to Contribute + +Contribution is easy, you can contribute in a lot of different fields, +contributions small or big are always appreciated. Here is an example +list of what you can do: + +- Install InDefero on your system and report the problem you had. +- Find the bad English and typos and propose corrections. +- Help with the translation effort. +- Find little bugs or usability problems and provide ideas on how to fix them. +- Register to the [discussion group][group] and help new users. +- Come and chat on IRC #indefero on the [FreeNode][freenode] servers. +- Find ways to improve the design while keeping it **beautifully simple**. +- Write a blog post about the project, what you think is good or bad. +- Translate InDefero for the sake of the community. +- Or maybe really hack into the code. + +As you can see, the real hacking into the code is just a small part of the work, so even if you are not a coder you can do a lot. + +[group]: http://groups.google.com/group/indefero-users +[freenode]: http://freenode.net/ + +## I am a simple user + +Thanks a lot! Really! As a project leader, I consider **you** as +**the most important person in the success of the project**. So do not +worry, I will really listen to your needs and make you love this +project. + +What you can do to help: + +- Use the software and each time you find something a bit annoying in your daily use, report a bug. Usability issues are high priority issues. +- Find typos, grammar mistakes, etc. and report a bug. +- Write about InDefero on your blog/website. +- Read the issues submitted by the users and provide answers if you have them. +- ... + +## I am a designer + +A lot of things to do for you: + +- Check the design and find the flaws in it. Is the space well used, does it look really nice and is it also functional for the first users? +- Do we have good support of all the major browsers? +- ... + +## I am a coder + +Checkout the code and have fun, but keep in mind that your results +must be simple to use. Do not worry about the beautiful part, the +designers can work on that. + +## I am a security guy + +Please, do try to break it, if you find a problem, come on IRC or +contact the developers to get the issue fixed as soon as +possible. Please, be nice, do not release the issue in the wild +without first talking to us. + +## I am a translator + +We currently use (transifex)[http://trac.transifex.org] to help our +users to translate indefero. You don't have to use it, but it's an +easy way to do the job. You can visit the indefero page at transifex +here : http://www.transifex.net/projects/p/indefero/c/indefero/ + +Please understand that your changes will not be commited instantly, +but are sent to the maintainers e-mails before. Then, your changes +will not be in the main repository until da-loic push the changes. In +that way, try to do big changes with less submissions. \ No newline at end of file From f3f00dd1822c4dc2ec64b2eefc40df5713bbfc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Thu, 2 Sep 2010 14:46:15 +0200 Subject: [PATCH 16/23] Fixed ticket 489, improve the Markdown and wiki syntax documentation. --- src/IDF/templates/idf/wiki/edit-info.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/IDF/templates/idf/wiki/edit-info.html b/src/IDF/templates/idf/wiki/edit-info.html index 98abdc7..088421c 100644 --- a/src/IDF/templates/idf/wiki/edit-info.html +++ b/src/IDF/templates/idf/wiki/edit-info.html @@ -1,6 +1,8 @@ -{assign $url = 'http://michelf.com/projects/php-markdown/extra/'} +{assign $eurl = 'http://michelf.com/projects/php-markdown/extra/'} +{assign $burl = 'http://daringfireball.net/projects/markdown/syntax'} {blocktrans}

Instructions:

-

The content of the page can use the Markdown syntax.

+

The content of the page can use the Markdown syntax with the Extra extension.

Website addresses are automatically linked and you can link to another page in the documentation using double square brackets like that [[AnotherPage]].

+

To directly include a file content from the repository, embrace its path with triple square brackets: [[[path/to/file.txt]]].

{/blocktrans} From 7557a730143e5a5ec5c98bbd914dd4a6c55a9771 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 11 Sep 2010 00:21:30 +0200 Subject: [PATCH 17/23] While the dateAgo code internally doesn't seem to care about the argument (unless its not "withal"), its better to fix the spelling here anyways. --- src/IDF/templates/idf/source/git/tree.html | 4 ++-- src/IDF/templates/idf/source/mercurial/tree.html | 2 +- src/IDF/templates/idf/source/svn/tree.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/IDF/templates/idf/source/git/tree.html b/src/IDF/templates/idf/source/git/tree.html index 859a249..0f5d0eb 100644 --- a/src/IDF/templates/idf/source/git/tree.html +++ b/src/IDF/templates/idf/source/git/tree.html @@ -12,7 +12,7 @@ {trans 'Size'} {if !$tree_in and !$tags_in} -{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} +{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} {blocktrans}Source at commit {$commit} created {$cobject.date|dateago}.{/blocktrans}
{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans} @@ -35,7 +35,7 @@ {$file.file}{else}{$file.file}{/if} {if $file.type == 'blob'} {if isset($file.date) and $file.log != '----'} -{$file.date|dateago:"wihtout"} +{$file.date|dateago:"without"} {$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false} {else}{/if} {$file.size|size}{/if} diff --git a/src/IDF/templates/idf/source/mercurial/tree.html b/src/IDF/templates/idf/source/mercurial/tree.html index b5880cf..373e6a0 100644 --- a/src/IDF/templates/idf/source/mercurial/tree.html +++ b/src/IDF/templates/idf/source/mercurial/tree.html @@ -34,7 +34,7 @@ {$file.file} {if $file.type == 'blob'} {if isset($file.date)} -{$file.date|dateago:"wihtout"} +{$file.date|dateago:"without"} {$file.author|strip_tags|trim}{trans ':'} {$file.log} {else}{/if} {/if} diff --git a/src/IDF/templates/idf/source/svn/tree.html b/src/IDF/templates/idf/source/svn/tree.html index d9a1e29..dbb1713 100644 --- a/src/IDF/templates/idf/source/svn/tree.html +++ b/src/IDF/templates/idf/source/svn/tree.html @@ -45,7 +45,7 @@ {$file.type} {$file.file} - {$file.date|dateago:"wihtout"} + {$file.date|dateago:"without"} {$file.rev} {$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false} {if $file.type == 'blob'} From 37d0ccc72827d06f1dc0978d9753df0094e2ae1d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 11 Sep 2010 00:28:31 +0200 Subject: [PATCH 18/23] partially resolve issue 492 (at least for the monotone tree view) --- src/IDF/Scm/Monotone.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index eb44292..a2026b4 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -340,7 +340,14 @@ class IDF_Scm_Monotone extends IDF_Scm foreach ($certs['date'] as $date) $dates[] = date('Y-m-d H:i:s', strtotime($date)); $file['date'] = implode(', ', $dates); - $file['log'] = implode("\n---\n", $certs['changelog']); + $combinedChangelog = implode("\n---\n", $certs['changelog']); + $split = preg_split("/[\n\r]/", $combinedChangelog, 2); + // FIXME: the complete log message is currently not used in the + // tree view (the same is true for the other SCM implementations) + // but we _should_ really use or at least return that here + // in case we want to do fancy stuff like described in + // issue 492 + $file['log'] = $split[0]; } $files[] = (object) $file; From f68bba1292ee6df54d480a320c567db629af2927 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sun, 12 Sep 2010 23:18:58 +0000 Subject: [PATCH 19/23] Be more careful when parsing value lists - in case we process the last line of a stanza which does _not_ close with a newline, we're accessing a not existing string index. --- src/IDF/Scm/Monotone/BasicIO.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/IDF/Scm/Monotone/BasicIO.php b/src/IDF/Scm/Monotone/BasicIO.php index 0562c9b..78707c5 100644 --- a/src/IDF/Scm/Monotone/BasicIO.php +++ b/src/IDF/Scm/Monotone/BasicIO.php @@ -75,6 +75,9 @@ class IDF_Scm_Monotone_BasicIO } ++$pos; // closing quote + if ($pos >= strlen($in)) + break; + if ($in[$pos] == ' ') { ++$pos; // space ++$valCount; From 77cdbefe0c71575bbf151529f56a3288f9f200aa Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 13 Sep 2010 00:50:16 +0000 Subject: [PATCH 20/23] Added getter for the stdio instance --- src/IDF/Scm/Monotone.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index a2026b4..a0b01c6 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -47,6 +47,16 @@ class IDF_Scm_Monotone extends IDF_Scm $this->stdio = new IDF_Scm_Monotone_Stdio($project); } + /** + * Returns the stdio instance in use + * + * @return IDF_Scm_Monotone_Stdio + */ + public function getStdio() + { + return $this->stdio; + } + /** * @see IDF_Scm::getRepositorySize() */ From bb13722a2fb91248f26f8109a8caca641209ef9d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 13 Sep 2010 00:51:45 +0000 Subject: [PATCH 21/23] bump copyright year --- src/IDF/Views/Source/Precondition.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Views/Source/Precondition.php b/src/IDF/Views/Source/Precondition.php index 39b6ab4..a3a0172 100644 --- a/src/IDF/Views/Source/Precondition.php +++ b/src/IDF/Views/Source/Precondition.php @@ -3,7 +3,7 @@ /* # ***** BEGIN LICENSE BLOCK ***** # This file is part of InDefero, an open source project management application. -# Copyright (C) 2008 Céondo Ltd and contributors. +# 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 From 0f9f337e669ca150f3a4f090907b5f214b19145f Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 13 Sep 2010 00:53:24 +0000 Subject: [PATCH 22/23] * configure whether or not to set remote client authentication for IDF -> remote_stdio * hook into IDF_Project::preDelete, IDF_Key::postSave and IDF_Key::preDelete * this is all not quite finished, but a big leap forward to completion --- src/IDF/Plugin/SyncMonotone.php | 578 ++++++++++++++++-- .../Plugin/SyncMonotone/monotonerc-auth.tpl | 63 ++ .../{monotonerc.tpl => monotonerc-noauth.tpl} | 12 +- src/IDF/Scm/Monotone/Stdio.php | 53 +- src/IDF/conf/idf.php-dist | 15 +- src/IDF/relations.php | 6 + 6 files changed, 663 insertions(+), 64 deletions(-) create mode 100644 src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl rename src/IDF/Plugin/SyncMonotone/{monotonerc.tpl => monotonerc-noauth.tpl} (93%) diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index ecbb301..6fa9aca 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -35,28 +35,39 @@ class IDF_Plugin_SyncMonotone $plug = new IDF_Plugin_SyncMonotone(); switch ($signal) { case 'IDF_Project::created': - $plug->processMonotoneCreate($params['project']); + $plug->processProjectCreate($params['project']); + break; + case 'IDF_Project::preDelete': + $plug->processProjectDelete($params['project']); + break; + case 'IDF_Key::postSave': + $plug->processKeyCreate($params['key']); + break; + case 'IDF_Key::preDelete': + $plug->processKeyDelete($params['key']); break; case 'mtnpostpush.php::run': - $plug->processSyncTimeline($params); + $plug->processSyncTimeline($params['project']); break; } } /** - * Four steps to setup a new monotone project: + * Initial 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 + * 3) create a new client key for IDF and store it in the project conf + * 4) write monotonerc + * 5) add the database as new local server in the usher configuration + * 6) reload the running usher instance so it acknowledges the new server + * 7) create read-/write-permissions for the project and add all public + * keys to the project * * @param IDF_Project */ - function processMonotoneCreate($project) + function processProjectCreate($project) { if ($project->getConf()->getVal('scm') != 'mtn') { return; @@ -82,7 +93,7 @@ class IDF_Plugin_SyncMonotone __('Could not find mtn-post-push script "%s".'), $mtnpostpush )); } - + $shortname = $project->shortname; $projectpath = sprintf($projecttempl, $shortname); if (file_exists($projectpath)) { @@ -101,18 +112,8 @@ class IDF_Plugin_SyncMonotone // 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 - )); - } + $cmd = sprintf('db init -d %s', escapeshellarg($dbfile)); + self::_mtn_exec($cmd); // // step 2) create a server key @@ -126,43 +127,92 @@ class IDF_Plugin_SyncMonotone $server = $parsed['host']; } - $keyname = $shortname.'-server@'.$server; - $cmd = sprintf( - Pluf::f('mtn_path', 'mtn').' au generate_key --confdir=%s %s ""', + $serverkey = $shortname.'-server@'.$server; + $cmd = sprintf('au generate_key --confdir=%s %s ""', escapeshellarg($projectpath), - escapeshellarg($keyname) + escapeshellarg($serverkey) ); + self::_mtn_exec($cmd); - $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) create a client key, and save it in IDF + // + $clientkey_hash = ''; + $monotonerc_tpl = 'monotonerc-noauth.tpl'; + + if (Pluf::f('mtn_remote_auth', true)) { + $monotonerc_tpl = 'monotonerc-auth.tpl'; + $keydir = Pluf::f('tmp_folder').'/mtn-client-keys'; + if (!file_exists($keydir)) { + if (!mkdir($keydir)) { + throw new IDF_Scm_Exception(sprintf( + __('The key directory %s could not be created.'), $keydir + )); + } + } + + $clientkey_name = $shortname.'-client@'.$server; + $cmd = sprintf('au generate_key --keydir=%s %s ""', + escapeshellarg($keydir), + escapeshellarg($clientkey_name) + ); + $keyinfo = self::_mtn_exec($cmd); + + $parsed_keyinfo = array(); + try { + $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo); + } + catch (Exception $e) { + echo $e->getTraceAsString(); exit; + throw new IDF_Scm_Exception(sprintf( + __('Could not parse key information: %s'), $e->getMessage() + )); + } + + $clientkey_hash = $parsed_keyinfo[0][1]['hash']; + $clientkey_file = $keydir . '/' . $clientkey_name . '.' . $clientkey_hash; + $clientkey_data = file_get_contents($clientkey_file); + + $project->getConf()->setVal('mtn_client_key_name', $clientkey_name); + $project->getConf()->setVal('mtn_client_key_hash', $clientkey_hash); + $project->getConf()->setVal('mtn_client_key_data', $clientkey_data); + + // add the public client key to the server + $cmd = sprintf('au get_public_key --keydir=%s %s', + escapeshellarg($keydir), + escapeshellarg($clientkey_hash) + ); + $clientkey_pubdata = self::_mtn_exec($cmd); + + $cmd = sprintf('au put_public_key --confdir=%s %s', + escapeshellarg($projectpath), + escapeshellarg($clientkey_pubdata) + ); + $keyinfo = self::_mtn_exec($cmd); } // - // step 3) write monotonerc for access control - // FIXME: netsync access control is still missing! - // - $monotonerc = file_get_contents(dirname(__FILE__) . "/SyncMonotone/monotonerc.tpl"); + // step 4) write monotonerc + // + $monotonerc = file_get_contents( + dirname(__FILE__).'/SyncMonotone/'.$monotonerc_tpl + ); $monotonerc = str_replace( - array("%%MTNPOSTPUSH%%", "%%PROJECT%%"), - array($mtnpostpush, $shortname), + array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'), + array($mtnpostpush, $shortname, $clientkey_hash), $monotonerc ); $rcfile = $projectpath.'/monotonerc'; - if (!file_put_contents($rcfile, $monotonerc, LOCK_EX)) { + if (file_put_contents($rcfile, $monotonerc, LOCK_EX) === false) { 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 + // step 5) read in and append the usher config with the new server // $usher_rc = file_get_contents($usher_config); $parsed_config = array(); @@ -177,13 +227,10 @@ class IDF_Plugin_SyncMonotone } // ensure we haven't configured a server with this name already - foreach ($parsed_config as $stanzas) - { - foreach ($stanzas as $stanza_line) - { + foreach ($parsed_config as $stanzas) { + foreach ($stanzas as $stanza_line) { if ($stanza_line['key'] == 'server' && - $stanza_line['values'][0] == $shortname) - { + $stanza_line['values'][0] == $shortname) { throw new IDF_Scm_Exception(sprintf( __('usher configuration already contains a server '. 'entry named "%s"'), @@ -206,44 +253,463 @@ class IDF_Plugin_SyncMonotone // 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)) { + if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) { 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 + // step 6) reload usher to pick up the new configuration // IDF_Scm_Monotone_Usher::reload(); + + // + // step 7) add public monotone keys for the project to + // read-permissions, write-permissions and the database + // + $mtn = IDF_Scm_Monotone::factory($project); + $stdio = $mtn->getStdio(); + + $auth_ids = self::getAuthorizedUserIds($project); + $key_ids = array(); + foreach ($auth_ids as $auth_id) { + $sql = new Pluf_SQL('user=%s', array($auth_id)); + $keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen())); + foreach ($keys as $key) { + if ($key->getType() != 'mtn') + continue; + $stdio->exec(array('put_public_key', $key->content)); + $key_ids[] = $key->getMtnId(); + } + } + + $write_permissions = implode("\n", $key_ids); + $rcfile = $projectpath.'/write-permissions'; + if (file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write write-permissions file "%s"'), $rcfile + )); + } + + if ($project->private) { + $stanza = array( + array('key' => 'pattern', 'values' => array('*')), + ); + foreach ($key_ids as $key_id) + { + $stanza[] = array('key' => 'allow', 'values' => array($key_id)); + } + } + else { + $stanza = array( + array('key' => 'pattern', 'values' => array('*')), + array('key' => 'allow', 'values' => array('*')), + ); + } + $read_permissions = IDF_Scm_Monotone_BasicIO::compile(array($stanza)); + $rcfile = $projectpath.'/read-permissions'; + if (file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write read-permissions file "%s"'), $rcfile + )); + } + } + + /** + * Clean up after a mtn project was deleted + * + * @param IDF_Project + */ + public function processProjectDelete($project) + { + if ($project->getConf()->getVal('scm') != 'mtn') { + return; + } + + $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.' + ); + } + + $shortname = $project->shortname; + IDF_Scm_Monotone_Usher::killServer($shortname); + + $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.' + ); + } + + $projectpath = sprintf($projecttempl, $shortname); + if (file_exists($projectpath)) { + if (!self::_delete_recursive($projectpath)) { + throw new IDF_Scm_Exception(sprintf( + __('One or more paths underknees %s could not be deleted.'), $projectpath + )); + } + } + + if (Pluf::f('mtn_remote_auth', true)) { + $keydir = Pluf::f('tmp_folder').'/mtn-client-keys'; + $keyname = $project->getConf()->getVal('mtn_client_key_name', false); + $keyhash = $project->getConf()->getVal('mtn_client_key_hash', false); + if ($keyname && $keyhash && + file_exists($keydir .'/'. $keyname . '.' . $keyhash)) { + if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not delete client private key %s'), $keyname + )); + } + } + } + + $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() + )); + } + + foreach ($parsed_config as $idx => $stanzas) { + foreach ($stanzas as $stanza_line) { + if ($stanza_line['key'] == 'server' && + $stanza_line['values'][0] == $shortname) { + unset($parsed_config[$idx]); + break; + } + } + } + + $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) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write usher configuration file "%s"'), $usher_config + )); + } + + IDF_Scm_Monotone_Usher::reload(); + } + + /** + * Adds the (monotone) key to all monotone projects of this forge + * where the user of the key has write access to + */ + public function processKeyCreate($key) + { + if ($key->getType() != '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.' + ); + } + + foreach (Pluf::factory('IDF_Project')->getList() as $project) { + $conf = new IDF_Conf(); + $conf->setProject($project); + $scm = $conf->getVal('scm', 'mtn'); + if ($scm != 'mtn') + continue; + + $shortname = $project->shortname; + $projectpath = sprintf($projecttempl, $shortname); + if (!file_exists($projectpath)) { + throw new IDF_Scm_Exception(sprintf( + __('The project path %s does not exists.'), $projectpath + )); + } + + $auth_ids = self::getAuthorizedUserIds($project); + if (!in_array($key->user, $auth_ids)) + continue; + + $mtn_key_id = $key->getMtnId(); + + // if the project is not defined as private, all people have + // read access already, so we don't need to write anything + // and we currently do not check if read-permissions really + // contains + // pattern "*" + // allow "*" + // which is the default for non-private projects + if ($project->private == true) { + $read_perms = file_get_contents($projectpath.'/read-permissions'); + $parsed_read_perms = array(); + try { + $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms); + } + catch (Exception $e) { + throw new IDF_Scm_Exception(sprintf( + __('Could not parse read-permissions for project "%s": %s'), + $shortname, $e->getMessage() + )); + } + + $wildcard_section = null; + foreach ($parsed_read_perms as $stanzas) { + foreach ($stanzas as $stanza_line) { + if ($stanza_line['key'] == 'pattern' && + $stanza_line['values'][0] == '*') { + $wildcard_section =& $stanzas; + break; + } + } + } + + if ($wildcard_section == null) + { + $wildcard_section = array( + array('key' => 'pattern', 'values' => array('*')) + ); + $parsed_read_perms[] =& $wildcard_section; + } + + $key_found = false; + foreach ($wildcard_section as $line) + { + if ($line['key'] == 'allow' && $line['values'][0] == $mtn_key_id) { + $key_found = true; + break; + } + } + + if (!$key_found) { + $wildcard_section[] = array( + 'key' => 'allow', 'values' => array($mtn_key_id) + ); + } + + $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms); + + if (file_put_contents($projectpath.'/read-permissions', + $read_perms, LOCK_EX) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write read-permissions for project "%s"'), $shortname + )); + } + } + + $write_perms = file_get_contents($projectpath.'/write-permissions'); + $lines = preg_split("/(\n|\r\n)/", $write_perms); + if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) { + $lines[] = $mtn_key_id; + } + if (file_put_contents($projectpath.'/write-permissions', + implode("\n", $lines), LOCK_EX) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write write-permissions file for project "%s"'), + $shortname + )); + } + + $mtn = IDF_Scm_Monotone::factory($project); + $stdio = $mtn->getStdio(); + $stdio->exec(array('put_public_key', $key->content)); + } + } + + /** + * Removes the (monotone) key from all monotone projects of this forge + * where the user of the key has write access to + */ + public function processKeyDelete($key) + { + if ($key->getType() != '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.' + ); + } + + foreach (Pluf::factory('IDF_Project')->getList() as $project) { + $conf = new IDF_Conf(); + $conf->setProject($project); + $scm = $conf->getVal('scm', 'mtn'); + if ($scm != 'mtn') + continue; + + $shortname = $project->shortname; + $projectpath = sprintf($projecttempl, $shortname); + if (!file_exists($projectpath)) { + throw new IDF_Scm_Exception(sprintf( + __('The project path %s does not exists.'), $projectpath + )); + } + + $auth_ids = self::getAuthorizedUserIds($project); + if (!in_array($key->user, $auth_ids)) + continue; + + $mtn_key_id = $key->getMtnId(); + + // if the project is not defined as private, all people have + // read access already, so we don't need to write anything + // and we currently do not check if read-permissions really + // contains + // pattern "*" + // allow "*" + // which is the default for non-private projects + if ($project->private === true) { + $read_perms = file_get_contents($projectpath.'/read-permissions'); + $parsed_read_perms = array(); + try { + $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms); + } + catch (Exception $e) { + throw new IDF_Scm_Exception(sprintf( + __('Could not parse read-permissions for project "%s": %s'), + $shortname, $e->getMessage() + )); + } + + // while we add new keys only to an existing wild-card entry + // we remove dropped keys from all sections since the key + // should be simply unavailable for all of them + foreach ($parsed_read_perms as $stanzas) { + for ($i=0; $igetStdio(); + // if the public key did not sign any revisions, drop it from + // the database as well + if (strlen($stdio->exec(array('select', 'k:' . $mtn_key_id))) == 0) { + $stdio->exec(array('drop_public_key', $mtn_key_id)); + } + } + } + + private static function getAuthorizedUserIds($project) + { + $mem = $project->getMembershipData(); + $members = array_merge((array)$mem['members'], + (array)$mem['owners'], + (array)$mem['authorized']); + $userids = array(); + foreach ($members as $member) { + $userids[] = $member->id; + } + return $userids; } /** * Update the timeline after a push * */ - public function processSyncTimeline($params) + public function processSyncTimeline($project_name) { - $pname = $params['project']; try { - $project = IDF_Project::getOr404($pname); + $project = IDF_Project::getOr404($project_name); } catch (Pluf_HTTP_Error404 $e) { Pluf_Log::event(array( - 'IDF_Plugin_SyncMonotone::processSyncTimeline', + 'IDF_Plugin_SyncMonotone::processSyncTimeline', 'Project not found.', - array($pname, $params) + array($project_name, $params) )); return false; // Project not found } Pluf_Log::debug(array( - 'IDF_Plugin_SyncMonotone::processSyncTimeline', - 'Project found', $pname, $project->id + 'IDF_Plugin_SyncMonotone::processSyncTimeline', + 'Project found', $project_name, $project->id )); IDF_Scm::syncTimeline($project, true); Pluf_Log::event(array( 'IDF_Plugin_SyncMonotone::processSyncTimeline', - 'sync', array($pname, $project->id) + 'sync', array($project_name, $project->id) )); } + + private static function _mtn_exec($cmd) + { + $fullcmd = sprintf('%s %s %s', + Pluf::f('idf_exec_cmd_prefix', ''), + Pluf::f('mtn_path', 'mtn'), + $cmd + ); + + $output = $return = null; + exec($fullcmd, $output, $return); + if ($return != 0) { + throw new IDF_Scm_Exception(sprintf( + __('The command "%s" could not be executed.'), $cmd + )); + } + return implode("\n", $output); + } + + private static function _delete_recursive($path) + { + if (is_file($path)) { + return @unlink($path); + } + + if (is_dir($path)) { + $scan = glob(rtrim($path, '/') . '/*'); + $status = 0; + foreach ($scan as $subpath) { + $status |= self::_delete_recursive($subpath); + } + $status |= rmdir($path); + return $status; + } + } } diff --git a/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl b/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl new file mode 100644 index 0000000..daf3307 --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl @@ -0,0 +1,63 @@ +-- ***** 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 ***** + +-- +-- controls the access rights for remote_stdio which is used by IDFs frontend +-- and other interested parties +-- +function get_remote_automate_permitted(key_identity, command, options) + if (key_identity.id == "%%MTNCLIENTKEY%%") then + return true + end + + return false +end + +-- +-- let IDF know of new arriving revisions to fill its timeline +-- +_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/Plugin/SyncMonotone/monotonerc.tpl b/src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl similarity index 93% rename from src/IDF/Plugin/SyncMonotone/monotonerc.tpl rename to src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl index 6c74813..c0c0050 100644 --- a/src/IDF/Plugin/SyncMonotone/monotonerc.tpl +++ b/src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl @@ -1,6 +1,6 @@ -- ***** BEGIN LICENSE BLOCK ***** -- This file is part of InDefero, an open source project management application. --- Copyright (C) 2008 Céondo Ltd and contributors. +-- 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 @@ -20,6 +20,7 @@ -- -- controls the access rights for remote_stdio which is used by IDFs frontend +-- and other interested parties -- function get_remote_automate_permitted(key_identity, command, options) local read_only_commands = { @@ -38,10 +39,13 @@ function get_remote_automate_permitted(key_identity, command, options) return true end end - + return false end +-- +-- let IDF know of new arriving revisions to fill its timeline +-- _idf_revs = {} function note_netsync_start(session_id) _idf_revs[session_id] = {} @@ -61,11 +65,11 @@ function note_netsync_end (session_id, ...) 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/Stdio.php b/src/IDF/Scm/Monotone/Stdio.php index b447955..fa243d8 100644 --- a/src/IDF/Scm/Monotone/Stdio.php +++ b/src/IDF/Scm/Monotone/Stdio.php @@ -62,6 +62,55 @@ class IDF_Scm_Monotone_Stdio $this->stop(); } + /** + * Returns a string with additional options which are passed to + * an mtn instance connecting to remote databases + * + * @return string + */ + public function _getAuthOptions() + { + // no remote authentication - the simple case + if (!Pluf::f('mtn_remote_auth', true)) { + return '--key= '; + } + + $prjconf = $this->project->getConf(); + $name = $prjconf->getVal('mtn_client_key_name', false); + $hash = $prjconf->getVal('mtn_client_key_hash', false); + + if (!$name || !$hash) { + throw new IDF_Scm_Exception(sprintf( + __('Monotone client key name or hash not in project conf.') + )); + } + + $keydir = Pluf::f('tmp_folder').'/mtn-client-keys'; + if (!file_exists($keydir)) { + if (!mkdir($keydir)) { + throw new IDF_Scm_Exception(sprintf( + __('The key directory %s could not be created.'), $keydir + )); + } + } + + // in case somebody cleaned out the cache, we restore the key here + $keyfile = $keydir . '/' . $name .'.'. $hash; + if (!file_exists($keyfile)) { + $data = $prjconf->getVal('mtn_client_key_data'); + if (!file_put_contents($keyfile, $data, LOCK_EX)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write client key "%s"'), $keyfile + )); + } + } + + return sprintf('--keydir=%s --key=%s ', + escapeshellarg($keydir), + escapeshellarg($hash) + ); + } + /** * Starts the stdio process and resets the command counter */ @@ -80,9 +129,8 @@ class IDF_Scm_Monotone_Stdio $cmd .= sprintf('%s ', escapeshellarg($opt)); } - // FIXME: we might want to add an option for anonymous / no key - // access, but upstream bug #30237 prevents that for now if ($remote_db_access) { + $cmd .= $this->_getAuthOptions(); $host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname); $cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host)); } @@ -104,7 +152,6 @@ class IDF_Scm_Monotone_Stdio ); $env = array('LANG' => 'en_US.UTF-8'); - $this->proc = proc_open($cmd, $descriptors, $this->pipes, null, $env); diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 9339a5a..d4bcd5c 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -76,7 +76,7 @@ $cfg['svn_remote_url'] = 'http://localhost/svn/%s'; # 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', '--no-standard-rcfiles', '--key='); +$cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles'); # # You can setup monotone for use with indefero in several ways. The # two most-used should be: @@ -157,6 +157,19 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s'; # $cfg['mtn_db_access'] = 'remote'; # +# If true, each access to the database is authenticated with an auto-generated +# project key which is stored in the IDF project configuration +# ('mtn_client_key_*') and written out to $cfg['tmp_folder']/mtn-client-keys +# for its actual use. This key is then configured on the server to have +# full read / write access to all functions, while anonymous access can be +# completely disabled. +# If false, IDF tries to connect anonymously, without authentication, to +# the remote monotone server instance. In this case no project-specific +# keys are generated and the server must be configured to allow at least +# anonymous read access to the main functions. +# +$cfg['mtn_remote_auth'] = true; +# # If configured, this allows basic control of a running usher process # via the forge administration. The variable must point to the full (writable) # path of the usher configuration file which gets updated when new projects diff --git a/src/IDF/relations.php b/src/IDF/relations.php index 6734515..16dd8b4 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -88,6 +88,12 @@ Pluf_Signal::connect('gitpostupdate.php::run', # monotone synchronization Pluf_Signal::connect('IDF_Project::created', array('IDF_Plugin_SyncMonotone', 'entry')); +Pluf_Signal::connect('IDF_Project::preDelete', + array('IDF_Plugin_SyncMonotone', 'entry')); +Pluf_Signal::connect('IDF_Key::postSave', + array('IDF_Plugin_SyncMonotone', 'entry')); +Pluf_Signal::connect('IDF_Key::preDelete', + array('IDF_Plugin_SyncMonotone', 'entry')); Pluf_Signal::connect('phppostpush.php::run', array('IDF_Plugin_SyncMonotone', 'entry')); From a32d6d826570657f6ae3eba7b40de36274e3d084 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 13 Sep 2010 01:13:49 +0000 Subject: [PATCH 23/23] * its late - put_public_key of course needs a specific database * do not throw around exceptions if a key which should be removed is not found in the database --- src/IDF/Plugin/SyncMonotone.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index 6fa9aca..b347d37 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -163,7 +163,6 @@ class IDF_Plugin_SyncMonotone $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo); } catch (Exception $e) { - echo $e->getTraceAsString(); exit; throw new IDF_Scm_Exception(sprintf( __('Could not parse key information: %s'), $e->getMessage() )); @@ -184,11 +183,11 @@ class IDF_Plugin_SyncMonotone ); $clientkey_pubdata = self::_mtn_exec($cmd); - $cmd = sprintf('au put_public_key --confdir=%s %s', - escapeshellarg($projectpath), + $cmd = sprintf('au put_public_key --db=%s %s', + escapeshellarg($dbfile), escapeshellarg($clientkey_pubdata) ); - $keyinfo = self::_mtn_exec($cmd); + self::_mtn_exec($cmd); } // @@ -631,8 +630,13 @@ class IDF_Plugin_SyncMonotone $stdio = $mtn->getStdio(); // if the public key did not sign any revisions, drop it from // the database as well - if (strlen($stdio->exec(array('select', 'k:' . $mtn_key_id))) == 0) { - $stdio->exec(array('drop_public_key', $mtn_key_id)); + try { + if (strlen($stdio->exec(array('select', 'k:' . $mtn_key_id))) == 0) { + $stdio->exec(array('drop_public_key', $mtn_key_id)); + } + } catch (IDF_Scm_Exception $e) { + if (strpos($e->getMessage(), 'there is no key named') === false) + throw $e; } } }