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