| 1 |
#!/usr/bin/php
|
| 2 |
<?php
|
| 3 |
|
| 4 |
// $Id: cvs-release-notes.php,v 1.7 2008/02/13 22:15:33 weitzman 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 |
// This line allows you keep one copy of this script at a given location.
|
| 37 |
// Setup a shell alias to this file and then just call the alias from the dir
|
| 38 |
// whose notes need generating.
|
| 39 |
chdir(getcwd());
|
| 40 |
|
| 41 |
if (!is_dir("CVS")) {
|
| 42 |
usage("You must run this script in a local CVS workspace for your project");
|
| 43 |
}
|
| 44 |
|
| 45 |
$changes = get_changes($prev, $cur);
|
| 46 |
print "<p>Changes since $prev:</p>\n";
|
| 47 |
print_changes($changes);
|
| 48 |
|
| 49 |
|
| 50 |
function usage($msg = NULL) {
|
| 51 |
global $argv;
|
| 52 |
if (!empty($msg)) {
|
| 53 |
print "ERROR: $msg\n";
|
| 54 |
}
|
| 55 |
print <<<EOF
|
| 56 |
Usage: $argv[0] [previous_release_tag] [current_release_tag]
|
| 57 |
For example:
|
| 58 |
$argv[0] DRUPAL-4-7--1-0 DRUPAL-4-7--1-1
|
| 59 |
|
| 60 |
EOF;
|
| 61 |
exit(empty($msg) ? 0 : 1);
|
| 62 |
}
|
| 63 |
|
| 64 |
// Based loosely on cvs.module cvs_process_log()
|
| 65 |
function get_changes($prev, $cur) {
|
| 66 |
$changes = array();
|
| 67 |
$rval = '';
|
| 68 |
$logs = array();
|
| 69 |
exec("cvs -qf log -NS -r$prev::$cur 2>&1", $logs, $rval);
|
| 70 |
if ($rval) {
|
| 71 |
print "ERROR: 'cvs log' returned failure: $rval";
|
| 72 |
print implode("\n", $logs);
|
| 73 |
exit(1);
|
| 74 |
}
|
| 75 |
$msg_sep = '----------------------------';
|
| 76 |
$file_sep = '=============================================================================';
|
| 77 |
$in_log = false;
|
| 78 |
while (($line = next($logs)) !== false) {
|
| 79 |
if (preg_match('/^cvs log:.*$/', $line)) {
|
| 80 |
// This line was a warning generated by cvs log, skip it.
|
| 81 |
// @todo An alternative to this would be redirecting STDERR to /dev/null... Why aren't we doing that?
|
| 82 |
continue;
|
| 83 |
}
|
| 84 |
if (trim($line) == $msg_sep || $in_log) {
|
| 85 |
if (!$in_log) {
|
| 86 |
$in_log = true;
|
| 87 |
}
|
| 88 |
$entry = new stdClass();
|
| 89 |
$parts = explode(' ', next($logs));
|
| 90 |
$entry->revision = trim($parts[1]);
|
| 91 |
$parts = explode(';', next($logs));
|
| 92 |
$entry->date = strtotime(cvs_explode($parts[0]));
|
| 93 |
$entry->user = cvs_explode($parts[1]);
|
| 94 |
$entry->state = cvs_explode($parts[2]);
|
| 95 |
$entry->commitid = cvs_explode($parts[4]);
|
| 96 |
$parts = explode(' ', cvs_explode($parts[3]));
|
| 97 |
$entry->lines_added = abs($parts[0]);
|
| 98 |
$entry->lines_removed = abs($parts[1]);
|
| 99 |
$temp = next($logs);
|
| 100 |
$comment = substr($temp, 0, 9) != 'branches:' ? $temp : '';
|
| 101 |
$cur_log = true;
|
| 102 |
while ($cur_log && ($line = next($logs)) !== FALSE) {
|
| 103 |
if (trim($line) == $msg_sep) {
|
| 104 |
prev($logs); // Need to rewind so our outer loop isn't confused.
|
| 105 |
$cur_log = false;
|
| 106 |
}
|
| 107 |
elseif (trim($line) == $file_sep) {
|
| 108 |
$cur_log = false;
|
| 109 |
$in_log = false;
|
| 110 |
}
|
| 111 |
else {
|
| 112 |
$comment .= "\n" . $line;
|
| 113 |
}
|
| 114 |
}
|
| 115 |
$entry->comment = trim($comment);
|
| 116 |
$changes[$entry->commitid] = $entry;
|
| 117 |
}
|
| 118 |
}
|
| 119 |
return $changes;
|
| 120 |
}
|
| 121 |
|
| 122 |
function print_changes($changes) {
|
| 123 |
// Sort changes chronologically
|
| 124 |
usort($changes, 'log_date_cmp');
|
| 125 |
print "<ul>\n";
|
| 126 |
foreach ($changes as $k => $obj) {
|
| 127 |
print '<li>' . preg_replace('/#(\d+)/', '<a href="/node/$1">#$1</a>', $obj->comment) . "</li>\n";
|
| 128 |
}
|
| 129 |
print "</ul>\n";
|
| 130 |
}
|
| 131 |
|
| 132 |
function cvs_explode($text, $delim = ':') {
|
| 133 |
$parts = explode($delim, $text, 2);
|
| 134 |
return trim($parts[1]);
|
| 135 |
}
|
| 136 |
|
| 137 |
function log_date_cmp($a, $b) {
|
| 138 |
if ($a->date == $b->date) {
|
| 139 |
return 0;
|
| 140 |
}
|
| 141 |
return ($a->date < $b->date) ? -1 : 1;
|
| 142 |
}
|