cvs-release-notes

  1. #!/usr/bin/php
  2. <?php
  3.  
  4. // $Id: cvs-release-notes.php,v 1.6 2007/03/06 09:37:48 dww Exp $
  5.  
  6. /**
  7.  * @file
  8.  * Parses all CVS log messages between 2 release tags and automatically
  9.  * generates initial HTML for the release notes. This script must be
  10.  * run inside the root directory of a local CVS workspace of the project
  11.  * you want to generate release notes for.  Assumes "cvs" is in your
  12.  * PATH, and that the workspace has already been checked out with the
  13.  * appropriate CVSROOT.
  14.  *
  15.  * Usage:
  16.  * cvs-release-notes.php [previous-release-tag] [current-release-tag]
  17.  *
  18.  * TODO:
  19.  * - Option to include patch committer if "by" isn't included in message
  20.  * - Pretty formatting of previous release version (instead of the tag)
  21.  * - Lookup issues on d.o to group changes by issue type (bug, feature)
  22.  * - Should strip out leading dashes: "- something"
  23.  * - Should remove the word "Patch " before patch #s so they are
  24.  *   formatted consistently.
  25.  *
  26.  * @author Derek Wright (http://drupal.org/user/46549)
  27.  *
  28.  */
  29.  
  30. if (count($argv) < 3) {
  31.   usage("You must specify the release tags to compare");
  32. }
  33. $prev = $argv[1];
  34. $cur = $argv[2];
  35.  
  36. if (!is_dir('CVS')) {
  37.   usage("You must run this script in a local CVS workspace for your project");
  38. }
  39.  
  40. $changes = get_changes($prev, $cur);
  41. print "<p>Changes since $prev:</p>\n";
  42. print_changes($changes);
  43.  
  44.  
  45. function usage($msg = NULL) {
  46.   global $argv;
  47.   if (!empty($msg)) {
  48.     print "ERROR: $msg\n";
  49.   }
  50.   print <<<EOF
  51. Usage: $argv[0] [previous_release_tag] [current_release_tag]
  52. For example:
  53. $argv[0] DRUPAL-4-7--1-0 DRUPAL-4-7--1-1
  54. EOF;
  55.   exit(empty($msg) ? 0 : 1);
  56. }
  57.  
  58. // Based loosely on cvs.module cvs_process_log()
  59. function get_changes($prev, $cur) {
  60.   $changes = array();
  61.   $rval = '';
  62.   $logs = array();
  63.   exec("cvs -qf log -NS -r$prev::$cur 2>&1", $logs, $rval);
  64.   if ($rval) {
  65.     print "ERROR: 'cvs log' returned failure: $rval";
  66.     print implode("\n", $logs);
  67.     exit(1);
  68.   }
  69.   $msg_sep = '----------------------------';
  70.   $file_sep = '=============================================================================';
  71.   $in_log = false;
  72.   while (($line = next($logs)) !== false) {
  73.     if (preg_match('/^cvs log:.*$/', $line)) {
  74.       next;
  75.     }
  76.     if (trim($line) == $msg_sep || $in_log) {
  77.       if (!$in_log) {
  78.         $in_log = true;
  79.       }
  80.       $entry = new stdClass();
  81.       $parts = explode(' ', next($logs));
  82.       $entry->revision = trim($parts[1]);
  83.       $parts = explode(';', next($logs));
  84.       $entry->date = strtotime(cvs_explode($parts[0]));
  85.       $entry->user = cvs_explode($parts[1]);
  86.       $entry->state = cvs_explode($parts[2]);
  87.       $entry->commitid = cvs_explode($parts[4]);
  88.       $parts = explode(' ', cvs_explode($parts[3]));
  89.       $entry->lines_added = abs($parts[0]);
  90.       $entry->lines_removed = abs($parts[1]);
  91.       $temp = next($logs);
  92.       $comment = substr($temp, 0, 9) != 'branches:' ? $temp : '';
  93.       $cur_log = true;
  94.       while ($cur_log && ($line = next($logs))) {
  95.         if (trim($line) == $msg_sep) {
  96.           prev($logs);  // Need to rewind so our outer loop isn't confused.
  97.           $cur_log = false;
  98.         }
  99.         elseif (trim($line) == $file_sep) {
  100.           $cur_log = false;
  101.           $in_log = false;
  102.         }
  103.         else {
  104.           $comment .= "\n" . $line;
  105.         }
  106.       }
  107.       $entry->comment = trim($comment);
  108.       $changes[$entry->commitid] = $entry;
  109.     }
  110.   }
  111.   return $changes;
  112. }
  113.  
  114. function print_changes($changes) {
  115.   // Sort changes chronologically
  116.   usort($changes, 'log_date_cmp');
  117.   print "<ul>\n";
  118.   foreach ($changes as $k => $obj) {
  119.     print '<li>' . preg_replace('/#(\d+)/', '<a href="/node/$1">#$1</a>', $obj->comment) . "</li>\n";
  120.   }
  121.   print "</ul>\n";
  122. }
  123.  
  124. function cvs_explode($text, $delim = ':') {
  125.   $parts = explode($delim, $text, 2);
  126.   return trim($parts[1]);
  127. }
  128.  
  129. function log_date_cmp($a, $b) {
  130.   if ($a->date == $b->date) {
  131.     return 0;
  132.   }
  133.   return ($a->date < $b->date) ? -1 : 1;
  134. }