2929
3030import re
3131from collections import OrderedDict , defaultdict
32- from collections .abc import Generator , Iterable , Mapping , Sequence
32+ from collections .abc import Generator , Iterable , Mapping , MutableMapping , Sequence
3333from dataclasses import dataclass
3434from datetime import date
35+ from itertools import chain
3536from typing import TYPE_CHECKING , Any
3637
3738from jinja2 import (
@@ -88,33 +89,32 @@ def generate_tree_from_commits(
8889 pat = re .compile (changelog_pattern )
8990 map_pat = re .compile (commit_parser , re .MULTILINE )
9091 body_map_pat = re .compile (commit_parser , re .MULTILINE | re .DOTALL )
91- current_tag : GitTag | None = None
9292 rules = rules or TagRules ()
9393
9494 # Check if the latest commit is not tagged
95- if commits :
96- latest_commit = commits [0 ]
97- current_tag = get_commit_tag (latest_commit , tags )
98-
99- current_tag_name : str = unreleased_version or "Unreleased"
100- current_tag_date : str = ""
101- if unreleased_version is not None :
102- current_tag_date = date .today ().isoformat ()
103- if current_tag is not None and current_tag .name :
104- current_tag_name = current_tag .name
105- current_tag_date = current_tag .date
10695
96+ current_tag = get_commit_tag (commits [0 ], tags ) if commits else None
97+ current_tag_name = unreleased_version or "Unreleased"
98+ current_tag_date = (
99+ date .today ().isoformat () if unreleased_version is not None else ""
100+ )
101+
102+ used_tags : set [GitTag ] = set ()
103+ if current_tag :
104+ used_tags .add (current_tag )
105+ if current_tag .name :
106+ current_tag_name = current_tag .name
107+ current_tag_date = current_tag .date
108+
109+ commit_tag : GitTag | None = None
107110 changes : dict = defaultdict (list )
108- used_tags : list = [current_tag ]
109111 for commit in commits :
110- commit_tag = get_commit_tag (commit , tags )
111-
112112 if (
113- commit_tag
113+ ( commit_tag := get_commit_tag ( commit , tags ))
114114 and commit_tag not in used_tags
115115 and rules .include_in_changelog (commit_tag )
116116 ):
117- used_tags .append (commit_tag )
117+ used_tags .add (commit_tag )
118118 release = {
119119 "version" : current_tag_name ,
120120 "date" : current_tag_date ,
@@ -127,24 +127,15 @@ def generate_tree_from_commits(
127127 current_tag_date = commit_tag .date
128128 changes = defaultdict (list )
129129
130- matches = pat .match (commit .message )
131- if not matches :
130+ if not pat .match (commit .message ):
132131 continue
133132
134- # Process subject from commit message
135- if message := map_pat .match (commit .message ):
136- process_commit_message (
137- changelog_message_builder_hook ,
138- message ,
139- commit ,
140- changes ,
141- change_type_map ,
142- )
143-
144- # Process body from commit message
145- body_parts = commit .body .split ("\n \n " )
146- for body_part in body_parts :
147- if message := body_map_pat .match (body_part ):
133+ # Process subject and body from commit message
134+ for message in chain (
135+ [map_pat .match (commit .message )],
136+ (body_map_pat .match (block ) for block in commit .body .split ("\n \n " )),
137+ ):
138+ if message :
148139 process_commit_message (
149140 changelog_message_builder_hook ,
150141 message ,
@@ -167,8 +158,8 @@ def process_commit_message(
167158 hook : MessageBuilderHook | None ,
168159 parsed : re .Match [str ],
169160 commit : GitCommit ,
170- changes : dict [str | None , list ],
171- change_type_map : dict [str , str ] | None = None ,
161+ ref_changes : MutableMapping [str | None , list ],
162+ change_type_map : Mapping [str , str ] | None = None ,
172163) -> None :
173164 message : dict [str , Any ] = {
174165 "sha1" : commit .rev ,
@@ -178,13 +169,16 @@ def process_commit_message(
178169 ** parsed .groupdict (),
179170 }
180171
181- if processed := hook (message , commit ) if hook else message :
182- messages = [processed ] if isinstance (processed , dict ) else processed
183- for msg in messages :
184- change_type = msg .pop ("change_type" , None )
185- if change_type_map :
186- change_type = change_type_map .get (change_type , change_type )
187- changes [change_type ].append (msg )
172+ processed_msg = hook (message , commit ) if hook else message
173+ if not processed_msg :
174+ return
175+
176+ messages = [processed_msg ] if isinstance (processed_msg , dict ) else processed_msg
177+ for msg in messages :
178+ change_type = msg .pop ("change_type" , None )
179+ if change_type_map :
180+ change_type = change_type_map .get (change_type , change_type )
181+ ref_changes [change_type ].append (msg )
188182
189183
190184def generate_ordered_changelog_tree (
@@ -251,6 +245,7 @@ def incremental_build(
251245 unreleased_start = metadata .unreleased_start
252246 unreleased_end = metadata .unreleased_end
253247 latest_version_position = metadata .latest_version_position
248+
254249 skip = False
255250 output_lines : list [str ] = []
256251 for index , line in enumerate (lines ):
@@ -260,9 +255,7 @@ def incremental_build(
260255 skip = False
261256 if (
262257 latest_version_position is None
263- or isinstance (latest_version_position , int )
264- and isinstance (unreleased_end , int )
265- and latest_version_position > unreleased_end
258+ or latest_version_position > unreleased_end
266259 ):
267260 continue
268261
@@ -271,13 +264,15 @@ def incremental_build(
271264
272265 if index == latest_version_position :
273266 output_lines .extend ([new_content , "\n " ])
274-
275267 output_lines .append (line )
276- if not isinstance (latest_version_position , int ):
277- if output_lines and output_lines [- 1 ].strip ():
278- # Ensure at least one blank line between existing and new content.
279- output_lines .append ("\n " )
280- output_lines .append (new_content )
268+
269+ if latest_version_position is not None :
270+ return output_lines
271+
272+ if output_lines and output_lines [- 1 ].strip ():
273+ # Ensure at least one blank line between existing and new content.
274+ output_lines .append ("\n " )
275+ output_lines .append (new_content )
281276 return output_lines
282277
283278
0 commit comments