--- /dev/null
+<?php
+/**
+ * commit-update-tickets.php configuration
+ */
+// Path to git
+$git = '/usr/bin/git';
+
+// XML-RPC endpoint
+$rpc_endpoint = 'https://dev.horde.org/horde/rpc.php';
+
+// XML-RPC method to call
+$rpc_method = 'tickets.addComment';
+
+// XML-RPC options, usually username and password
+$rpc_options = array('user' => '',
+ 'pass' => '');
--- /dev/null
+#!/usr/bin/env php
+<?php
+/**
+ * commit-update-tickets.php: scan commit logs for ticket numbers
+ * denoted by a flexible regular expression and post the log message
+ * and a link to the changeset diff in a comment to those tickets.
+ *
+ * Usage: commit-update-tickets.php PATH_TO_REPO REVISION
+ *
+ * @category Horde
+ * @package maintainer_tools
+ */
+
+require_once 'Horde/Autoloader/Default.php';
+
+
+/**
+ ** Initialize expected values to rule out pre-existing input by any
+ ** means, then include our configuration file.
+ **/
+
+$git = $rpc_endpoint = $rpc_method = null;
+$rpc_options = array();
+
+require dirname(__FILE__) . '/commit-update-tickets-conf.php';
+
+
+/**
+ ** Sanity checks
+ **/
+
+if (!is_executable($git)) {
+ abort("Required program $git is not executable.");
+}
+
+if (is_null($rpc_endpoint) || is_null($rpc_method)) {
+ abort("Required XML-RPC configuration is missing or incomplete.");
+}
+
+if (count($_SERVER['argv']) < 4) {
+ usage();
+}
+
+
+/**
+ ** Command-line parsing
+ **/
+
+array_shift($_SERVER['argv']);
+$repo = array_shift($_SERVER['argv']);
+$rev = array_shift($_SERVER['argv']);
+$links = implode("\n", $_SERVER['argv']);
+
+if (!file_exists($repo)) {
+ abort("Repository $repo does not exist.");
+}
+
+if (!is_dir($repo)) {
+ abort("Repository $repo is not a directory.");
+}
+
+
+/**
+ ** Read the log message for this revision
+ **/
+
+// Run git show to get the log message for this revision
+$log_message = shell_exec(implode(' ', array(
+ escapeshellcmd($git),
+ '--git-dir=' . escapeshellarg($repo),
+ 'show',
+ '--summary',
+ '--pretty=format:%s%n%b',
+ escapeshellarg($rev)
+)));
+
+if (!empty($log_message)) {
+ $tickets = find_tickets($log_message);
+ $kolab_tickets = find_kolab_tickets($log_message);
+
+ foreach ($kolab_tickets as $ticket) {
+ $log_message .= "\nThis ticket also references kolab issue: http://issues.kolab.org/issue$ticket\n\n";
+ }
+
+ if (count($tickets)) {
+ $log_message = "Changes have been made in Git for this ticket:\n\n$log_message$links";
+ foreach ($tickets as $ticket) {
+ post_comment($ticket, $log_message);
+ }
+ }
+}
+
+exit(0);
+
+
+/**
+ ** Functions
+ **/
+
+function abort($msg) {
+ fputs(STDERR, $msg . "\n");
+ exit(1);
+}
+
+function usage() {
+ abort("usage: commit-update-tickets.php PATH_TO_REPO REVISION LINKS");
+}
+
+function find_tickets($log_message) {
+ preg_match_all('/(?:(?<!kolab\/)(?:bug|ticket|request|enhancement|issue):?\s*#?|#)(\d+)/i', $log_message, $matches);
+ return array_unique($matches[1]);
+}
+
+/**
+ * Locate kolab tickets with kolab/issue1234
+ */
+function find_kolab_tickets($log_message) {
+ preg_match_all('/(?:(?:kolab\/issue))(\d+)/i', $log_message, $matches);
+ return array_unique($matches[1]);
+}
+
+function post_comment($ticket, $log_message) {
+ $result = Horde_Rpc::request(
+ 'xmlrpc',
+ $GLOBALS['rpc_endpoint'],
+ $GLOBALS['rpc_method'],
+ array((int)$ticket, $log_message),
+ $GLOBALS['rpc_options']);
+
+ if (is_a($result, 'PEAR_Error')) {
+ abort($result->getMessage());
+ }
+
+ return true;
+}
--- /dev/null
+<?php
+/**
+ ** commit-update-tickets.php configuration
+ **/
+
+// Path to svnlook
+$svnlook = '/usr/bin/svnlook';
+
+// XML-RPC endpoint
+$rpc_endpoint = 'https://dev.horde.org/horde/rpc.php';
+
+// XML-RPC method to call
+$rpc_method = 'tickets.addComment';
+
+// XML-RPC options, usually username and password
+$rpc_options = array('user' => '',
+ 'pass' => '');
--- /dev/null
+#!/usr/bin/env php
+<?php
+/**
+ * commit-update-tickets.php: scan commit logs for ticket numbers
+ * (denoted by a flexible regular expression and post the log message
+ * and a link to the changeset diff in a comment to those tickets.
+ *
+ * Usage: commit-update-tickets.php REPOS REVISION
+ *
+ * @category Horde
+ * @package maintainer_tools
+ */
+
+
+/**
+ ** Includes
+ **/
+
+require_once 'Horde/RPC.php';
+
+
+/**
+ ** Initialize expected values to rule out pre-existing input by any
+ ** means, then include our configuration file.
+ **/
+
+$svnlook = null;
+$rpc_endpoint = null;
+$rpc_method = null;
+$rpc_options = array();
+
+include dirname(__FILE__) . '/commit-update-tickets-conf.php';
+
+
+/**
+ ** Sanity checks
+ **/
+
+if (!is_executable($svnlook)) {
+ abort("Required program $svnlook is not executable.");
+}
+
+if (is_null($rpc_endpoint) || is_null($rpc_method)) {
+ abort("Required XML-RPC configuration is missing or incomplete.");
+}
+
+if (count($_SERVER['argv']) != 3) {
+ usage();
+}
+
+
+/**
+ ** Command-line parsing
+ **/
+
+$repos = $_SERVER['argv'][1];
+$rev = $_SERVER['argv'][2];
+
+if (!file_exists($repos)) {
+ abort("Repository $repos does not exist.");
+}
+
+if (!is_dir($repos)) {
+ abort("Repository $repos is not a directory.");
+}
+
+
+/**
+ ** Read the log message for this revision
+ **/
+
+// Run svnlook log to get the log message for this revision
+exec(implode(' ', array($svnlook,
+ 'log',
+ '--revision',
+ escapeshellarg($rev),
+ escapeshellarg($repos))),
+ $log_message);
+
+$tickets = find_tickets($log_message);
+if (count($tickets)) {
+ foreach ($tickets as $ticket) {
+ post_comment($ticket, $log_message);
+ }
+}
+
+exit(0);
+
+
+/**
+ ** Functions
+ **/
+
+function abort($msg) {
+ fputs(STDERR, $msg . "\n");
+ exit(1);
+}
+
+function usage() {
+ abort("usage: commit-update-tickets.php REPOS REVISION");
+}
+
+function find_tickets($log_message) {
+ preg_match_all('/#(\d+)/', $log_message, $matches_1);
+ preg_match_all('/(bug|ticket|request|enhancement|issue):\s*#?(\d+)/i', $log_message, $matches_2);
+ return array_unique(array_merge($matches_1[1], $matches_2[2]));
+}
+
+function post_comment($ticket, $log_message) {
+ $result = Horde_RPC::request(
+ 'xmlrpc',
+ $GLOBALS['rpc_endpoint'],
+ $GLOBALS['rpc_method'],
+ array((int)$ticket, $log_message),
+ $GLOBALS['rpc_options']);
+
+ if (is_a($result, 'PEAR_Error')) {
+ abort($result->getMessage());
+ }
+
+ return true;
+}