From 9ccbcea743d4a97e945b690efc2912bf289bc45d Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Wed, 5 Jan 2011 12:07:57 +0100 Subject: [PATCH 01/15] Fixed issue 557, better CSS to render UL/OL. --- www/media/idf/css/style.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index ef64f05..a36e94c 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -702,6 +702,14 @@ div.deprecated-page { color: #a00; } +ul > li { + list-style: disc outside none; +} + +ol > li { + list-style: decimal outside none; +} + #branding { float: right; position: relative; From fd7a53a854329b4f03708b36eab439ecb750ddcf Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Wed, 5 Jan 2011 15:34:01 +0100 Subject: [PATCH 02/15] According to the base64 standard, zero, one or two fill bytes ("=") might pop up at the end, so always expecting "==" is plainly wrong (originates from 0897c860, fixes issue 592) --- src/IDF/Form/UserAccount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Form/UserAccount.php b/src/IDF/Form/UserAccount.php index 7b6a5cb..d09417d 100644 --- a/src/IDF/Form/UserAccount.php +++ b/src/IDF/Form/UserAccount.php @@ -295,7 +295,7 @@ class IDF_Form_UserAccount extends Pluf_Form return ''; } - if (preg_match('#^ssh\-[a-z]{3}\s\S+==(\s\S+)?$#', $key)) { + if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) { $key = str_replace(array("\n", "\r"), '', $key); if (Pluf::f('idf_strong_key_check', false)) { From c7c39c6fa13285d133fcda302eee71c2434accf0 Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Wed, 5 Jan 2011 17:02:06 +0100 Subject: [PATCH 03/15] Implementation of the watch-list viewer Fix issue 589 --- src/IDF/Views/Issue.php | 165 ++++++++++++++++++ src/IDF/conf/urls.php | 10 ++ src/IDF/templates/idf/issues/base.html | 3 +- .../templates/idf/issues/forge-watchlist.html | 12 ++ .../idf/issues/project-watchlist.html | 17 ++ src/IDF/templates/idf/user/dashboard.html | 2 + 6 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/IDF/templates/idf/issues/forge-watchlist.html create mode 100644 src/IDF/templates/idf/issues/project-watchlist.html diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 7dbafee..77a0213 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -78,6 +78,158 @@ class IDF_Views_Issue $params, $request); } + /** + * View the issues watch list of a given user. + * Limited to a specified project + */ + public $watchList_precond = array('IDF_Precondition::accessIssues', + 'Pluf_Precondition::loginRequired'); + public function watchList($request, $match) + { + $prj = $request->project; + $otags = $prj->getTagIdsByStatus('open'); + $ctags = $prj->getTagIdsByStatus('closed'); + if (count($otags) == 0) $otags[] = 0; + if (count($ctags) == 0) $ctags[] = 0; + + // Get the id list of issue in the user watch list (for all projects !) + $db =& Pluf::db(); + $issues_id = $db->select('SELECT GROUP_CONCAT(idf_issue_id) as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id.' GROUP BY pluf_user_id'); + if (empty ($issues_id)) $issues_id = ""; + else $issues_id = $issues_id[0]['id']; + + // Count open and close issues + $sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); + $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); + $sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); + $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); + + // Generate a filter for the paginator + switch ($match[2]) { + case 'closed': + $title = sprintf(__('Watch List: Closed Issues for %s'), (string) $prj); + $summary = __('This table shows the closed issues in your watch list for %s project.', (string) $prj); + $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); + break; + case 'open': + default: + $title = sprintf(__('Watch List: Open Issues for %s'), (string) $prj); + $summary = __('This table shows the open issues in your watch list for %s project.', (string) $prj); + $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); + break; + } + + // Paginator to paginate the issues + $pag = new Pluf_Paginator(new IDF_Issue()); + $pag->class = 'recent-issues'; + $pag->item_extra_props = array('project_m' => $prj, + 'shortname' => $prj->shortname, + 'current_user' => $request->user); + $pag->summary = $summary; + $pag->forced_where = $f_sql; + $pag->action = array('IDF_Views_Issue::watchList', array($prj->shortname, $match[1])); + $pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted + $pag->sort_reverse_order = array('modif_dtime'); + $pag->sort_link_title = true; + $pag->extra_classes = array('a-c', '', 'a-c', ''); + $list_display = array( + 'id' => __('Id'), + array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')), + array('status', 'IDF_Views_Issue_ShowStatus', __('Status')), + array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')), + ); + $pag->configure($list_display, array(), array('id', 'status', 'modif_dtime')); + $pag->items_per_page = 10; + $pag->no_results_text = __('No issues were found.'); + $pag->setFromRequest($request); + return Pluf_Shortcuts_RenderToResponse('idf/issues/project-watchlist.html', + array('project' => $prj, + 'page_title' => $title, + 'open' => $nb_open, + 'closed' => $nb_closed, + 'issues' => $pag, + ), + $request); + } + + /** + * View the issues watch list of a given user. + * For all projects + */ + public $forgeWatchList_precond = array('Pluf_Precondition::loginRequired'); + public function forgeWatchList($request, $match) + { + $otags = array(); + $ctags = array(); + // Note that this approach does not scale, we will need to add + // a table to cache the meaning of the tags for large forges. + foreach (IDF_Views::getProjects($request->user) as $project) { + $otags = array_merge($otags, $project->getTagIdsByStatus('open')); + } + foreach (IDF_Views::getProjects($request->user) as $project) { + $ctags = array_merge($ctags, $project->getTagIdsByStatus('closed')); + } + if (count($otags) == 0) $otags[] = 0; + if (count($ctags) == 0) $ctags[] = 0; + + // Get the id list of issue in the user watch list (for all projects !) + $db =& Pluf::db(); + $issues_id = $db->select('SELECT GROUP_CONCAT(idf_issue_id) as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id.' GROUP BY pluf_user_id'); + if (empty ($issues_id)) $issues_id = ""; + else $issues_id = $issues_id[0]['id']; + + // Count open and close issues + $sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array()); + $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); + $sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array()); + $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); + + // Generate a filter for the paginator + switch ($match[1]) { + case 'closed': + $title = sprintf(__('Watch List: Closed Issues')); + $summary = __('This table shows the closed issues in your watch list.'); + $f_sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array()); + break; + case 'open': + default: + $title = sprintf(__('Watch List: Open Issues')); + $summary = __('This table shows the open issues in your watch list.'); + $f_sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array()); + break; + } + + // Paginator to paginate the issues + $pag = new Pluf_Paginator(new IDF_Issue()); + $pag->class = 'recent-issues'; + $pag->item_extra_props = array('current_user' => $request->user); + $pag->summary = $summary; + $pag->forced_where = $f_sql; + $pag->action = array('IDF_Views_Issue::forgeWatchList', array($match[1])); + $pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted + $pag->sort_reverse_order = array('modif_dtime'); + $pag->sort_link_title = true; + $pag->extra_classes = array('a-c', '', 'a-c', ''); + $list_display = array( + 'id' => __('Id'), + array('summary', 'IDF_Views_Issue_SummaryAndLabelsUnknownProject', __('Summary')), + array('project', 'Pluf_Paginator_FkToString', __('Project')), + array('status', 'IDF_Views_Issue_ShowStatus', __('Status')), + array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')), + ); + $pag->configure($list_display, array(), array('id', 'project', 'status', 'modif_dtime')); + $pag->items_per_page = 10; + $pag->no_results_text = __('No issues were found.'); + $pag->setFromRequest($request); + return Pluf_Shortcuts_RenderToResponse('idf/issues/forge-watchlist.html', + array('page_title' => $title, + 'open' => $nb_open, + 'closed' => $nb_closed, + 'issues' => $pag, + ), + $request); + } + /** * View the issues of a given user. * @@ -540,6 +692,17 @@ class IDF_Views_Issue } } +/** + * When you access to your forge watch list, issue don't known + * the project shortname. + */ +function IDF_Views_Issue_SummaryAndLabelsUnknownProject($field, $issue, $extra='') +{ + $shortname = $issue->get_project()->shortname; + $issue->__set('shortname', $shortname); + return IDF_Views_Issue_SummaryAndLabels ($field, $issue, $extra); +} + /** * Display the summary of an issue, then on a new line, display the * list of labels with a link to a view "by label only". @@ -576,3 +739,5 @@ function IDF_Views_Issue_ShowStatus($field, $issue, $extra='') { return Pluf_esc($issue->get_status()->name); } + + diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 1afe134..b341580 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -141,6 +141,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#', 'model' => 'IDF_Views_Issue', 'method' => 'viewAttachment'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/watchlist/(\w+)$#', + 'base' => $base, + 'model' => 'IDF_Views_Issue', + 'method' => 'watchList'); + +$ctl[] = array('regex' => '#^/watchlist/(\w+)$#', + 'base' => $base, + 'model' => 'IDF_Views_Issue', + 'method' => 'forgeWatchList'); + // ---------- SCM ---------------------------------------- $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#', diff --git a/src/IDF/templates/idf/issues/base.html b/src/IDF/templates/idf/issues/base.html index 8c142f8..779de34 100644 --- a/src/IDF/templates/idf/issues/base.html +++ b/src/IDF/templates/idf/issues/base.html @@ -3,7 +3,8 @@ {block subtabs}
{trans 'Open Issues'} -{if !$user.isAnonymous()} | {trans 'New Issue'} | {trans 'My Issues'}{/if} | +{if !$user.isAnonymous()} | {trans 'New Issue'} | {trans 'My Issues'} +| {trans 'My watch list'}{/if} |
diff --git a/src/IDF/templates/idf/issues/forge-watchlist.html b/src/IDF/templates/idf/issues/forge-watchlist.html new file mode 100644 index 0000000..b6d2884 --- /dev/null +++ b/src/IDF/templates/idf/issues/forge-watchlist.html @@ -0,0 +1,12 @@ +{extends "idf/base-simple.html"} + +{block body} +{$issues.render} +{/block} + +{block context} +{aurl 'open_url', 'IDF_Views_Issue::forgeWatchList', array('open')} +{aurl 'closed_url', 'IDF_Views_Issue::forgeWatchList', array('closed')} +{blocktrans}

Open issues: {$open}

+

Closed issues: {$closed}

{/blocktrans} +{/block} diff --git a/src/IDF/templates/idf/issues/project-watchlist.html b/src/IDF/templates/idf/issues/project-watchlist.html new file mode 100644 index 0000000..ecc9241 --- /dev/null +++ b/src/IDF/templates/idf/issues/project-watchlist.html @@ -0,0 +1,17 @@ +{extends "idf/issues/base.html"} + +{block docclass}yui-t2{assign $inWatchList = true}{/block} + +{block body} +{$issues.render} +{if !$user.isAnonymous()} +{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)} +

+ {trans 'New Issue'}

{/if} +{/block} + +{block context} +{aurl 'open_url', 'IDF_Views_Issue::watchList', array($project.shortname, 'open')} +{aurl 'closed_url', 'IDF_Views_Issue::watchList', array($project.shortname, 'closed')} +{blocktrans}

Open issues: {$open}

+

Closed issues: {$closed}

{/blocktrans} +{/block} diff --git a/src/IDF/templates/idf/user/dashboard.html b/src/IDF/templates/idf/user/dashboard.html index 67f9856..584d837 100644 --- a/src/IDF/templates/idf/user/dashboard.html +++ b/src/IDF/templates/idf/user/dashboard.html @@ -11,5 +11,7 @@

{blocktrans}Update your account.{/blocktrans}

{aurl 'url', 'IDF_Views_User::view', array($user.login)}

{blocktrans}See your public profile.{/blocktrans}

+{aurl 'url', 'IDF_Views_Issue::forgeWatchList', array('open')} +

{blocktrans}See your forge issue watch list.{/blocktrans}

{/block} From a437da6a4c65c00e2fef9691e10c33aca4a1c7ed Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Wed, 5 Jan 2011 17:51:34 +0100 Subject: [PATCH 04/15] Fix sort option of forge watchlist --- src/IDF/Views/Issue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 77a0213..cd992c8 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -209,7 +209,7 @@ class IDF_Views_Issue $pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted $pag->sort_reverse_order = array('modif_dtime'); $pag->sort_link_title = true; - $pag->extra_classes = array('a-c', '', 'a-c', ''); + $pag->extra_classes = array('a-c', '', 'a-c', 'a-c', 'a-c'); $list_display = array( 'id' => __('Id'), array('summary', 'IDF_Views_Issue_SummaryAndLabelsUnknownProject', __('Summary')), From dd05a58c8c94f52eb24ed093eb0ea5381d181f02 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 6 Jan 2011 01:44:41 +0100 Subject: [PATCH 05/15] Major configuration changes for SyncMonotone - we're now using a predefined configuration tree as template for a new project and copy / symlink that on project creation. To make this process a little more configurable, two new configuration options, 'mtn_confdir' and 'mtn_confdir_extra', have been added which allow the forge admin to adapt the directory structure and its default hooks to his likings for all new projects. (More on that in doc/syncmonotone.mdtext). The 'mtn_remote_auth' configuration option was removed, because setting this to false would have not worked for setups which did not allow write access to remote automate commands for anonymous users and opening this would have meant a huge security hole. Instead, for every project which is created a corresponding client key is created as well which is used as authentication in the IDF source frontend. Finally the monolithic monotonerc file has been split up into individual, easily configurable lua files which are linked / copied underknees hooks.d/ and which do not conflict with each other (for example by overwriting certain main notification hooks). --- doc/syncmonotone.mdtext | 53 ++++- src/IDF/Plugin/SyncMonotone.php | 198 +++++++++++------- .../indefero_authorize_remote_automate.conf | 10 + .../indefero_authorize_remote_automate.lua | 88 ++++++++ .../hooks.d/indefero_post_push.conf.in | 2 + .../hooks.d/indefero_post_push.lua | 58 +++++ .../Plugin/SyncMonotone/monotonerc-auth.tpl | 79 ------- .../Plugin/SyncMonotone/monotonerc-noauth.tpl | 92 -------- src/IDF/Plugin/SyncMonotone/monotonerc.in | 30 +++ .../remote-automate-permissions.in | 1 + src/IDF/Scm/Monotone/Stdio.php | 5 - src/IDF/conf/idf.php-dist | 23 +- 12 files changed, 372 insertions(+), 267 deletions(-) create mode 100644 src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.conf create mode 100644 src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.lua create mode 100644 src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.conf.in create mode 100644 src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua delete mode 100644 src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl delete mode 100644 src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl create mode 100644 src/IDF/Plugin/SyncMonotone/monotonerc.in create mode 100644 src/IDF/Plugin/SyncMonotone/remote-automate-permissions.in diff --git a/doc/syncmonotone.mdtext b/doc/syncmonotone.mdtext index 7355810..c21a325 100644 --- a/doc/syncmonotone.mdtext +++ b/doc/syncmonotone.mdtext @@ -149,7 +149,6 @@ look like this: $cfg['mtn_repositories'] = '/var/lib/usher/projects/%s/'; $cfg['mtn_remote_url'] = 'mtn://my.server.com/%s'; $cfg['mtn_db_access'] = 'remote'; - $cfg['mtn_remote_auth'] = true; $cfg['mtn_usher_conf'] = '/var/lib/usher/usher.conf'; ... @@ -188,8 +187,10 @@ Remote commands can be helpful for a user or a 3rd party tool (like contents remotely without having to pull everything in first instance. Private projects on the other hand can only be synced by team members -or additional invited people. Also noo remote command execution is enabled -by default. +or additional invited people. Remote command execution is still enabled +by default - if you want to disable that, simply remove the symlink to +the file `indefero_authorize_remote_automate.conf` in your project's `hooks.d` +directory or copy the file from the original location and adapt it. ## Notifications @@ -204,8 +205,54 @@ in a directory called `hooks.d` right under the project's base directory (configured via $cfg['mtn_repositories']) and this is the ideal place to put or link these additional lua sources. +## Custom project configurations and templates + +If a new project is created in IDF, the SyncMonotone plugin creates a new +configuration tree for the project into the project's configuration directory, +determined by `$cfg['mtn_repositories']`. IDF ships with the minimum set of +files for this configuration tree and sets up everything automatically for you. + +Even more, most of the configuration files from the newly created tree are only +symlinked to the original configuration directory which is configurable via +`$cfg['mtn_confdir']` and defaults to `src/IDF/Plugin/SyncMonotone/`. This has +the advantage that your standard IDF setup automatically receives updates to +existing (symlinked) configuration files as soon as you update to a newer +version. + +You could, however, also choose to place the directory tree somewhere else +and adapt the contents of the individual files yourself, so these changes get +automatically applied to all new projects you create. You could even go so far +and add new files to the tree and let them be processed automatically just +as the basic files! All you need to do is to copy your files and / or directories +underknees your `$cfg['mtn_confdir']` and add their relative paths to +`$cfg['mtn_confdir_extra']`. + +By convention, all entries which end with a slash are considered directories, +so mkdir(1) is issued for these entries, all files which do not end up with +".in" are considered to be static script files which are just symlinked from +the basic configuration dir and all entries ending on ".in" are considered +configuration files or templates, which are copied over to the project's +configuration tree and which get some basic project-specific values replaced. + +The following placeholders are currently recognized and replaced for these files: + + * %%PROJECT%% - the name of the created project + * %%MTNPOSTPUSH%% - the absolute path to the `mtn-post-push` script + * %%MTNCLIENTKEY%% - the public key hash of the key which is used by IDF + to authenticate remote stdio access + +Thats it - I hope you find it useful :) + ## Q&A +### After I created a new project, IDF throws an exception and tells me that it couldn't save the membership data with a cryptic error message. Whats wrong? + +Multiple issues could cause that. If you've set up usher, make sure the usher +can fork your database at all and look out for specific errors in the log file +of your project. If you stumble upon permission issues, ensure that the user +who runs the usher can access all files in your project's configuration directory, +including symlinked files. + ### I pushed a branch to my server, but it does not show up in IDF. Whats wrong? Check if the heads of your branch are not suspended, i.e. do not carry a diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index 2da68cd..f75f530 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -62,7 +62,7 @@ class IDF_Plugin_SyncMonotone * 'mtn_repositories' * 2) create a new server key in the same directory * 3) create a new client key for IDF and store it in the project conf - * 4) write monotonerc + * 4) setup the configuration * 5) add the database as new local server in the usher configuration * 6) reload the running usher instance so it acknowledges the new server * @@ -101,6 +101,36 @@ class IDF_Plugin_SyncMonotone )); } + // check some static configuration files + $confdir = Pluf::f('mtn_confdir', false); + if ($confdir === false) { + $confdir = dirname(__FILE__).'/SyncMonotone/'; + } + $confdir_contents = array( + 'monotonerc.in', + 'remote-automate-permissions.in', + 'hooks.d/', + // this is linked and not copied to be able to update + // the list of read-only commands on upgrades + 'hooks.d/indefero_authorize_remote_automate.conf', + 'hooks.d/indefero_authorize_remote_automate.lua', + 'hooks.d/indefero_post_push.conf.in', + 'hooks.d/indefero_post_push.lua', + ); + // check whether we should handle additional files in the config directory + $confdir_extra_contents = Pluf::f('mtn_confdir_extra', false); + if ($confdir_extra_contents !== false) { + $confdir_contents = + array_merge($confdir_contents, $confdir_extra_contents); + } + foreach ($confdir_contents as $content) { + if (!file_exists($confdir.$content)) { + throw new IDF_Scm_Exception(sprintf( + __('The configuration file %s is missing.'), $content + )); + } + } + $shortname = $project->shortname; $projectpath = sprintf($projecttempl, $shortname); if (file_exists($projectpath)) { @@ -144,79 +174,97 @@ class IDF_Plugin_SyncMonotone // // 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) { + $keydir = Pluf::f('tmp_folder').'/mtn-client-keys'; + if (!file_exists($keydir)) { + if (!mkdir($keydir)) { throw new IDF_Scm_Exception(sprintf( - __('Could not parse key information: %s'), $e->getMessage() + __('The key directory %s could not be created.'), $keydir )); } - - $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 --db=%s %s', - escapeshellarg($dbfile), - escapeshellarg($clientkey_pubdata) - ); - self::_mtn_exec($cmd); } - // - // step 4) write monotonerc - // - $monotonerc = file_get_contents( - dirname(__FILE__).'/SyncMonotone/'.$monotonerc_tpl - ); - $monotonerc = str_replace( - array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'), - array($mtnpostpush, $shortname, $clientkey_hash), - $monotonerc + $clientkey_name = $shortname.'-client@'.$server; + $cmd = sprintf('au generate_key --keydir=%s %s ""', + escapeshellarg($keydir), + escapeshellarg($clientkey_name) ); + $keyinfo = self::_mtn_exec($cmd); - $rcfile = $projectpath.'/monotonerc'; - - if (file_put_contents($rcfile, $monotonerc, LOCK_EX) === false) { + $parsed_keyinfo = array(); + try { + $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo); + } + catch (Exception $e) { throw new IDF_Scm_Exception(sprintf( - __('Could not write mtn configuration file "%s"'), $rcfile + __('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 --db=%s %s', + escapeshellarg($dbfile), + escapeshellarg($clientkey_pubdata) + ); + self::_mtn_exec($cmd); + + // + // step 4) setup the configuration + // + + // we assume that all confdir entries ending with a slash mean a + // directory that has to be created, that all files ending on ".in" + // have to be processed and copied in place and that all other files + // just need to be symlinked from the original location + foreach ($confdir_contents as $content) { + $filepath = $projectpath.'/'.$content; + if (substr($content, -1) == '/') { + if (!mkdir($filepath)) { + throw new IDF_Scm_Exception(sprintf( + __('Could not create configuration directory "%s"'), $filepath + )); + } + continue; + } + + if (substr($content, -3) != '.in') { + if (!symlink($confdir.$content, $filepath)) { + IDF_Scm_Exception(sprintf( + __('Could not create symlink "%s"'), $filepath + )); + } + continue; + } + + $filecontents = file_get_contents($confdir.'/'.$content); + $filecontents = str_replace( + array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'), + array($mtnpostpush, $shortname, $clientkey_hash), + $filecontents + ); + + // remove the .in + $filepath = substr($filepath, 0, -3); + if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) { + throw new IDF_Scm_Exception(sprintf( + __('Could not write configuration file "%s"'), $filepath + )); + } + } + // // step 5) read in and append the usher config with the new server // @@ -252,7 +300,7 @@ class IDF_Plugin_SyncMonotone '--confdir', $projectpath, '-d', $dbfile, '--timestamps', - '--ticker=dot' + '--ticker=dot' )), ); @@ -378,17 +426,15 @@ class IDF_Plugin_SyncMonotone } } - 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 - )); - } + $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 + )); } } @@ -721,7 +767,7 @@ class IDF_Plugin_SyncMonotone private static function _delete_recursive($path) { - if (is_file($path)) { + if (is_file($path) || is_link($path)) { return @unlink($path); } diff --git a/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.conf b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.conf new file mode 100644 index 0000000..c38789f --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.conf @@ -0,0 +1,10 @@ +ARA_safe_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", "get_extended_manifest_of" +} diff --git a/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.lua b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.lua new file mode 100644 index 0000000..e66a64b --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_authorize_remote_automate.lua @@ -0,0 +1,88 @@ +-- ***** BEGIN LICENSE BLOCK ***** +-- This file is part of InDefero, an open source project management application. +-- Copyright (C) 2011 Céondo Ltd and contributors. +-- Copyright (C) 2010 Thomas Keller +-- Richard Levitte +-- +-- 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 reads key identities from a file "remote-automate-permissions" +-- in the configuration directory and permits those authenticating with one +-- of those keys to perform dangerous (read/write) remote automate operations. +-- The format of the file is very simple, one key identity on every line. +-- Lines starting with # are ignore, as well as empty lines. +-- +-- It's possible to configure this script to allow the performance of some +-- remote automate commands anonymously, through the variable +-- ARA_safe_commands, which has to be a table of commands as strings. +-- One example configuration, taken from the setup at code.monotone.ca, could +-- be this: +-- +-- ARA_safe_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", "get_extended_manifest_of" +-- } +-- +do + local _safe_commands = {} + if ARA_safe_commands then + _safe_commands = ARA_safe_commands + end + + local _save_get_remote_automate_permitted = get_remote_automate_permitted + function get_remote_automate_permitted(key_identity, command, options) + local permfile = + io.open(get_confdir() .. "/remote-automate-permissions", "r") + if (permfile == nil) then + return false + end + + -- See if the incoming key matches any of the key identities or + -- patterns found in the permissions file. + local matches = false + local line = permfile:read() + while (not matches and line ~= nil) do + if not globish_match("#*", line) then + local _, _, ln = string.find(line, "%s*([^%s]*)%s*") + if ln == "*" then matches = true end + if ln == key_identity.id then matches = true end + if globish_match(ln, key_identity.name) then matches = true end + line = permfile:read() + end + end + io.close(permfile) + if matches then return true end + + -- No matching key found, let's see if the command matches one the + -- admin allowed to be performed anonymously + for _,v in ipairs(_safe_commands) do + if (v == command[1]) then + return true + end + end + + -- No matches found anywhere, then don't permit this operation + return false + end +end diff --git a/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.conf.in b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.conf.in new file mode 100644 index 0000000..efeeaad --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.conf.in @@ -0,0 +1,2 @@ +IDF_project = "%%PROJECT%%" +IDF_push_script = "%%MTNPOSTPUSH%%" diff --git a/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua new file mode 100644 index 0000000..3038037 --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua @@ -0,0 +1,58 @@ +-- ***** BEGIN LICENSE BLOCK ***** +-- This file is part of InDefero, an open source project management application. +-- Copyright (C) 2011 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 ***** + +-- +-- let IDF know of new arriving revisions to fill its timeline +-- +_idf_revs = {} +push_hook_functions( + { + start = + function (session_id) + _idf_revs[session_id] = {} + return "continue",nil + end, + revision_received = + function (new_id, revision, certs, session_id) + table.insert(_idf_revs[session_id], new_id) + return "continue",nil + end, + ["end"] = + function (session_id, ...) + if table.getn(_idf_revs[session_id]) == 0 then + return "continue",nil + end + + local pin,pout,pid = spawn_pipe(IDF_push_script, IDF_project); + if pid == -1 then + print("could not execute " .. IDF_push_script) + return "continue",nil + end + + for _,r in ipairs(_idf_revs[session_id]) do + pin:write(r .. "\n") + end + pin:close() + + wait(pid) + return "continue",nil + end + }) + diff --git a/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl b/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl deleted file mode 100644 index c59d58f..0000000 --- a/src/IDF/Plugin/SyncMonotone/monotonerc-auth.tpl +++ /dev/null @@ -1,79 +0,0 @@ --- ***** 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 = {} -push_hook_functions({ - ["start"] = function (session_id) - _idf_revs[session_id] = {} - return "continue",nil - end, - ["revision_received"] = function (new_id, revision, certs, session_id) - table.insert(_idf_revs[session_id], new_id) - return "continue",nil - end, - ["end"] = function (session_id, ...) - if table.getn(_idf_revs[session_id]) == 0 then - return "continue",nil - end - - local pin,pout,pid = spawn_pipe("%%MTNPOSTPUSH%%", "%%PROJECT%%"); - if pid == -1 then - print("could not execute %%MTNPOSTPUSH%%") - return - end - - for _,r in ipairs(_idf_revs[session_id]) do - pin:write(r .. "\n") - end - pin:close() - - wait(pid) - return "continue",nil - end -}) - --- --- Load local hooks if they exist. --- --- The way this is supposed to work is that hooks.d can contain symbolic --- links to lua scripts. These links MUST have the extension .lua --- If the script needs some configuration, a corresponding file with --- the extension .conf is the right spot. --- --- First load the configuration of the hooks, if applicable -includedirpattern(get_confdir() .. "/hooks.d/", "*.conf") --- Then load the hooks themselves -includedirpattern(get_confdir() .. "/hooks.d/", "*.lua") - diff --git a/src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl b/src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl deleted file mode 100644 index 3ad79ba..0000000 --- a/src/IDF/Plugin/SyncMonotone/monotonerc-noauth.tpl +++ /dev/null @@ -1,92 +0,0 @@ --- ***** 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) - 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", "get_file_size", - "get_extended_manifest_of" - } - - for _,v in ipairs(read_only_commands) do - if (v == command[1]) then - return true - end - end - - return false -end - --- --- let IDF know of new arriving revisions to fill its timeline --- -_idf_revs = {} -push_hook_functions({ - ["start"] = function (session_id) - _idf_revs[session_id] = {} - return "continue",nil - end, - ["revision_received"] = function (new_id, revision, certs, session_id) - table.insert(_idf_revs[session_id], new_id) - return "continue",nil - end, - ["end"] = function (session_id, ...) - if table.getn(_idf_revs[session_id]) == 0 then - return "continue",nil - end - - local pin,pout,pid = spawn_pipe("%%MTNPOSTPUSH%%", "%%PROJECT%%"); - if pid == -1 then - print("could not execute %%MTNPOSTPUSH%%") - return - end - - for _,r in ipairs(_idf_revs[session_id]) do - pin:write(r .. "\n") - end - pin:close() - - wait(pid) - return "continue",nil - end -}) - --- --- Load local hooks if they exist. --- --- The way this is supposed to work is that hooks.d can contain symbolic --- links to lua scripts. These links MUST have the extension .lua --- If the script needs some configuration, a corresponding file with --- the extension .conf is the right spot. --- --- First load the configuration of the hooks, if applicable -includedirpattern(get_confdir() .. "/hooks.d/", "*.conf") --- Then load the hooks themselves -includedirpattern(get_confdir() .. "/hooks.d/", "*.lua") diff --git a/src/IDF/Plugin/SyncMonotone/monotonerc.in b/src/IDF/Plugin/SyncMonotone/monotonerc.in new file mode 100644 index 0000000..985ef49 --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/monotonerc.in @@ -0,0 +1,30 @@ +-- ***** BEGIN LICENSE BLOCK ***** +-- This file is part of InDefero, an open source project management application. +-- Copyright (C) 2011 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 ***** + +---- Load local hooks if they exist. +-- The way this is supposed to work is that hooks.d can contain symbolic +-- links to lua scripts. These links MUST have the extension .lua +-- If the script needs some configuration, a corresponding file with +-- the extension .conf is the right spot. +---- +-- First load the configuration of the hooks, if applicable +includedirpattern(get_confdir() .. "/hooks.d/","*.conf") +-- Then load the hooks themselves +includedirpattern(get_confdir() .. "/hooks.d/","*.lua") diff --git a/src/IDF/Plugin/SyncMonotone/remote-automate-permissions.in b/src/IDF/Plugin/SyncMonotone/remote-automate-permissions.in new file mode 100644 index 0000000..164a44c --- /dev/null +++ b/src/IDF/Plugin/SyncMonotone/remote-automate-permissions.in @@ -0,0 +1 @@ +%%MTNCLIENTKEY%% diff --git a/src/IDF/Scm/Monotone/Stdio.php b/src/IDF/Scm/Monotone/Stdio.php index fa243d8..86c4b50 100644 --- a/src/IDF/Scm/Monotone/Stdio.php +++ b/src/IDF/Scm/Monotone/Stdio.php @@ -70,11 +70,6 @@ class IDF_Scm_Monotone_Stdio */ 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); diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index e1dbd4a..e76c4b3 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -98,17 +98,16 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s'; # choosed for manual setups and / or ssh access. $cfg['mtn_db_access'] = 'local'; -# 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; +# Full path to the directory tree which contains default configuration files +# that are automatically created for new projects. This is only needed +# if $cfg['mtn_db_access'] is set to remote, i.e. in case the SyncMonotone +# plugin should be used. If unset, it defaults to the tree underknees +# src/IDF/Plugin/SyncMonotone/. Don't forget the trailing slash! +#$cfg['mtn_confdir'] = '/path/to/dir/tree/'; + +# Additional configuration files you want to create / copy for new setups. +# All these file paths have to be relative to $cfg['mtn_confdir']. +#$cfg['mtn_confdir_extra'] = array('hooks.d/something.lua') # Needs to be configured for remote / usher usage. # This allows basic control of a running usher process via the forge @@ -260,7 +259,7 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git', # syntactically, but also by the specific backend. For SSH public # keys, ssh-keygen(3) must be available and usable in PATH, for # monotone public keys, the monotone binary (as configured above) -# is used. +# is used. # $cfg['idf_strong_key_check'] = false; # If you want to use another memtypes database From 5635cdcac7af2f798c24c8ae474dfa94ee42abc4 Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 10:26:29 +0100 Subject: [PATCH 06/15] Remove the use of GROUP_CONCAT in SQL request. --- src/IDF/Views/Issue.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index cd992c8..3562b36 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -174,9 +174,12 @@ class IDF_Views_Issue // Get the id list of issue in the user watch list (for all projects !) $db =& Pluf::db(); - $issues_id = $db->select('SELECT GROUP_CONCAT(idf_issue_id) as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id.' GROUP BY pluf_user_id'); - if (empty ($issues_id)) $issues_id = ""; - else $issues_id = $issues_id[0]['id']; + $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id); + $issues_id = array(); + foreach ($sql_results as $id) { + array_push($issues_id, $id['id']); + } + $issues_id = implode (',', $issues_id); // Count open and close issues $sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array()); From 4245617c6f2972ffb7a21723da809dc4c34113af Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 11:01:35 +0100 Subject: [PATCH 07/15] Fix issue 546 : Add the irc protocol in the markdown prefilter --- src/IDF/Template/MarkdownPrefilter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/IDF/Template/MarkdownPrefilter.php b/src/IDF/Template/MarkdownPrefilter.php index 1cabf43..ecf2b1f 100644 --- a/src/IDF/Template/MarkdownPrefilter.php +++ b/src/IDF/Template/MarkdownPrefilter.php @@ -168,6 +168,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter 'https', 'ftp', 'mailto', + 'irc' ); // tags which should be removed if they contain no content // (e.g. "" or "") From 439f1fefe29d986d91b21e6ae34da53a72d8a3b8 Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 11:13:29 +0100 Subject: [PATCH 08/15] Fix issue 588 : Redirect connected user to the anonymous url if they don't have register a SSH Key for GIT --- src/IDF/Scm/Git.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/IDF/Scm/Git.php b/src/IDF/Scm/Git.php index ca05429..edc1aab 100644 --- a/src/IDF/Scm/Git.php +++ b/src/IDF/Scm/Git.php @@ -280,6 +280,14 @@ class IDF_Scm_Git extends IDF_Scm public static function getAuthAccessUrl($project, $user, $commit=null) { + // if the user haven't registred a public ssh key, + // he can't use the write url which use the SSH authentification + if ($user != null) { + $keys = $user->get_idf_key_list(); + if (count ($keys) == 0) + return self::getAnonymousAccessUrl($project); + } + return sprintf(Pluf::f('git_write_remote_url'), $project->shortname); } From d654c956898a083b9d6cf46d9182f038f53ef15d Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 14:03:07 +0100 Subject: [PATCH 09/15] Fix issue 553 : Git escape too much character in a UTF-8 shell Add an option to configure the git core.quotepath option --- src/IDF/Plugin/SyncGit/Serve.php | 18 ++++++++++++++++++ src/IDF/conf/idf.php-dist | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/src/IDF/Plugin/SyncGit/Serve.php b/src/IDF/Plugin/SyncGit/Serve.php index 499a1ba..842797b 100644 --- a/src/IDF/Plugin/SyncGit/Serve.php +++ b/src/IDF/Plugin/SyncGit/Serve.php @@ -230,6 +230,24 @@ class IDF_Plugin_SyncGit_Serve } Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository', 'Added post-update hook.', $fullpath)); + // Configure the core.quotepath option + $quotepath = (Pluf::f('git_core_quotepath', true) == true) ? 'true' : 'false'; + $out = array(); + $res = 0; + exec(sprintf(Pluf::f('idf_exec_cmd_prefix', ''). + Pluf::f('git_path', 'git').' config -f %s/config --add core.quotepath %s', + escapeshellarg($fullpath), + escapeshellarg($quotepath) + ), + $out, $res); + if ($res != 0) { + Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository', + 'core.quotepath configuration error.', + $quotepath)); + return; + } + Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository', + 'core.quotepath configured.', $quotepath)); } /** diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index e76c4b3..9e0ed8a 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -255,6 +255,12 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git', 'mtn' => 'IDF_Scm_Monotone', ); +# Specific git config +# The core.quotepath is configured on new repository +# True -> All characters upper than 0x80 will be escape (default) +# False -> Characters is print directly, enable accented character in a UTF-8 shell +# $cfg['git_core_quotepath'] = false; + # Set to true when uploaded public keys should not only be validated # syntactically, but also by the specific backend. For SSH public # keys, ssh-keygen(3) must be available and usable in PATH, for From c2a9a60aa724d7e5ee75aa297b299d6e63833897 Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 14:30:39 +0100 Subject: [PATCH 10/15] Naming conventions correction --- src/IDF/Views/Issue.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 3562b36..58d4f05 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -175,16 +175,16 @@ class IDF_Views_Issue // Get the id list of issue in the user watch list (for all projects !) $db =& Pluf::db(); $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id); - $issues_id = array(); + $issues_ids = array(); foreach ($sql_results as $id) { - array_push($issues_id, $id['id']); + $issues_ids[] = $id['id']; } - $issues_id = implode (',', $issues_id); + $issues_ids = implode (',', $issues_ids); // Count open and close issues - $sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array()); + $sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $otags).')', array()); $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); - $sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array()); + $sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $ctags).')', array()); $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); // Generate a filter for the paginator @@ -192,13 +192,13 @@ class IDF_Views_Issue case 'closed': $title = sprintf(__('Watch List: Closed Issues')); $summary = __('This table shows the closed issues in your watch list.'); - $f_sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array()); + $f_sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $ctags).')', array()); break; case 'open': default: $title = sprintf(__('Watch List: Open Issues')); $summary = __('This table shows the open issues in your watch list.'); - $f_sql = new Pluf_SQL('id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array()); + $f_sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $otags).')', array()); break; } From afa91188d8e265fa3fcaa6b6a93a3190f8bc8019 Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Thu, 6 Jan 2011 22:38:38 +0100 Subject: [PATCH 11/15] Enhancement of the view of an issue. Add link previous and after to quickly jump to another issue. Those links are pointing to issue with the same status (open/closed). With little trick on url we can do the same thing for browse "my issue" and "my watchlist". --- src/IDF/Views/Issue.php | 22 ++++++++++++++++++++++ src/IDF/templates/idf/issues/view.html | 9 +++++++++ www/media/idf/css/style.css | 1 + 3 files changed, 32 insertions(+) diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 58d4f05..2aa1480 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -435,6 +435,26 @@ class IDF_Views_Issue } } + // Search previous and next issue id + $octags = $prj->getTagIdsByStatus(($closed) ? 'closed' : 'open'); + if (count($octags) == 0) $octags[] = 0; + $sql_previous = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id<%s', + array($prj->id, $match[2]) + ); + $sql_next = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id>%s', + array($prj->id, $match[2]) + ); + $previous_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_previous->gen(), + 'order' => 'id DESC', + 'nb' => 1 + )); + $next_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_next->gen(), + 'order' => 'id ASC', + 'nb' => 1 + )); + $previous_issue_id = (isset($previous_issue[0])) ? $previous_issue[0]->id : 0; + $next_issue_id = (isset($next_issue[0])) ? $next_issue[0]->id : 0; + $arrays = self::autoCompleteArrays($prj); return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html', array_merge( @@ -447,6 +467,8 @@ class IDF_Views_Issue 'closed' => $closed, 'preview' => $preview, 'interested' => $interested->count(), + 'previous_issue_id' => $previous_issue_id, + 'next_issue_id' => $next_issue_id ), $arrays), $request); diff --git a/src/IDF/templates/idf/issues/view.html b/src/IDF/templates/idf/issues/view.html index 4d0e1ba..4ad9e4e 100644 --- a/src/IDF/templates/idf/issues/view.html +++ b/src/IDF/templates/idf/issues/view.html @@ -1,6 +1,15 @@ {extends "idf/issues/base.html"} {block titleicon}{if $form} {/if}{/block} {block body} +
+{if $previous_issue_id} +Previous issue +{/if} +{if $previous_issue_id and $next_issue_id} - {/if} +{if $next_issue_id} +Next issue +{/if} +
{assign $i = 0} {assign $nc = $comments.count()} {foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request} diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index a36e94c..9188e1d 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -225,6 +225,7 @@ div.issue-comment { div.issue-comment-first { border-top: 1px solid #d3d7cf; + clear: both; } div.issue-comment-signin { From 146e956432782f3aa7c4d91fd21cbc79d4d1e99e Mon Sep 17 00:00:00 2001 From: William MARTIN Date: Fri, 7 Jan 2011 11:17:14 +0100 Subject: [PATCH 12/15] Add a favicon Fix issue 594 --- logo/indefero-logo-lite.svg | 86 +++++++++++++++++++++++++ src/IDF/templates/idf/base-full.html | 1 + src/IDF/templates/idf/base-simple.html | 1 + src/IDF/templates/idf/base.html | 1 + www/media/idf/img/favicon.png | Bin 0 -> 677 bytes 5 files changed, 89 insertions(+) create mode 100644 logo/indefero-logo-lite.svg create mode 100644 www/media/idf/img/favicon.png diff --git a/logo/indefero-logo-lite.svg b/logo/indefero-logo-lite.svg new file mode 100644 index 0000000..ac50cfe --- /dev/null +++ b/logo/indefero-logo-lite.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/IDF/templates/idf/base-full.html b/src/IDF/templates/idf/base-full.html index e0bb337..5f505d8 100644 --- a/src/IDF/templates/idf/base-full.html +++ b/src/IDF/templates/idf/base-full.html @@ -25,6 +25,7 @@ + diff --git a/src/IDF/templates/idf/base-simple.html b/src/IDF/templates/idf/base-simple.html index 27e6a5c..b5168c8 100644 --- a/src/IDF/templates/idf/base-simple.html +++ b/src/IDF/templates/idf/base-simple.html @@ -25,6 +25,7 @@ + diff --git a/src/IDF/templates/idf/base.html b/src/IDF/templates/idf/base.html index 91af76a..27fdae1 100644 --- a/src/IDF/templates/idf/base.html +++ b/src/IDF/templates/idf/base.html @@ -25,6 +25,7 @@ + diff --git a/www/media/idf/img/favicon.png b/www/media/idf/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..51c91e4ac4fbd12ade136af1ed1c9e3f1c212049 GIT binary patch literal 677 zcmV;W0$TlvP)wnqhh9%coY@F1w-$Z_ax=P!WDJZt)Wo@mx_p=IzII~mgGbLfa<2S zEUigrUtibi006|O=_JV>84Cvm7pF2}ZY;??aWOSaxIBlra8kGzVws$(8S`w2=IH>1 z273-gLZ{9Z5%c3F?)U_5d>B8SIgx!4Pe=0zA=urP&s4YeA0xuY2VM+kQ!IB80N#5~ zxK%`aJSDtLOpfOf>JvgNBLc=Ef&2m_MFL3>qR)e1YT|i3%^w?V?C9M(L8Bf)_^m^si}|HW+I6XMmw|gL$)O+52+)t&9J82THTukmdHTAVbHzBnGIg z)xW=*Zai~S6aau}n>ELFfEd`qGOdn7p&oYnmuhw`YqMNmY)L!U1_x090LHew&`b-n zLV}z0hGDQj&lnrGOR Date: Fri, 7 Jan 2011 22:33:18 +0100 Subject: [PATCH 13/15] Add table of contents on wiki pages --- src/IDF/templates/idf/wiki/view.html | 4 ++++ www/media/idf/css/style.css | 26 ++++++++++++++++++++++++++ www/media/idf/js/wiki-toc.js | 7 +++++++ 3 files changed, 37 insertions(+) create mode 100644 www/media/idf/js/wiki-toc.js diff --git a/src/IDF/templates/idf/wiki/view.html b/src/IDF/templates/idf/wiki/view.html index 21594cd..42fba21 100644 --- a/src/IDF/templates/idf/wiki/view.html +++ b/src/IDF/templates/idf/wiki/view.html @@ -18,6 +18,9 @@ by {$submitter}.{/blocktrans}

{/if} +
+
{trans 'Table of Content'}
+

{$page.summary}

{if !$oldrev} @@ -29,6 +32,7 @@ by {$submitter}.{/blocktrans}

{/if} {/if} +
{/block} {block context} {ashowuser 'submitter', $page.get_submitter(), $request} diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index 9188e1d..942a1b7 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -746,6 +746,32 @@ ol > li { margin: 0px; } +#wiki-toc { + float: right; + margin-left: 10px; +} + +#wiki-toc-content { + border: 1px solid #999999; + border-width: 1px 0; + padding: 10px 0; + padding-bottom: 25px; + background-color: #ffffff; + display: block; +} + +#wiki-toc-content a { + display: block; +} + +#wiki-toc-content a.wiki-h2 { + margin-left: 1em; +} + +#wiki-toc-content a.wiki-h3 { + margin-left: 2em; +} + /** * main menu */ diff --git a/www/media/idf/js/wiki-toc.js b/www/media/idf/js/wiki-toc.js new file mode 100644 index 0000000..e7e8e3d --- /dev/null +++ b/www/media/idf/js/wiki-toc.js @@ -0,0 +1,7 @@ +$(document).ready(function() { + $(":header", "#wiki-content").map(function (index) { + this.id = "wikititle_" + index; + $("" + this.innerText + "").addClass("wiki-" + this.tagName.toLowerCase()).appendTo('#wiki-toc-content'); + }); +}); + From cdeefb43a59734eb5131f2cd97f3453a914dd27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Sat, 8 Jan 2011 21:33:51 +0100 Subject: [PATCH 14/15] Added a logging of an event. --- scripts/svnpostcommit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/svnpostcommit.php b/scripts/svnpostcommit.php index d3430b9..c38c796 100644 --- a/scripts/svnpostcommit.php +++ b/scripts/svnpostcommit.php @@ -55,6 +55,7 @@ Pluf_Dispatcher::loadControllers(Pluf::f('idf_views')); $params = array('repo_dir' => $argv[1], 'revision' => $argv[2], 'env' => array_merge($_ENV, $_SERVER)); +Pluf_Log::event(array('svnpostcommit.php', 'Send run signal.', $params)); Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params); From c67e61cbaa4b6adc1f12d3f3d31e988a286a0004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20d=27Anterroches?= Date: Sat, 8 Jan 2011 21:34:51 +0100 Subject: [PATCH 15/15] Fixed the watch list to not crash when empty and support PostgreSQL. --- src/IDF/Views/Issue.php | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 2aa1480..2f43f6a 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -92,16 +92,19 @@ class IDF_Views_Issue if (count($otags) == 0) $otags[] = 0; if (count($ctags) == 0) $ctags[] = 0; - // Get the id list of issue in the user watch list (for all projects !) + // Get the id list of issue in the user watch list (for all projects !) $db =& Pluf::db(); - $issues_id = $db->select('SELECT GROUP_CONCAT(idf_issue_id) as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id.' GROUP BY pluf_user_id'); - if (empty ($issues_id)) $issues_id = ""; - else $issues_id = $issues_id[0]['id']; + $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id); + $issue_ids = array(0); + foreach ($sql_results as $id) { + $issue_ids[] = $id['id']; + } + $issue_ids = implode (',', $issue_ids); // Count open and close issues - $sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); + $sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); - $sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); + $sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); // Generate a filter for the paginator @@ -109,13 +112,13 @@ class IDF_Views_Issue case 'closed': $title = sprintf(__('Watch List: Closed Issues for %s'), (string) $prj); $summary = __('This table shows the closed issues in your watch list for %s project.', (string) $prj); - $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); + $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id)); break; case 'open': default: $title = sprintf(__('Watch List: Open Issues for %s'), (string) $prj); $summary = __('This table shows the open issues in your watch list for %s project.', (string) $prj); - $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issues_id.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); + $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id)); break; } @@ -175,16 +178,16 @@ class IDF_Views_Issue // Get the id list of issue in the user watch list (for all projects !) $db =& Pluf::db(); $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id); - $issues_ids = array(); + $issue_ids = array(0); foreach ($sql_results as $id) { - $issues_ids[] = $id['id']; + $issue_ids[] = $id['id']; } - $issues_ids = implode (',', $issues_ids); + $issue_ids = implode (',', $issue_ids); // Count open and close issues - $sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $otags).')', array()); + $sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array()); $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); - $sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $ctags).')', array()); + $sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array()); $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen())); // Generate a filter for the paginator @@ -192,13 +195,13 @@ class IDF_Views_Issue case 'closed': $title = sprintf(__('Watch List: Closed Issues')); $summary = __('This table shows the closed issues in your watch list.'); - $f_sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $ctags).')', array()); + $f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array()); break; case 'open': default: $title = sprintf(__('Watch List: Open Issues')); $summary = __('This table shows the open issues in your watch list.'); - $f_sql = new Pluf_SQL('id IN ('.$issues_ids.') AND status IN ('.implode(', ', $otags).')', array()); + $f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array()); break; }