@@ -30,7 +30,7 @@ def __init__(self, repo_path: Path, config: Optional[Config] = None) -> None:
30
30
def generate (self ) -> str :
31
31
"""Generate changelog content."""
32
32
try :
33
- # Get commits from repository
33
+ # Get commits from repository using the improved method
34
34
commits = self .git_repo .get_commits (
35
35
since = self .config .since_tag ,
36
36
until = self .config .until_tag ,
@@ -97,61 +97,144 @@ def _group_commits_by_version(
97
97
self , parsed_commits : List [tuple [Commit , ParsedCommit ]]
98
98
) -> List [ChangelogEntry ]:
99
99
"""Group commits by version/tag."""
100
+ # If we have specific since/until tags, handle them differently
101
+ if self .config .since_tag or self .config .until_tag :
102
+ return self ._group_commits_by_tag_range (parsed_commits )
103
+ else :
104
+ return self ._group_commits_by_all_tags (parsed_commits )
105
+
106
+ def _group_commits_by_tag_range (
107
+ self , parsed_commits : List [tuple [Commit , ParsedCommit ]]
108
+ ) -> List [ChangelogEntry ]:
109
+ """Group commits when specific tag range is provided."""
110
+ # For tag ranges, we want to show all commits in that range
111
+ # grouped by the tags that exist within that range
112
+
113
+ # Get all tags
114
+ all_tags = self .git_repo .get_tags ()
115
+
116
+ if not all_tags :
117
+ # No tags, create single "Unreleased" entry
118
+ return self ._create_unreleased_entry (parsed_commits )
119
+
120
+ # Sort tags by date (newest first for display)
121
+ all_tags .sort (key = lambda t : t .date , reverse = True )
122
+
123
+ # If we have both since and until, create a single entry for that range
124
+ if self .config .since_tag and self .config .until_tag :
125
+ version_name = f"{ self .config .since_tag } ..{ self .config .until_tag } "
126
+ entry = ChangelogEntry (version_name , datetime .now ())
127
+
128
+ for commit , parsed_commit in parsed_commits :
129
+ section_name = self .config .get_section_name (parsed_commit .type )
130
+ entry .add_commit (parsed_commit , section_name )
131
+
132
+ return [entry ] if entry .has_changes () else []
133
+
134
+ # Otherwise, group by individual tags in the range
135
+ entries = []
136
+
137
+ # Find relevant tags based on the range
138
+ relevant_tags = []
139
+ for tag in all_tags :
140
+ include_tag = True
141
+
142
+ # Check if tag is within our range
143
+ if self .config .since_tag :
144
+ since_tag = next (
145
+ (t for t in all_tags if t .name == self .config .since_tag ), None
146
+ )
147
+ if since_tag and tag .date <= since_tag .date :
148
+ include_tag = False
149
+
150
+ if self .config .until_tag :
151
+ until_tag = next (
152
+ (t for t in all_tags if t .name == self .config .until_tag ), None
153
+ )
154
+ if until_tag and tag .date > until_tag .date :
155
+ include_tag = False
156
+
157
+ if include_tag :
158
+ relevant_tags .append (tag )
159
+
160
+ # Create entries for relevant tags
161
+ for tag in relevant_tags :
162
+ # For now, include all commits for each tag in range
163
+ # This is a simplified approach - in practice you might want
164
+ # to be more precise about which commits belong to which tag
165
+ if parsed_commits :
166
+ entry = self ._create_version_entry (tag .name , tag .date , parsed_commits )
167
+ if entry .has_changes ():
168
+ entries .append (entry )
169
+ break # Only create one entry to avoid duplication
170
+
171
+ # If no relevant tags found, create unreleased entry
172
+ if not entries and parsed_commits :
173
+ entries = self ._create_unreleased_entry (parsed_commits )
174
+
175
+ return entries
176
+
177
+ def _group_commits_by_all_tags (
178
+ self , parsed_commits : List [tuple [Commit , ParsedCommit ]]
179
+ ) -> List [ChangelogEntry ]:
180
+ """Group commits by all tags when no specific range is provided."""
100
181
# Get tags from repository
101
182
tags = self .git_repo .get_tags ()
102
183
103
184
if not tags :
104
185
# No tags, create single "Unreleased" entry
105
186
return self ._create_unreleased_entry (parsed_commits )
106
187
107
- # Sort tags by date (newest first)
108
- tags .sort (key = lambda t : t .date , reverse = True )
188
+ # Sort tags by date (oldest first)
189
+ tags .sort (key = lambda t : t .date )
109
190
110
191
entries = []
111
- remaining_commits = parsed_commits . copy ()
192
+ processed_commits = set ()
112
193
113
- # Process each tag
194
+ # Process each tag (oldest to newest)
114
195
for i , tag in enumerate (tags ):
115
- # Get commits for this version
196
+ # Get the date range for this tag
116
197
if i == 0 :
117
- # Latest tag - commits up to and including the tag
118
- # Include commits that are at or before the tag date
119
- version_commits = [
120
- (commit , parsed )
121
- for commit , parsed in remaining_commits
122
- if commit .date <= tag .date
123
- ]
198
+ # First tag - all commits up to this tag
199
+ start_date = datetime .min
124
200
else :
125
- # Previous tag - commits between previous tag and this tag
126
- next_tag = tags [i - 1 ]
127
- version_commits = [
128
- (commit , parsed )
129
- for commit , parsed in remaining_commits
130
- if next_tag .date < commit .date <= tag .date
131
- ]
201
+ # Subsequent tags - commits after previous tag
202
+ start_date = tags [i - 1 ].date
203
+
204
+ end_date = tag .date
205
+
206
+ # Get commits for this version
207
+ version_commits = [
208
+ (commit , parsed )
209
+ for commit , parsed in parsed_commits
210
+ if start_date < commit .date <= end_date
211
+ and (commit .hash , parsed .type ) not in processed_commits
212
+ ]
132
213
133
214
if version_commits :
134
215
entry = self ._create_version_entry (tag .name , tag .date , version_commits )
135
216
entries .append (entry )
136
217
137
- # Remove processed commits
138
- for commit_tuple in version_commits :
139
- if commit_tuple in remaining_commits :
140
- remaining_commits .remove (commit_tuple )
218
+ # Mark commits as processed
219
+ for commit , parsed in version_commits :
220
+ processed_commits .add ((commit .hash , parsed .type ))
141
221
142
- # Add unreleased commits if any (commits newer than the latest tag)
143
- if remaining_commits :
144
- # Only include commits that are actually newer than the latest tag
145
- latest_tag_date = tags [0 ].date if tags else datetime .min
146
- truly_unreleased = [
222
+ # Add unreleased commits (commits newer than the latest tag)
223
+ if tags :
224
+ latest_tag_date = max (tag .date for tag in tags )
225
+ unreleased_commits = [
147
226
(commit , parsed )
148
- for commit , parsed in remaining_commits
227
+ for commit , parsed in parsed_commits
149
228
if commit .date > latest_tag_date
229
+ and (commit .hash , parsed .type ) not in processed_commits
150
230
]
151
231
152
- if truly_unreleased :
153
- unreleased_entry = self ._create_unreleased_entry (truly_unreleased )
154
- entries .insert (0 , unreleased_entry [0 ]) # Add at the beginning
232
+ if unreleased_commits :
233
+ unreleased_entry = self ._create_unreleased_entry (unreleased_commits )
234
+ entries = unreleased_entry + entries
235
+
236
+ # Sort entries by version (newest first for display)
237
+ entries .sort (key = lambda e : e .date , reverse = True )
155
238
156
239
return entries
157
240
0 commit comments