release-notes.pl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #!/usr/bin/env perl
  2. #***************************************************************************
  3. # _ _ ____ _
  4. # Project ___| | | | _ \| |
  5. # / __| | | | |_) | |
  6. # | (__| |_| | _ <| |___
  7. # \___|\___/|_| \_\_____|
  8. #
  9. # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  10. #
  11. # This software is licensed as described in the file COPYING, which
  12. # you should have received as part of this distribution. The terms
  13. # are also available at https://curl.se/docs/copyright.html.
  14. #
  15. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  16. # copies of the Software, and permit persons to whom the Software is
  17. # furnished to do so, under the terms of the COPYING file.
  18. #
  19. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  20. # KIND, either express or implied.
  21. #
  22. # SPDX-License-Identifier: curl
  23. #
  24. ###########################################################################
  25. ###############################################
  26. #
  27. # ==== How to use this script ====
  28. #
  29. # 1. Get recent commits added to RELEASE-NOTES:
  30. #
  31. # $ ./scripts/release-notes.pl
  32. #
  33. # 2. Edit RELEASE-NOTES and remove all entries that don't belong. Unused
  34. # references below will be cleaned up in the next step. Make sure to move
  35. # "changes" up to the changes section. All entries will by default be listed
  36. # under bug-fixes as this script can't know where to put them.
  37. #
  38. # 3. Run the cleanup script and let it sort the entries and remove unused
  39. # references from lines you removed in step (2):
  40. #
  41. # $ ./scripts/release-notes.pl cleanup
  42. #
  43. # 4. Reload RELEASE-NOTES and verify that things look okay. The cleanup
  44. # procedure can and should be re-run when lines are removed or rephrased.
  45. #
  46. # 5. Run ./scripts/contributors.sh and update the contributor list of names
  47. # The list can also be extended or edited manually.
  48. #
  49. # 6. Run ./scripts/delta and update the contributor count at the top, and
  50. # double-check/update the other counters.
  51. #
  52. # 7. Commit the file using "RELEASE-NOTES: synced" as commit message.
  53. #
  54. ################################################
  55. my $cleanup = ($ARGV[0] eq "cleanup");
  56. my @gitlog=`git log @^{/RELEASE-NOTES:.synced}..` if(!$cleanup);
  57. my @releasenotes=`cat RELEASE-NOTES`;
  58. my @o; # the entire new RELEASE-NOTES
  59. my @refused; # [num] = [2 bits of use info]
  60. my @refs; # [number] = [URL]
  61. for my $l (@releasenotes) {
  62. if($l =~ /^ o .*\[(\d+)\]/) {
  63. # referenced, set bit 0
  64. $refused[$1]=1;
  65. }
  66. elsif($l =~ /^ \[(\d+)\] = (.*)/) {
  67. # listed in a reference, set bit 1
  68. $refused[$1] |= 2;
  69. $refs[$1] = $2;
  70. }
  71. }
  72. # Return a new fresh reference number
  73. sub getref {
  74. for my $r (1 .. $#refs) {
  75. if(!$refused[$r] & 1) {
  76. return $r;
  77. }
  78. }
  79. # add at the end
  80. return $#refs + 1;
  81. }
  82. # '#num'
  83. # 'num'
  84. # 'https://github.com/curl/curl/issues/6939'
  85. # 'https://github.com/curl/curl-www/issues/69'
  86. # 'https://elsewhere.example.com/discussion'
  87. sub extract {
  88. my ($ref)=@_;
  89. if($ref =~ /^(\#|)(\d+)/) {
  90. # return the plain number
  91. return $2;
  92. }
  93. elsif($ref =~ /^https:\/\/github.com\/curl\/curl\/.*\/(\d+)/) {
  94. # return the plain number
  95. return $1;
  96. }
  97. elsif($ref =~ /:\/\//) {
  98. # contains a '://', return the URL
  99. return $ref;
  100. }
  101. # false alarm, not a valid line
  102. }
  103. my $short;
  104. my $first;
  105. for my $l (@gitlog) {
  106. chomp $l;
  107. if($l =~ /^commit/) {
  108. if($first) {
  109. onecommit($short);
  110. }
  111. # starts a new commit
  112. undef @fixes;
  113. undef @closes;
  114. undef @bug;
  115. $short = "";
  116. $first = 0;
  117. }
  118. elsif(($l =~ /^ (.*)/) && !$first) {
  119. # first line
  120. $short = $1;
  121. $short =~ s/ ?\[(ci skip|skip ci)\]//g;
  122. $first = 1;
  123. push @line, $short;
  124. }
  125. elsif(($l =~ /^ (.*)/) && $first) {
  126. # not the first
  127. my $line = $1;
  128. if($line =~ /^Fixes(:|) *(.*)/i) {
  129. my $ref = extract($2);
  130. push @fixes, $ref if($ref);
  131. }
  132. elsif($line =~ /^Clo(s|)es(:|) *(.*)/i) {
  133. my $ref = extract($3);
  134. push @closes, $ref if($ref);
  135. }
  136. elsif($line =~ /^Bug: (.*)/i) {
  137. my $ref = extract($1);
  138. push @bug, $ref if($ref);
  139. }
  140. }
  141. }
  142. if($first) {
  143. onecommit($short);
  144. }
  145. # call at the end of a parsed commit
  146. sub onecommit {
  147. my ($short)=@_;
  148. my $ref;
  149. if($bug[0]) {
  150. $ref = $bug[0];
  151. }
  152. elsif($fixes[0]) {
  153. $ref = $fixes[0];
  154. }
  155. elsif($closes[0]) {
  156. $ref = $closes[0];
  157. }
  158. if($ref =~ /^#?(\d+)/) {
  159. $ref = "https://curl.se/bug/?i=$1"
  160. }
  161. if($ref) {
  162. my $r = getref();
  163. $refs[$r] = $ref;
  164. $moreinfo{$short}=$r;
  165. $refused[$r] |= 1;
  166. }
  167. }
  168. #### Output the new RELEASE-NOTES
  169. my @bullets;
  170. for my $l (@releasenotes) {
  171. if(($l =~ /^This release includes the following bugfixes:/) && !$cleanup) {
  172. push @o, $l;
  173. push @o, "\n";
  174. for my $f (@line) {
  175. push @o, sprintf " o %s%s\n", $f,
  176. $moreinfo{$f}? sprintf(" [%d]", $moreinfo{$f}): "";
  177. $refused[$moreinfo{$f}]=3;
  178. }
  179. push @o, " --- new entries are listed above this ---";
  180. next;
  181. }
  182. elsif($cleanup) {
  183. if($l =~ /^ --- new entries are listed/) {
  184. # ignore this if still around
  185. next;
  186. }
  187. elsif($l =~ /^ o .*/) {
  188. push @bullets, $l;
  189. next;
  190. }
  191. elsif($bullets[0]) {
  192. # output them case insensitively
  193. for my $b (sort { "\L$a" cmp "\L$b" } @bullets) {
  194. push @o, $b;
  195. }
  196. undef @bullets;
  197. }
  198. }
  199. if($l =~ /^ \[(\d+)\] = /) {
  200. # stop now
  201. last;
  202. }
  203. else {
  204. push @o, $l;
  205. }
  206. }
  207. my @srefs;
  208. my $ln;
  209. for my $n (1 .. $#refs) {
  210. my $r = $refs[$n];
  211. if($r && ($refused[$n] & 1)) {
  212. push @o, sprintf " [%d] = %s\n", $n, $r;
  213. }
  214. }
  215. open(O, ">RELEASE-NOTES");
  216. for my $l (@o) {
  217. print O $l;
  218. }
  219. close(O);
  220. exit;
  221. # Debug: show unused references
  222. for my $r (1 .. $#refs) {
  223. if($refused[$r] != 3) {
  224. printf "%s is %d!\n", $r, $refused[$r];
  225. }
  226. }