Skip to content

Commit e8814f3

Browse files
committed
docs(changelog): improve changelog generation
Signed-off-by: Thierry Bugier <[email protected]>
1 parent 62e0de1 commit e8814f3

File tree

1 file changed

+126
-30
lines changed

1 file changed

+126
-30
lines changed

RoboFile.php

Lines changed: 126 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,9 @@ public static function createCommitList($commits) {
694694
$line = explode(' ', $commit, 2);
695695
$commitObject = new StdClass();
696696
$commitObject->hash = $line[0];
697-
$commitObject->message = $line[1];
697+
$commitObject->subject = $line[1];
698698
$commitObjects[] = $commitObject;
699+
$commitObject->body = self::getCommitBody($commitObject->hash);
699700
}
700701

701702
return $commitObjects;
@@ -767,6 +768,23 @@ public static function getFileFromGit($path, $rev) {
767768
}
768769
return $output;
769770
}
771+
772+
public static function getCommitBody($hash) {
773+
$output = shell_exec("git log $hash --max-count=1 --pretty=format:\"%b\"");
774+
if ($output === null) {
775+
throw new Exception ("could not get commit body");
776+
}
777+
return $output;
778+
}
779+
780+
public static function getCurrentBranch() {
781+
$output = shell_exec("git rev-parse --abbrev-ref HEAD");
782+
if ($output === null) {
783+
throw new Exception ("could not get current branch");
784+
}
785+
return $output;
786+
787+
}
770788
}
771789

772790
class ConventionalChangelog
@@ -776,22 +794,22 @@ class ConventionalChangelog
776794
*/
777795
public static function filterCommits($commits) {
778796
$types = [
779-
'build', 'ci', 'docs', 'fix', 'feat', 'perf', 'refactor', 'style', 'test'
797+
'build', 'chore', 'ci', 'docs', 'fix', 'feat', 'perf', 'refactor', 'style', 'test'
780798
];
781799
$types = implode('|', $types);
782800
$scope = "(\([^\)]*\))?";
783-
$message = ".*";
784-
$filter = "/^(?P<type>$types)(?P<scope>$scope):(?P<message>$message)$/";
801+
$subject = ".*";
802+
$filter = "/^(?P<type>$types)(?P<scope>$scope):(?P<subject>$subject)$/";
785803
$filtered = [];
786804
$matches = null;
787805
foreach ($commits as $commit) {
788-
if (preg_match($filter, $commit->message, $matches) === 1) {
806+
if (preg_match($filter, $commit->subject, $matches) === 1) {
789807
$commit->type = $matches['type'];
790808
$commit->scope = '';
791809
if (isset($matches['scope']) && strlen($matches['scope']) > 0) {
792810
$commit->scope = $matches['scope'];
793811
}
794-
$commit->message = trim($matches['message']);
812+
$commit->subject = trim($matches['subject']);
795813
$filtered[] = $commit;
796814
}
797815
}
@@ -817,12 +835,69 @@ public static function compareCommits($a, $b) {
817835
return 0;
818836
}
819837

838+
820839
public static function buildLog($a, $b = 'HEAD') {
840+
if (!Git::tagExists($b)) {
841+
// $b is not a tag, try to find a matching one
842+
if (Git::getTagOfCommit($b) === false) {
843+
// throw new Exception("current HEAD does not match a tag");
844+
}
845+
}
846+
847+
// get All tags between $a and $b
848+
$tags = Git::getAllTags();
849+
850+
// Remove non semver compliant versions
851+
$tags = array_filter($tags, function ($tag) {
852+
return Semver::isSemVer($tag);
853+
});
854+
855+
$startVersion = $a;
856+
$endVersion = $b;
857+
$prefix = 'v';
858+
if (substr($startVersion, 0, strlen($prefix)) == $prefix) {
859+
$startVersion = substr($startVersion, strlen($prefix));
860+
}
861+
if (substr($endVersion, 0, strlen($prefix)) == $prefix) {
862+
$endVersion = substr($endVersion, strlen($prefix));
863+
}
864+
865+
$tags = array_filter($tags, function ($version) use ($startVersion, $endVersion) {
866+
$prefix = 'v';
867+
if (substr($version, 0, strlen($prefix)) == $prefix) {
868+
$version = substr($version, strlen($prefix));
869+
}
870+
if (version_compare($version, $startVersion) < 0) {
871+
return false;
872+
}
873+
if ($endVersion !== 'HEAD' && version_compare($version, $endVersion) > 0) {
874+
return false;
875+
}
876+
return true;
877+
});
878+
879+
// sort tags
880+
usort($tags, function ($a, $b) {
881+
return version_compare($a, $b);
882+
});
883+
884+
$log = [];
885+
$tags[] = $b;
886+
$startRef = array_shift($tags);
887+
while ($endRef = array_shift($tags)) {
888+
$log = array_merge($log, self::buildLogOneBump($startRef, $endRef));
889+
$startRef = $endRef;
890+
}
891+
892+
return $log;
893+
}
894+
895+
public static function buildLogOneBump($a, $b) {
821896
$tag = $b;
822897
if (!Git::tagExists($b)) {
823898
// $b is not a tag, try to find a matching one
824899
if ($tag = Git::getTagOfCommit($b) === false) {
825-
throw new Exception("current HEAD does not patch a tag");
900+
$tag = 'Unreleased';
826901
}
827902
}
828903

@@ -857,8 +932,14 @@ public static function buildLog($a, $b = 'HEAD') {
857932
// generate markdown log
858933
$log = [];
859934

860-
$tagDate = Git::getTagDate($tag)->format('-m-d');
861-
$compare = "$remote/compare/$a..$tag";
935+
$tagDate = (new DateTime())->format('Y-m-d');
936+
$compare = "$remote/compare/$a..";
937+
if ($tag !== 'Unreleased') {
938+
$tagDate = Git::getTagDate($tag)->format('Y-m-d');
939+
$compare .= $tag;
940+
} else {
941+
$compare .= Git::getCurrentBranch();
942+
}
862943
$log[] = '<a name="' . $tag . '"></a>';
863944
$log[] = '## [' . $tag . '](' . $compare . ') (' . $tagDate . ')';
864945
$log[] = '';
@@ -868,33 +949,15 @@ public static function buildLog($a, $b = 'HEAD') {
868949
$log[] = '### Bug Fixes';
869950
$log[] = '';
870951
foreach ($fixes as $commit) {
871-
$line = '* ';
872-
$scope = $commit->scope;
873-
if ($scope !== '') {
874-
$scope = "**$scope:**";
875-
$line .= " $scope";
876-
}
877-
$hash = $commit->hash;
878-
$line .= " $commit->message"
879-
. "([$hash]($remote/commit/$hash))";
880-
$log[] = $line;
952+
$log[] = self::buildLogLine($commit, $remote);
881953
}
882954
}
883955

884956
if (count($feats) > 0) {
885957
$log[] = '### Features';
886958
$log[] = '';
887959
foreach ($feats as $commit) {
888-
$line = '* ';
889-
$scope = $commit->scope;
890-
if ($scope !== '') {
891-
$scope = "**$scope:**";
892-
$line .= " $scope";
893-
}
894-
$hash = $commit->hash;
895-
$line .= " $commit->message"
896-
. "([$hash]($remote/commit/$hash))";
897-
$log[] = $line;
960+
$log[] = self::buildLogLine($commit, $remote);
898961
}
899962
}
900963

@@ -905,6 +968,40 @@ public static function buildLog($a, $b = 'HEAD') {
905968
return $log;
906969
}
907970

971+
public static function buildLogLine($commit, $remote) {
972+
$line = '* ';
973+
$scope = $commit->scope;
974+
if ($scope !== '') {
975+
$scope = "**$scope:**";
976+
$line .= " $scope";
977+
}
978+
$hash = $commit->hash;
979+
$line .= " $commit->subject"
980+
. "([$hash]($remote/commit/$hash))";
981+
982+
// Search for closed issues
983+
$body = explode(PHP_EOL, $commit->body);
984+
$pattern = '/^((close|closes|fix|fixed) #(?P<id>\\d+)(,\s+)?)/i';
985+
$commit->close = [];
986+
foreach ($body as $bodyLine) {
987+
$matches = null;
988+
if (preg_match($pattern, $bodyLine, $matches)) {
989+
if (!is_array($matches['id'])) {
990+
$matches['id'] = [$matches['id']];
991+
}
992+
$commit->close = $matches['id'];
993+
}
994+
}
995+
if (count($commit->close) > 0) {
996+
foreach ($commit->close as &$issue) {
997+
$issue = "[#$issue]($remote/issues/$issue)";
998+
}
999+
$line .= ', closes ' . implode(', ', $commit->close);
1000+
1001+
}
1002+
1003+
return $line;
1004+
}
9081005
}
9091006

9101007
class SemVer
@@ -925,5 +1022,4 @@ public static function isSemVer($version) {
9251022

9261023
return true;
9271024
}
928-
9291025
}

0 commit comments

Comments
 (0)