@@ -74,6 +74,23 @@ def get_commit_tag(commit: GitCommit, tags: list[GitTag]) -> GitTag | None:
7474 return next ((tag for tag in tags if tag .rev == commit .rev ), None )
7575
7676
77+ def _get_release_info (
78+ current_tag_name : str ,
79+ current_tag_date : str ,
80+ changes : dict [str | None , list ],
81+ changelog_release_hook : ChangelogReleaseHook | None ,
82+ commit_tag : GitTag | None ,
83+ ) -> dict [str , Any ]:
84+ release = {
85+ "version" : current_tag_name ,
86+ "date" : current_tag_date ,
87+ "changes" : changes ,
88+ }
89+ if changelog_release_hook :
90+ return changelog_release_hook (release , commit_tag )
91+ return release
92+
93+
7794def generate_tree_from_commits (
7895 commits : list [GitCommit ],
7996 tags : list [GitTag ],
@@ -88,47 +105,47 @@ def generate_tree_from_commits(
88105 pat = re .compile (changelog_pattern )
89106 map_pat = re .compile (commit_parser , re .MULTILINE )
90107 body_map_pat = re .compile (commit_parser , re .MULTILINE | re .DOTALL )
91- current_tag : GitTag | None = None
92108 rules = rules or TagRules ()
93109
110+ used_tag_names : set [str ] = set ()
111+ current_tag_name = unreleased_version or "Unreleased"
112+ current_tag_date = (
113+ date .today ().isoformat () if unreleased_version is not None else ""
114+ )
115+
94116 # 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
106-
107- changes : dict = defaultdict (list )
108- used_tags : list = [current_tag ]
117+ current_tag = get_commit_tag (commits [0 ], tags ) if commits else None
118+ if current_tag is not None :
119+ used_tag_names .add (current_tag .name )
120+ if current_tag .name :
121+ current_tag_name = current_tag .name
122+ current_tag_date = current_tag .date
123+
124+ changes : defaultdict [str | None , list ] = defaultdict (list )
125+ commit_tag : GitTag | None = None
109126 for commit in commits :
110127 commit_tag = get_commit_tag (commit , tags )
111128
112129 if (
113130 commit_tag
114- and commit_tag not in used_tags
131+ and commit_tag . name not in used_tag_names
115132 and rules .include_in_changelog (commit_tag )
116133 ):
117- used_tags .append (commit_tag )
118- release = {
119- "version" : current_tag_name ,
120- "date" : current_tag_date ,
121- "changes" : changes ,
122- }
123- if changelog_release_hook :
124- release = changelog_release_hook (release , commit_tag )
125- yield release
134+ used_tag_names .add (commit_tag .name )
135+
136+ yield _get_release_info (
137+ current_tag_name ,
138+ current_tag_date ,
139+ changes ,
140+ changelog_release_hook ,
141+ commit_tag ,
142+ )
143+
126144 current_tag_name = commit_tag .name
127145 current_tag_date = commit_tag .date
128146 changes = defaultdict (list )
129147
130- matches = pat .match (commit .message )
131- if not matches :
148+ if not pat .match (commit .message ):
132149 continue
133150
134151 # Process subject from commit message
@@ -153,14 +170,13 @@ def generate_tree_from_commits(
153170 change_type_map ,
154171 )
155172
156- release = {
157- "version" : current_tag_name ,
158- "date" : current_tag_date ,
159- "changes" : changes ,
160- }
161- if changelog_release_hook :
162- release = changelog_release_hook (release , commit_tag )
163- yield release
173+ yield _get_release_info (
174+ current_tag_name ,
175+ current_tag_date ,
176+ changes ,
177+ changelog_release_hook ,
178+ commit_tag ,
179+ )
164180
165181
166182def process_commit_message (
@@ -178,13 +194,15 @@ def process_commit_message(
178194 ** parsed .groupdict (),
179195 }
180196
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 )
197+ if not (processed := hook (message , commit ) if hook else message ):
198+ return
199+
200+ processed_messages = [processed ] if isinstance (processed , dict ) else processed
201+ for msg in processed_messages :
202+ change_type = msg .pop ("change_type" , None )
203+ if change_type_map :
204+ change_type = change_type_map .get (change_type , change_type )
205+ changes [change_type ].append (msg )
188206
189207
190208def generate_ordered_changelog_tree (
@@ -228,8 +246,7 @@ def render_changelog(
228246 ** kwargs : Any ,
229247) -> str :
230248 jinja_template = get_changelog_template (loader , template )
231- changelog : str = jinja_template .render (tree = tree , ** kwargs )
232- return changelog
249+ return jinja_template .render (tree = tree , ** kwargs )
233250
234251
235252def incremental_build (
@@ -256,13 +273,12 @@ def incremental_build(
256273 for index , line in enumerate (lines ):
257274 if index == unreleased_start :
258275 skip = True
259- elif index == unreleased_end :
276+ continue
277+
278+ if index == unreleased_end :
260279 skip = False
261- if (
262- 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
280+ if latest_version_position is None or (
281+ unreleased_end is not None and latest_version_position > unreleased_end
266282 ):
267283 continue
268284
@@ -271,13 +287,15 @@ def incremental_build(
271287
272288 if index == latest_version_position :
273289 output_lines .extend ([new_content , "\n " ])
274-
275290 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 )
291+
292+ if latest_version_position is not None :
293+ return output_lines
294+
295+ if output_lines and output_lines [- 1 ].strip ():
296+ # Ensure at least one blank line between existing and new content.
297+ output_lines .append ("\n " )
298+ output_lines .append (new_content )
281299 return output_lines
282300
283301
@@ -327,8 +345,7 @@ def get_oldest_and_newest_rev(
327345 if not (newest_tag := rules .find_tag_for (tags , newest )):
328346 raise NoCommitsFoundError ("Could not find a valid revision range." )
329347
330- oldest_tag = None
331- oldest_tag_name = None
348+ oldest_tag_name : str | None = None
332349 if oldest :
333350 if not (oldest_tag := rules .find_tag_for (tags , oldest )):
334351 raise NoCommitsFoundError ("Could not find a valid revision range." )
@@ -340,17 +357,19 @@ def get_oldest_and_newest_rev(
340357 if not tags_range :
341358 raise NoCommitsFoundError ("Could not find a valid revision range." )
342359
343- oldest_rev : str | None = tags_range [- 1 ].name
344360 newest_rev = newest_tag .name
345361
346- # check if it's the first tag created
347- # and it's also being requested as part of the range
348- if oldest_rev == tags [- 1 ].name and oldest_rev == oldest_tag_name :
349- return None , newest_rev
350-
351- # when they are the same, and it's also the
352- # first tag created
353- if oldest_rev == newest_rev :
354- return None , newest_rev
362+ # Return None for oldest_rev if:
363+ # 1. The oldest tag is the last tag in the list and matches the requested oldest tag, or
364+ # 2. The oldest and newest tags are the same
365+ oldest_rev : str | None = (
366+ None
367+ if (
368+ tags_range [- 1 ].name == tags [- 1 ].name
369+ and tags_range [- 1 ].name == oldest_tag_name
370+ or tags_range [- 1 ].name == newest_rev
371+ )
372+ else tags_range [- 1 ].name
373+ )
355374
356375 return oldest_rev , newest_rev
0 commit comments