16
16
import re
17
17
import sys
18
18
from dataclasses import dataclass
19
+ from datetime import date
19
20
from typing import Any , Optional
20
21
21
22
import requests
@@ -87,11 +88,18 @@ def fetch_pr_info(pr_number: int) -> Optional[PrInfo]:
87
88
88
89
89
90
def 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 ) :
92
93
title = str (match .group (1 ))
93
94
pr_number = int (match .group (2 ))
94
95
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
+
95
103
else :
96
104
return CommitInfo (hexsha = commit .hexsha , title = commit .summary , pr_number = None )
97
105
@@ -110,13 +118,43 @@ def print_section(crate: str, items: list[str]) -> None:
110
118
print ()
111
119
112
120
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
+
113
149
def main () -> None :
114
150
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" )
116
152
args = parser .parse_args ()
117
153
154
+ commit_range = calc_commit_range (args .version )
155
+
118
156
repo = Repo ("." )
119
- commits = list (repo .iter_commits (args . commit_range ))
157
+ commits = list (repo .iter_commits (commit_range ))
120
158
commits .reverse () # Most recent last
121
159
commit_infos = list (map (get_commit_info , commits ))
122
160
@@ -170,8 +208,9 @@ def main() -> None:
170
208
line = line [0 ].upper () + line [1 :] # Upper-case first letter
171
209
prs [i ] = line
172
210
211
+ print (f"## { args .version } - { date .today ()} " )
173
212
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 } " )
175
214
print ()
176
215
print_section ("PRs" , prs )
177
216
print_section ("Unsorted commits" , unsorted_commits )
0 commit comments