1616import re
1717import sys
1818from dataclasses import dataclass
19+ from datetime import date
1920from typing import Any , Optional
2021
2122import requests
@@ -87,11 +88,18 @@ def fetch_pr_info(pr_number: int) -> Optional[PrInfo]:
8788
8889
8990def get_commit_info (commit : Any ) -> CommitInfo :
90- match = re . match ( r"(.*) \(#(\d+)\)" , commit . summary )
91- if match :
91+ # Squash-merge commits:
92+ if match := re . match ( r"(.*) \(#(\d+)\)" , commit . summary ) :
9293 title = str (match .group (1 ))
9394 pr_number = int (match .group (2 ))
9495 return CommitInfo (hexsha = commit .hexsha , title = title , pr_number = pr_number )
96+
97+ # Normal merge commits:
98+ elif match := re .match (r"Merge pull request #(\d+) from (.*)" , commit .summary ):
99+ title = str (match .group (2 ))
100+ pr_number = int (match .group (1 ))
101+ return CommitInfo (hexsha = commit .hexsha , title = title , pr_number = pr_number )
102+
95103 else :
96104 return CommitInfo (hexsha = commit .hexsha , title = commit .summary , pr_number = None )
97105
@@ -110,13 +118,43 @@ def print_section(crate: str, items: list[str]) -> None:
110118 print ()
111119
112120
121+ def calc_commit_range (new_version : str ) -> str :
122+ parts = new_version .split ("." )
123+ assert len (parts ) == 3 , "Expected version to be on the format X.Y.Z"
124+ major = int (parts [0 ])
125+ minor = int (parts [1 ])
126+ patch = int (parts [2 ])
127+
128+ if 0 < patch :
129+ # A patch release.
130+ # Include changes since last patch release.
131+ # This assumes we've cherry-picked stuff for this release.
132+ diff_since_version = f"0.{ minor } .{ patch - 1 } "
133+ elif 0 < minor :
134+ # A minor release
135+ # The diff should span everything since the last minor release.
136+ # The script later excludes duplicated automatically, so we don't include stuff that
137+ # was part of intervening patch releases.
138+ diff_since_version = f"{ major } .{ minor - 1 } .0"
139+ else :
140+ # A major release
141+ # The diff should span everything since the last major release.
142+ # The script later excludes duplicated automatically, so we don't include stuff that
143+ # was part of intervening minor/patch releases.
144+ diff_since_version = f"{ major - 1 } .{ minor } .0"
145+
146+ return f"{ diff_since_version } ..HEAD"
147+
148+
113149def main () -> None :
114150 parser = argparse .ArgumentParser (description = "Generate a changelog." )
115- parser .add_argument ("--commit-range " , help = "e.g. 0.1.0..HEAD" , required = True )
151+ parser .add_argument ("--version " , required = True , help = "The version of the new release, e.g. 0.42.0" )
116152 args = parser .parse_args ()
117153
154+ commit_range = calc_commit_range (args .version )
155+
118156 repo = Repo ("." )
119- commits = list (repo .iter_commits (args . commit_range ))
157+ commits = list (repo .iter_commits (commit_range ))
120158 commits .reverse () # Most recent last
121159 commit_infos = list (map (get_commit_info , commits ))
122160
@@ -170,8 +208,9 @@ def main() -> None:
170208 line = line [0 ].upper () + line [1 :] # Upper-case first letter
171209 prs [i ] = line
172210
211+ print (f"## { args .version } - { date .today ()} " )
173212 print ()
174- print (f"Full diff at https://github.com/{ OWNER } /{ REPO } /compare/{ args . commit_range } " )
213+ print (f"Full diff at https://github.com/{ OWNER } /{ REPO } /compare/{ commit_range } " )
175214 print ()
176215 print_section ("PRs" , prs )
177216 print_section ("Unsorted commits" , unsorted_commits )
0 commit comments