#!/usr/bin/php
<?php
// $Id: cvs-release-notes.php,v 1.6 2007/03/06 09:37:48 dww Exp $
/**
* @file
* Parses all CVS log messages between 2 release tags and automatically
* generates initial HTML for the release notes. This script must be
* run inside the root directory of a local CVS workspace of the project
* you want to generate release notes for. Assumes "cvs" is in your
* PATH, and that the workspace has already been checked out with the
* appropriate CVSROOT.
*
* Usage:
* cvs-release-notes.php [previous-release-tag] [current-release-tag]
*
* TODO:
* - Option to include patch committer if "by" isn't included in message
* - Pretty formatting of previous release version (instead of the tag)
* - Lookup issues on d.o to group changes by issue type (bug, feature)
* - Should strip out leading dashes: "- something"
* - Should remove the word "Patch " before patch #s so they are
* formatted consistently.
*
* @author Derek Wright (http://drupal.org/user/46549)
*
*/
usage("You must specify the release tags to compare");
}
$prev = $argv[1];
$cur = $argv[2];
usage("You must run this script in a local CVS workspace for your project");
}
$changes = get_changes($prev, $cur);
print "<p>Changes since $prev:</p>\n";
print_changes($changes);
function usage($msg = NULL) {
}
Usage: $argv[0] [previous_release_tag] [current_release_tag]
For example:
$argv[0] DRUPAL-4-7--1-0 DRUPAL-4-7--1-1
EOF;
}
// Based loosely on cvs.module cvs_process_log()
function get_changes($prev, $cur) {
$rval = '';
exec("cvs -qf log -NS -r$prev::$cur 2>&1",
$logs,
$rval);
if ($rval) {
print "ERROR: 'cvs log' returned failure: $rval";
}
$msg_sep = '----------------------------';
$file_sep = '=============================================================================';
$in_log = false;
while (($line =
next($logs)) !==
false) {
}
if (trim($line) ==
$msg_sep ||
$in_log) {
if (!$in_log) {
$in_log = true;
}
$entry = new stdClass();
$entry->
revision =
trim($parts[1]);
$entry->
date =
strtotime(cvs_explode
($parts[0]));
$entry->user = cvs_explode($parts[1]);
$entry->state = cvs_explode($parts[2]);
$entry->commitid = cvs_explode($parts[4]);
$parts =
explode(' ', cvs_explode
($parts[3]));
$entry->
lines_added =
abs($parts[0]);
$entry->
lines_removed =
abs($parts[1]);
$comment =
substr($temp,
0,
9) !=
'branches:' ?
$temp :
'';
$cur_log = true;
while ($cur_log &&
($line =
next($logs))) {
if (trim($line) ==
$msg_sep) {
prev($logs);
// Need to rewind so our outer loop isn't confused.
$cur_log = false;
}
elseif (trim($line) ==
$file_sep) {
$cur_log = false;
$in_log = false;
}
else {
$comment .= "\n" . $line;
}
}
$entry->
comment =
trim($comment);
$changes[$entry->commitid] = $entry;
}
}
return $changes;
}
function print_changes($changes) {
// Sort changes chronologically
usort($changes,
'log_date_cmp');
foreach ($changes as $k => $obj) {
print '<li>' .
preg_replace('/#(\d+)/',
'<a href="/node/$1">#$1</a>',
$obj->
comment) .
"</li>\n";
}
}
function cvs_explode($text, $delim = ':') {
$parts =
explode($delim,
$text,
2);
}
function log_date_cmp($a, $b) {
if ($a->date == $b->date) {
return 0;
}
return ($a->date < $b->date) ? -1 : 1;
}