Skip to content

Commit

Permalink
build: improve changelog parsing and order (#768)
Browse files Browse the repository at this point in the history
  • Loading branch information
braindigitalis committed Aug 10, 2023
2 parents 282c0e0 + 8e2b104 commit 877d7d5
Showing 1 changed file with 125 additions and 30 deletions.
155 changes: 125 additions & 30 deletions buildtools/changelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

// D++ changelog generator, saves 15 minutes for each release :-)

// Categories, in display order
$catgroup = [
'💣 Breaking Changes' => [],
'✨ New Features' => [],
'🐞 Bug Fixes' => [],
'🚀 Performance Improvements' => [],
'♻️ Refactoring' => [],
'🚨 Testing' => [],
'📚 Documentation' => [],
'💎 Style Changes' => [],
'🔧 Chore' => [],
'📜 Miscellaneous Changes' => []
];

// Pattern list
$categories = [
'break' => '💣 Breaking Changes',
Expand Down Expand Up @@ -42,23 +56,16 @@
'updated' => '📜 Miscellaneous Changes',
];

$catgroup = [];
$changelog = [];
$githubstyle = true;
if (count($argv) > 2 && $argv[1] == '--discord') {
$githubstyle = false;
}
$errors = [];

// Magic sauce
exec("git log --format=\"%s\" $(git log --no-walk --tags | head -n1 | cut -d ' ' -f 2)..HEAD | grep -v '^Merge '", $changelog);

// Leadin
if ($githubstyle) {
echo "The changelog is listed below:\n\nRelease Changelog\n===========\n";
} else {
echo "The changelog is listed below:\n\n__**Release Changelog**__\n";
}

// Case insensitive removal of duplicates
$changelog = array_intersect_key($changelog, array_unique(array_map("strtolower", $changelog)));

Expand All @@ -78,6 +85,20 @@
}
}

function add_change(string $change, string $category, string $scope = null) {
global $catgroup;

if (isset($scope) && !empty($scope)) {
// Group by feature inside the section
if (!isset($catgroup[$category][$scope])) {
$catgroup[$category][$scope] = [];
}
$catgroup[$category][$scope][] = $change;
return;
}
$catgroup[$category][] = $change;
}

foreach ($changelog as $change) {

// Wrap anything that looks like a symbol name in backticks
Expand All @@ -92,41 +113,111 @@
$change = preg_replace("/\b(was|is|wo)nt\b/i", '$1n\'t', $change);
$change = preg_replace("/\bfreebsd\b/", 'FreeBSD', $change);
$change = preg_replace("/``/", "`", $change);
$change = trim($change);

// Match keywords against categories
$matched = false;
foreach ($categories as $cat => $header) {
// Purposefully ignored: comments that are one word, merge commits, and version bumps
if (strpos($change, ' ') === false || preg_match("/^Merge (branch|pull request|remote-tracking branch) /", $change) || preg_match("/version bump/i", $change)) {
$matched = true;
continue;
}
// Groupings
if ((preg_match("/^" . $cat . ":/i", $change)) || (preg_match("/^\[" . $cat . "\//i", $change)) || (preg_match("/^\[" . $cat . "\]/i", $change)) || (preg_match("/^\[" . $cat . ":/i", $change)) || (preg_match("/^" . $cat . "\//i", $change)) || (preg_match("/^" . $cat . ":/i", $change))) {
if (!isset($catgroup[$header])) {
$catgroup[$header] = [];
$matches = [];
// Extract leading category section
if (preg_match("/^((?:(?:[\w_]+(?:\([\w_]+\))?+)(?:[\s]*[,\/][\s]*)?)+):/i", $change, $matches) || preg_match("/^\[((?:(?:[\w_]+(?:\([\w_]+\))?+)(?:[\s]*[,\/][\s]*)?)+)\](?:\s*:)?/i", $change, $matches)) {
$categorysection = $matches[0];
$changecategories = $matches[1];
$matchflags = PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL;
// Extract each category and scope
if (preg_match_all("/(?:[\s]*)([\w_]+)(?:\(([\w_]+)\))?(?:[\s]*)(?:[,\/]+)?/i", $changecategories, $matches, $matchflags) !== false) {
/**
* Given a commit "foo, bar(foobar): add many foos and bars" :
* $matches is [
* 0 => [[0] => 'foo,', [1] => 'foo', [2] => null],
* 1 => [[0] => ' bar(foobar)', [1] => 'bar', [2] => 'foobar'],
* ]
* In other words, for a matched category N, matches[N][1] is the category, matches[N][2] is the scope
*/
$header = $matches[0][1];
$scope = $matches[0][2];
$change = trim(substr($change, strlen($categorysection)));
// List in breaking if present
foreach ($matches as $nb => $match) {
if ($nb == 0) // Skip the first category which will be added anyways
continue;
$category = $match[1];
if (isset($categories[$category]) && $categories[$category] === '💣 Breaking Changes')
add_change($change, $categories[$category], $scope);
}
if (!isset($categories[$header])) {
$errors[] = "could not find category \"" . $header . "\" for commit \"" . $change . "\", adding it to misc";
$header = $categories['misc'];
}
else {
$header = $categories[$header];
}
$matched = true;
$catgroup[$header][] = preg_replace("/^\S+\s+/", "", $change);
break;
} else if (preg_match("/^" . $cat . " /i", $change)) {
if (!isset($catgroup[$header])) {
$catgroup[$header] = [];
}
$matched = true;
$catgroup[$header][] = $change;
break;
// Ignore version bumps
if (!preg_match("/^(version )?bump/i", $change)) {
add_change($change, $header, $scope);
}
}
}
if (!$matched) { // Could not parse category section, try keywords
// Match keywords against categories
foreach ($categories as $cat => $header) {
// Purposefully ignored: comments that are one word, merge commits, and version bumps
if (strpos($change, ' ') === false || preg_match("/^Merge (branch|pull request|remote-tracking branch) /", $change) || preg_match("/^(version )?bump/i", $change)) {
$matched = true;
break;
}
if (preg_match("/^" . $cat . " /i", $change)) {
if (!isset($catgroup[$header])) {
$catgroup[$header] = [];
}
$matched = true;
$catgroup[$header][] = $change;
break;
}
}
}
if (!$matched) {
$errors[] = "could not guess category for commit \"" . $change . "\", adding it to misc";
$header = $categories['misc'];
if (!isset($catgroup[$header])) {
$catgroup[$header] = [];
}
$matched = true;
$catgroup[$header][] = $change;
}
}

// Leadin
if ($githubstyle) {
echo "The changelog is listed below:\n\nRelease Changelog\n===========\n";
} else {
echo "The changelog is listed below:\n\n__**Release Changelog**__\n";
}

function print_change(string $change) {
global $githubstyle;

// Exclude bad commit messages like 'typo fix', 'test push' etc by pattern
if (!preg_match("/^(typo|test|fix)\s\w+$/", $change) && strpos($change, ' ') !== false) {
echo ($githubstyle ? '-' : '') . ' ' . ucfirst(str_replace('@', '', $change)) . "\n";
}
}

// Output tidy formatting
foreach ($catgroup as $cat => $list) {
echo "\n" . ($githubstyle ? '## ' : '__**') . $cat . ($githubstyle ? '' : '**__') . "\n";
foreach ($list as $item) {
// Exclude bad commit messages like 'typo fix', 'test push' etc by pattern
if (!preg_match("/^(typo|test|fix)\s\w+$/", $item) && strpos($item, ' ') !== false) {
echo ($githubstyle ? '-' : '') . ' ' . ucfirst(str_replace('@', '', $item)) . "\n";
if (!empty($list)) {
echo "\n" . ($githubstyle ? '## ' : '__**') . $cat . ($githubstyle ? '' : '**__') . "\n";
foreach ($list as $key => $item) {
if (is_array($item)) {
foreach ($item as $change) {
print_change("$key: $change");
}
}
else {
print_change($item);
}
}
}
}
Expand All @@ -138,3 +229,7 @@
echo 'The ' . $version . ' download can be found here: <https://dl.dpp.dev/' . $version . '>';
echo "\n";
}

foreach ($errors as $err) {
trigger_error($err, E_USER_WARNING);
}

0 comments on commit 877d7d5

Please sign in to comment.