Skip to content

Commit c92bab3

Browse files
committed
fix(core): Correct tag range and commit grouping logic
This commit resolves the issue where failed to correctly generate changelogs for specified tag ranges (, ), often only displaying changes for the latest tag. The method has been enhanced to accurately fetch commits using Git's native revision range syntax. The 's internal grouping logic () has been refined to properly assign commits to their respective versions within the requested range. This ensures that all relevant commits are included when filtering by tags, providing accurate and complete changelogs for specific release cycles. docs: Move changelog.yml to project root build: Bump version to v0.1.2
1 parent 7d9a195 commit c92bab3

File tree

4 files changed

+152
-34
lines changed

4 files changed

+152
-34
lines changed
File renamed without changes.

changelog_maestro/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Git Changelog Maestro - Generate elegant changelogs from Git commit history."""
22

3-
__version__ = "0.1.1"
3+
__version__ = "0.1.2"
44
__author__ = "petherldev"
55
__email__ = "petherl@protonmail.com"
66

changelog_maestro/core/generator.py

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, repo_path: Path, config: Optional[Config] = None) -> None:
3030
def generate(self) -> str:
3131
"""Generate changelog content."""
3232
try:
33-
# Get commits from repository
33+
# Get commits from repository using the improved method
3434
commits = self.git_repo.get_commits(
3535
since=self.config.since_tag,
3636
until=self.config.until_tag,
@@ -97,61 +97,144 @@ def _group_commits_by_version(
9797
self, parsed_commits: List[tuple[Commit, ParsedCommit]]
9898
) -> List[ChangelogEntry]:
9999
"""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."""
100181
# Get tags from repository
101182
tags = self.git_repo.get_tags()
102183

103184
if not tags:
104185
# No tags, create single "Unreleased" entry
105186
return self._create_unreleased_entry(parsed_commits)
106187

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)
109190

110191
entries = []
111-
remaining_commits = parsed_commits.copy()
192+
processed_commits = set()
112193

113-
# Process each tag
194+
# Process each tag (oldest to newest)
114195
for i, tag in enumerate(tags):
115-
# Get commits for this version
196+
# Get the date range for this tag
116197
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
124200
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+
]
132213

133214
if version_commits:
134215
entry = self._create_version_entry(tag.name, tag.date, version_commits)
135216
entries.append(entry)
136217

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))
141221

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 = [
147226
(commit, parsed)
148-
for commit, parsed in remaining_commits
227+
for commit, parsed in parsed_commits
149228
if commit.date > latest_tag_date
229+
and (commit.hash, parsed.type) not in processed_commits
150230
]
151231

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)
155238

156239
return entries
157240

changelog_maestro/core/git.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,41 @@ def get_commits(
100100
except git.GitCommandError as e:
101101
raise GitError(f"Failed to get commits: {e}")
102102

103+
def get_commits_in_range(
104+
self,
105+
since: Optional[str] = None,
106+
until: Optional[str] = None,
107+
include_merges: bool = False,
108+
) -> List[Commit]:
109+
"""Get commits in a specific tag range."""
110+
try:
111+
# Build the revision range for git log
112+
if since and until:
113+
# Between two tags
114+
rev_range = f"{since}..{until}"
115+
elif since:
116+
# From tag to HEAD
117+
rev_range = f"{since}..HEAD"
118+
elif until:
119+
# From beginning to tag
120+
rev_range = until
121+
else:
122+
# All commits
123+
rev_range = "HEAD"
124+
125+
# Get commits using the range
126+
git_commits = list(self.repo.iter_commits(rev_range))
127+
commits = [Commit.from_git_commit(gc) for gc in git_commits]
128+
129+
# Filter merge commits if requested
130+
if not include_merges:
131+
commits = [c for c in commits if not c.is_merge]
132+
133+
return commits
134+
135+
except git.GitCommandError as e:
136+
raise GitError(f"Failed to get commits in range: {e}")
137+
103138
def get_tags(self) -> List[Tag]:
104139
"""Get all tags from the repository."""
105140
try:

0 commit comments

Comments
 (0)