cvs diff: Diffing .
Index: cvs.install
===================================================================
RCS file: /Users/wright/drupal/local_repo/contributions/modules/cvslog/cvs.install,v
retrieving revision 1.17
diff -u -p -r1.17 cvs.install
--- cvs.install	11 Apr 2009 23:48:28 -0000	1.17
+++ cvs.install	18 Apr 2009 02:50:14 -0000
@@ -343,6 +343,26 @@ function cvs_schema() {
     ),
   );
   
+  $schema['cvs_cache_block'] = array(
+    'description' => t('Cache for CVS-specific blocks that cannot rely on the normal block cache.'),
+    'fields' => array(
+      'cid' => array(
+        'description' => 'Primary Key: Unique cache ID.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'data' => array(
+        'description' => 'A collection of data to cache.',
+        'type' => 'blob',
+        'not null' => FALSE,
+        'size' => 'big',
+      ),
+    ),
+    'primary key' => array('cid'),
+  );
+
   return $schema;
 }
 
@@ -385,6 +405,7 @@ function cvs_uninstall() {
     'cvs_message_new_release_branch',
     'cvs_message_new_release_tag',
     'cvs_pager',
+    'cvs_project_maintainers_block_length',
   );
   $query = db_query("SELECT name FROM {variable} WHERE name LIKE 'cvs_directory_tid_%'");
   while ($var = db_fetch_object($query)) {
@@ -418,3 +439,28 @@ function cvs_update_6001() {
   return $ret;
 }
 
+/**
+ * Add the {cvs_cache_block} table for the CVS maintainers block.
+ */
+function cvs_update_6002() {
+  $ret = array();
+  $table = array(
+    'fields' => array(
+      'cid' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'data' => array(
+        'type' => 'blob',
+        'not null' => FALSE,
+        'size' => 'big',
+      ),
+    ),
+    'primary key' => array('cid'),
+  );
+  db_create_table($ret, 'cvs_cache_block', $table);
+  return $ret;
+}
+
Index: cvs.module
===================================================================
RCS file: /Users/wright/drupal/local_repo/contributions/modules/cvslog/cvs.module,v
retrieving revision 1.229
diff -u -p -r1.229 cvs.module
--- cvs.module	12 Apr 2009 00:06:14 -0000	1.229
+++ cvs.module	18 Apr 2009 02:48:28 -0000
@@ -205,6 +205,23 @@ function cvs_menu() {
     'access arguments' => array(1),
     'type' => MENU_CALLBACK,
   );
+  $items['node/%project_node/committers'] = array(
+    'title' => 'Committers',
+    'page callback' => 'cvs_project_committers_page',
+    'page arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'access callback' => 'node_access',
+    'access arguments' => array('view', 1),
+  );
+  // Redirect from the legacy URL to prevent link rot.
+  $items['node/%project_node/developers'] = array(
+    'title' => 'Developers',
+    'page callback' => 'cvs_project_developers_redirect',
+    'page arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'access callback' => 'node_access',
+    'access arguments' => array('view', 1),
+  );
 
   return $items;
 }
@@ -327,6 +344,9 @@ function cvs_theme() {
     'cvs_project_access_form' => array(
       'arguments' => array('form'),
     ),
+    'cvs_project_maintainer_list' => array(
+      'arguments' => array('node', 'maintainers'),
+    ),
   );
 }
 
@@ -1225,17 +1245,15 @@ function cvs_project_release_form_pre_re
 /**
  * Implementation of hook_block().
  */
-function cvs_block($op = 'list', $delta = 0) {
+function cvs_block($op = 'list', $delta = 0, $edit = array()) {
   if ($op == 'list') {
-    $blocks['cvs_site_active_developers'] = array(
-      'info' => t('CVS: Most active developers'),
-      'cache' => BLOCK_CACHE_GLOBAL,
-    );
-    $blocks['cvs_site_active_projects'] = array(
-      'info' => t('CVS: Most active projects'),
-      'cache' => BLOCK_CACHE_GLOBAL,
-    );
-    return $blocks;
+    return cvs_block_list();
+  }
+  else if ($op == 'configure') {
+    return cvs_block_configure($delta);
+  }
+  else if ($op == 'save') {
+    return cvs_block_save($delta, $edit);
   }
   else if ($op == 'view') {
     return cvs_block_view($delta);
@@ -1243,6 +1261,61 @@ function cvs_block($op = 'list', $delta 
 }
 
 /**
+ * Implementation of hook_block() for the 'list' operation.
+ */
+function cvs_block_list() {
+  $blocks['cvs_site_active_developers'] = array(
+    'info' => t('CVS: Most active developers'),
+    'cache' => BLOCK_CACHE_GLOBAL,
+  );
+  $blocks['cvs_site_active_projects'] = array(
+    'info' => t('CVS: Most active projects'),
+    'cache' => BLOCK_CACHE_GLOBAL,
+  );
+  // We roll our own caching for this block, since the existing block cache is
+  // cleared on every comment or node added on the site, which isn't at all
+  // what we need for this.  There are expensive queries for this block, and
+  // it only changes when a commit is made to a given project.
+  $blocks['cvs_project_maintainers'] = array(
+    'info' => t('CVS: Project maintainers'),
+    'cache' => BLOCK_NO_CACHE,
+  );
+  return $blocks;
+}
+
+/**
+ * Implementation of hook_block() for the 'configure' operation.
+ */
+function cvs_block_configure($delta) {
+  $form = array();
+  switch ($delta) {
+    case 'cvs_project_maintainers':
+      for ($i=1; $i<15; $i++) {
+        $options[$i] = $i;
+      }
+      $form['cvs_project_maintainers_block_length'] = array(
+        '#type' => 'select',
+        '#options' => $options,
+        '#title' => t('Number of maintainers to display'),
+        '#default_value' => variable_get('cvs_project_maintainers_block_length', 5),
+      );
+      break;
+  }
+  return $form;
+}
+
+/**
+ * Implementation of hook_block() for the 'save' operation.
+ */
+function cvs_block_save($delta, $edit) {
+  switch ($delta) {
+    case 'cvs_project_maintainers':
+      variable_set('cvs_project_maintainers_block_length', $edit['cvs_project_maintainers_block_length']);
+      break;
+  }
+}
+
+/**
  * Implementation of hook_block() for the 'view' operation.
  */
 function cvs_block_view($delta) {
@@ -1272,10 +1345,48 @@ function cvs_block_view($delta) {
       }
       break;
 
+    case 'cvs_project_maintainers':
+      if (($node = project_get_project_from_menu()) && !empty($node->cvs['repository']) && node_access('view', $node) && user_access('access CVS messages')) {
+        $cid = 'cvs_project_maintainers:'. $node->nid;
+        $block = _cvs_block_cache_get($cid);
+        if (empty($block)) {
+          $maintainers = cvs_get_project_committers($node->nid);
+          $block = array(
+            'subject' => t('Maintainers for @project', array('@project' => $node->title)),
+            'content' => theme('cvs_project_maintainer_list', $node, $maintainers, variable_get('cvs_project_maintainers_block_length', 5)),
+          );
+          _cvs_block_cache_set($cid, $block);
+        }
+      }
+      break;
   }
   return $block;
 }
 
+/**
+ * Return themed HTML for the CVS project maintainers block.
+ */
+function theme_cvs_project_maintainer_list($node, $maintainers, $max_maintainers = NULL) {
+  $output = '<ul>';
+  $i = 0;
+  foreach ($maintainers as $maintainer) {
+    if (isset($max_maintainers) && $i >= $max_maintainers) {
+      break;
+    }
+    $output .= '<li><div class="cvs-user">';
+    $output .= theme('username', $maintainer) .' - ';
+    $output .= '<span class="cvs-commits">'. format_plural($maintainer->commits, '1 commit', '@count commits') .'</span></div>';
+    $output .= '<div class="cvs-commit-times">';
+    $output .= t('last: !last_time ago, first: !first_time ago', array('!last_time' => format_interval(time() - $maintainer->last_commit, 1), '!first_time' => format_interval(time() - $maintainer->first_commit, 1)));
+    $output .= '</div>';
+    $output .= '</li>';
+    $i++;
+  }
+  $output .= '</ul>';
+  $output .= l('View all committers', 'node/'. $node->nid .'/committers');
+  return $output;
+}
+
 function _cvs_date($timestamp) {
   static $last;
   $date = format_date($timestamp, 'custom', 'F j, Y');
@@ -1285,6 +1396,26 @@ function _cvs_date($timestamp) {
   }
 }
 
+function _cvs_block_cache_get($cid) {
+  $data = db_result(db_query("SELECT data FROM {cvs_cache_block} WHERE cid = '%s'", $cid));
+  if (!empty($data)) {
+    return unserialize($data);
+  }
+}
+
+function _cvs_block_cache_set($cid, $data) {
+  if (empty($data)) {
+    db_query("DELETE FROM {cvs_cache_block} WHERE cid = '%s'", $cid);
+  }
+  else {
+    $serialized = serialize($data);
+    db_query("UPDATE {cvs_cache_block} SET data = '%s' WHERE cid = '%s'", $serialized, $cid);
+    if (!db_affected_rows()) {  
+      db_query("INSERT INTO {cvs_cache_block} (cid, data) VALUES ('%s', '%s')", $cid, $serialized);
+    }
+  }
+}
+
 function theme_cvs_commit_message($commit) {
   $repositories = cvs_get_repository_info();
   $trackerurl = $repositories[$commit->rid]->trackerurl;
@@ -1512,27 +1643,89 @@ function cvs_search_form(&$form_state, $
 }
 
 /**
- * Print a list of contributors for the specified project.
+ * Return a list of users who committed to the specified project.
+ *
+ * @param $nid
+ *   Project node ID to get committers for.
+ * @param $sort
+ *   Optional ORDER BY clause to use for the list of committers. Defaults to
+ *   sorting by the most recent commit.
+ * @param $only_maintainers
+ *   Optional boolean to indicate if the list should only include users that
+ *   currently have CVS access to the project. Defaults to TRUE.
+ *
+ * @return
+ *   Array containing objects of committer information, keyed by uid.
  */
-function cvs_get_project_contributors($nid) {
+function cvs_get_project_committers($nid, $order = '', $only_maintainers = TRUE) {
+  if (empty($order)) {
+    $order = ' ORDER BY last_commit DESC';
+  }
+  $result = db_query("SELECT f.uid, u.name, MIN(m.created) AS first_commit, MAX(m.created) AS last_commit, COUNT(f.cid) AS commits FROM {cvs_files} f INNER JOIN {cvs_messages} m ON m.cid = f.cid INNER JOIN {users} u ON f.uid = u.uid $maintainer_join WHERE f.uid != %d AND f.nid = %d GROUP BY f.nid, f.uid $order", 0, $nid);
+  $committers = array();
+  while ($committer = db_fetch_object($result)) {
+    $committers[$committer->uid] = $committer;
+  }
+  if ($only_maintainers) {
+    return array_intersect_key($committers, cvs_get_project_maintainers($nid));
+  }
+  return $committers;
+}
 
- $header = array(
+/**
+ * Return an array of users with CVS access to the given project.
+ *
+ * @param $nid
+ *   Node ID of the project to find maintainers for.
+ *
+ * @return
+ *   Array of usernames with CVS access to the project, keyed by uids.
+ */
+function cvs_get_project_maintainers($nid) {
+  $maintainers = array();
+  $project = node_load($nid);
+  if (!empty($project->cvs['repository'])) {
+    // The project owner always has commit access.
+    $maintainers = array($project->uid => $project->name);
+    $result = db_query("SELECT cpm.uid, u.name FROM {cvs_project_maintainers} cpm INNER JOIN {users} u ON cpm.uid = u.uid WHERE cpm.nid = %d", $project->nid);
+    while ($row = db_fetch_object($result)) {
+      $maintainers[$row->uid] = $row->name;
+    }
+  }
+  return $maintainers;
+}
+
+/**
+ * Page callback to redirect to the committers page to avoid link rot.
+ */
+function cvs_project_developers_redirect($project) {
+  drupal_goto('node/'. $project->nid .'/committers');
+}
+
+/**
+ * Page callback to display all committers for a given project.
+ */
+function cvs_project_committers_page($project) {
+  drupal_set_title(t('Committers for %name', array('%name' => $project->title)));
+  project_project_set_breadcrumb($project, TRUE);
+  $header = array(
     array('data' => t('User'), 'field' => 'u.name'),
     array('data' => t('Last commit'), 'field' => 'last_commit', 'sort' => 'desc'),
     array('data' => t('First commit'), 'field' => 'first_commit'),
-    array('data' => t('Commits'), 'field' => 'commits'));
-
-  $result = db_query("SELECT f.uid, u.name, MIN(m.created) AS first_commit, MAX(m.created) AS last_commit, COUNT(f.cid) AS commits FROM {cvs_files} f INNER JOIN {cvs_messages} m ON m.cid = f.cid INNER JOIN {users} u ON f.uid = u.uid WHERE f.uid != %d AND f.nid = %d GROUP BY f.nid, f.uid". tablesort_sql($header), 0, $nid);
+    array('data' => t('Commits'), 'field' => 'commits'),
+  );
+  // Use FALSE for the 3rd argument to get all committers, not just the users
+  // that currently have commit access.
+  $committers = cvs_get_project_committers($project->nid, tablesort_sql($header), FALSE);
   $rows = array();
-  while ($contributor = db_fetch_object($result)) {
+  foreach ($committers as $committer) {
     $rows[] = array(
-      theme('username', $contributor),
-      t('!time ago', array('!time' => format_interval(time() - $contributor->last_commit, 1))),
-      t('!time ago', array('!time' => format_interval(time() - $contributor->first_commit, 1))),
-      format_plural($contributor->commits, '1 commit', '@count commits')
+      theme('username', $committer),
+      t('!time ago', array('!time' => format_interval(time() - $committer->last_commit, 1))),
+      t('!time ago', array('!time' => format_interval(time() - $committer->first_commit, 1))),
+      format_plural($committer->commits, '1 commit', '@count commits')
     );
   }
-
   return theme('table', $header, $rows);
 }
 
@@ -2710,13 +2903,7 @@ function cvs_project_issue_assignees(&$a
   // maintainer of the current project and thus we can skip the next, more
   // expensive query.
   if (project_use_cvs($project) && db_result(db_query("SELECT COUNT(*) FROM {cvs_accounts} WHERE uid = %d AND status = %d", $user->uid, CVS_APPROVED))) {
-    // Make an array with all maintainers of the current project.
-    $result = db_query("SELECT cpm.uid, u.name FROM {cvs_project_maintainers} cpm INNER JOIN {users} u ON cpm.uid = u.uid WHERE cpm.nid = %d", $project->nid);
-    $maintainers = array($project->uid => $project->name);
-    while ($row = db_fetch_object($result)) {
-      $maintainers[$row->uid] = $row->name;
-    }
-
+    $maintainers = cvs_get_project_maintainers($node);
     // Determine if the current user is one of the maintainers of the project.
     if (isset($maintainers[$user->uid])) {
       // Add any maintainers of this project who are not already in the
cvs diff: Diffing xcvs
Index: xcvs/xcvs-loginfo.php
===================================================================
RCS file: /Users/wright/drupal/local_repo/contributions/modules/cvslog/xcvs/xcvs-loginfo.php,v
retrieving revision 1.13
diff -u -p -r1.13 xcvs-loginfo.php
--- xcvs/xcvs-loginfo.php	3 Feb 2009 00:23:13 -0000	1.13
+++ xcvs/xcvs-loginfo.php	17 Apr 2009 18:27:31 -0000
@@ -231,6 +231,10 @@ function xcvs_init($argc, $argv) {
           }
         }
 
+        // Invalidate the cache for the CVS maintainers block for this project.
+        $cid = 'cvs_project_maintainers:'. $nid;
+        mysql_query("DELETE FROM cvs_cache_block WHERE cid = '$cid'");
+
         mysql_close($connection);
      }
 
