@@ -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
772790class 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
9101007class SemVer
@@ -925,5 +1022,4 @@ public static function isSemVer($version) {
9251022
9261023 return true ;
9271024 }
928-
9291025}
0 commit comments