Skip to content

Commit dc0184e

Browse files
Fix chapter tracking to count unique chapters instead of invocations (#241)
* Initial plan * Fix added_into_chapters to track unique chapter IDs Co-authored-by: miroslavpojer <[email protected]> * Address code review feedback - improve docstrings Co-authored-by: miroslavpojer <[email protected]> * Fix integration tests - add added_into_chapters to mock Record Co-authored-by: miroslavpojer <[email protected]> * Refactor chapter tracking methods in Record classes for consistency --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: miroslavpojer <[email protected]> Co-authored-by: miroslavpojer <[email protected]>
1 parent f3b83eb commit dc0184e

File tree

10 files changed

+56
-32
lines changed

10 files changed

+56
-32
lines changed

release_notes_generator/chapters/custom_chapters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def populate(self, records: dict[str, Record]) -> None:
9999
# Quick intersection check
100100
if any(lbl in ch.labels for lbl in record_labels):
101101
if record_id not in ch.rows:
102+
record.add_to_chapter_presence(ch.title)
102103
ch.add_row(record_id, record.to_chapter_row(True))
103104
# Track for backward compatibility (not used for gating anymore)
104105
if record_id not in self.populated_record_numbers_list:

release_notes_generator/chapters/service_chapters.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def populate(self, records: dict[str, Record]) -> None:
135135
logger.debug("Skipping open HierarchyIssueRecord %s (pr_count=%d)", record_id, pr_count)
136136
elif is_issue_like and pr_count > 0:
137137
# Open issue/sub-issue with linked PRs → add to the specific chapter
138+
record.add_to_chapter_presence(MERGED_PRS_LINKED_TO_NOT_CLOSED_ISSUES)
138139
self.chapters[MERGED_PRS_LINKED_TO_NOT_CLOSED_ISSUES].add_row(
139140
record_id, record.to_chapter_row()
140141
)
@@ -145,6 +146,7 @@ def populate(self, records: dict[str, Record]) -> None:
145146
pass
146147
else:
147148
if record_id not in self.used_record_numbers:
149+
record.add_to_chapter_presence(OTHERS_NO_TOPIC)
148150
self.chapters[OTHERS_NO_TOPIC].add_row(record_id, record.to_chapter_row())
149151
self.used_record_numbers.append(record_id)
150152

@@ -164,6 +166,7 @@ def __populate_closed_issues(self, record: IssueRecord, record_id: int | str) ->
164166
pulls_count = record.pull_requests_count()
165167

166168
if pulls_count == 0:
169+
record.add_to_chapter_presence(CLOSED_ISSUES_WITHOUT_PULL_REQUESTS)
167170
self.chapters[CLOSED_ISSUES_WITHOUT_PULL_REQUESTS].add_row(record_id, record.to_chapter_row())
168171
self.used_record_numbers.append(record_id)
169172
populated = True
@@ -174,6 +177,7 @@ def __populate_closed_issues(self, record: IssueRecord, record_id: int | str) ->
174177
if self.__is_row_present(record_id) and not self.duplicity_allowed():
175178
return
176179

180+
record.add_to_chapter_presence(CLOSED_ISSUES_WITHOUT_USER_DEFINED_LABELS)
177181
self.chapters[CLOSED_ISSUES_WITHOUT_USER_DEFINED_LABELS].add_row(record_id, record.to_chapter_row())
178182
self.used_record_numbers.append(record_id)
179183
populated = True
@@ -189,6 +193,7 @@ def __populate_closed_issues(self, record: IssueRecord, record_id: int | str) ->
189193
if record_id in self.used_record_numbers:
190194
return
191195

196+
record.add_to_chapter_presence(OTHERS_NO_TOPIC)
192197
self.chapters[OTHERS_NO_TOPIC].add_row(record_id, record.to_chapter_row())
193198
self.used_record_numbers.append(record_id)
194199

@@ -209,6 +214,7 @@ def __populate_pr(self, record: PullRequestRecord, record_id: int | str) -> None
209214
if self.__is_row_present(record_id) and not self.duplicity_allowed():
210215
return
211216

217+
record.add_to_chapter_presence(MERGED_PRS_WITHOUT_ISSUE_AND_USER_DEFINED_LABELS)
212218
self.chapters[MERGED_PRS_WITHOUT_ISSUE_AND_USER_DEFINED_LABELS].add_row(
213219
record_id, record.to_chapter_row()
214220
)
@@ -219,6 +225,7 @@ def __populate_pr(self, record: PullRequestRecord, record_id: int | str) -> None
219225
if self.__is_row_present(record_id) and not self.duplicity_allowed():
220226
return
221227

228+
record.add_to_chapter_presence(MERGED_PRS_LINKED_TO_NOT_CLOSED_ISSUES)
222229
self.chapters[MERGED_PRS_LINKED_TO_NOT_CLOSED_ISSUES].add_row(record_id, record.to_chapter_row())
223230
self.used_record_numbers.append(record_id)
224231

@@ -229,6 +236,7 @@ def __populate_pr(self, record: PullRequestRecord, record_id: int | str) -> None
229236
if record_id in self.used_record_numbers:
230237
return
231238

239+
record.add_to_chapter_presence(OTHERS_NO_TOPIC)
232240
self.chapters[OTHERS_NO_TOPIC].add_row(record_id, record.to_chapter_row())
233241
self.used_record_numbers.append(record_id)
234242

@@ -241,6 +249,7 @@ def __populate_pr(self, record: PullRequestRecord, record_id: int | str) -> None
241249
if self.__is_row_present(record_id) and not self.duplicity_allowed():
242250
return
243251

252+
record.add_to_chapter_presence(CLOSED_PRS_WITHOUT_ISSUE_AND_USER_DEFINED_LABELS)
244253
self.chapters[CLOSED_PRS_WITHOUT_ISSUE_AND_USER_DEFINED_LABELS].add_row(record_id, record.to_chapter_row())
245254
self.used_record_numbers.append(record_id)
246255

@@ -252,6 +261,7 @@ def __populate_pr(self, record: PullRequestRecord, record_id: int | str) -> None
252261
return
253262

254263
# not record.is_present_in_chapters:
264+
record.add_to_chapter_presence(OTHERS_NO_TOPIC)
255265
self.chapters[OTHERS_NO_TOPIC].add_row(record_id, record.to_chapter_row())
256266
self.used_record_numbers.append(record_id)
257267

@@ -262,6 +272,7 @@ def __populate_direct_commit(self, record: CommitRecord, record_id: int | str) -
262272
@param record: The CommitRecord object representing the direct commit.
263273
@return: None
264274
"""
275+
record.add_to_chapter_presence(DIRECT_COMMITS)
265276
self.chapters[DIRECT_COMMITS].add_row(record_id, record.to_chapter_row())
266277
self.used_record_numbers.append(record_id)
267278

release_notes_generator/model/record/commit_record.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ def commit(self) -> Commit:
6161
# methods - override Record methods
6262

6363
def to_chapter_row(self, add_into_chapters: bool = True) -> str:
64-
if add_into_chapters:
65-
self.added_into_chapters()
66-
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.present_in_chapters() > 1 else ""
64+
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else ""
6765

6866
# collecting values for formatting
6967
commit_message = self._commit.commit.message.replace("\n", " ")

release_notes_generator/model/record/hierarchy_issue_record.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,7 @@ def get_labels(self) -> list[str]:
116116
# methods - override ancestor methods
117117
def to_chapter_row(self, add_into_chapters: bool = True) -> str:
118118
logger.debug("Rendering hierarchy issue row for issue #%s", self.issue.number)
119-
if add_into_chapters:
120-
self.added_into_chapters()
121-
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.present_in_chapters() > 1 else ""
119+
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else ""
122120
format_values: dict[str, Any] = {}
123121

124122
# collect format values

release_notes_generator/model/record/issue_record.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,7 @@ def find_issue(self, issue_number: int) -> Optional["IssueRecord"]:
128128
return None
129129

130130
def to_chapter_row(self, add_into_chapters: bool = True) -> str:
131-
if add_into_chapters:
132-
self.added_into_chapters()
133-
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.present_in_chapters() > 1 else ""
131+
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else ""
134132
format_values: dict[str, Any] = {}
135133

136134
# collect format values

release_notes_generator/model/record/pull_request_record.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,7 @@ def get_labels(self) -> list[str]:
122122
return self.labels
123123

124124
def to_chapter_row(self, add_into_chapters: bool = True) -> str:
125-
if add_into_chapters:
126-
self.added_into_chapters()
127-
128-
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.present_in_chapters() > 1 else ""
125+
row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else ""
129126
format_values: dict[str, Any] = {}
130127

131128
# collecting values for formatting

release_notes_generator/model/record/record.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#
1616

1717
"""
18-
This module contains the BaseChapters class, which is responsible for representing the base chapters.
18+
Defines the abstract base `Record` type used by the release notes generator.
1919
"""
2020

2121
import logging
@@ -37,7 +37,7 @@ class Record(metaclass=ABCMeta):
3737
RELEASE_NOTE_LINE_MARKS: list[str] = ["-", "*", "+"]
3838

3939
def __init__(self, labels: Optional[list[str]] = None, skip: bool = False):
40-
self._present_in_chapters = 0
40+
self._chapters_present_in: set[str] = set()
4141
self._skip = skip
4242
self._is_cross_repo: bool = False
4343
self._is_release_note_detected: Optional[bool] = None
@@ -48,11 +48,12 @@ def __init__(self, labels: Optional[list[str]] = None, skip: bool = False):
4848
@property
4949
def is_present_in_chapters(self) -> bool:
5050
"""
51-
Checks if the record is present in any chapter.
51+
Checks if the record is present in at least one chapter.
52+
5253
Returns:
5354
bool: True if the record is present in at least one chapter, False otherwise.
5455
"""
55-
return self._present_in_chapters > 0
56+
return len(self._chapters_present_in) > 0
5657

5758
@property
5859
def is_cross_repo(self) -> bool:
@@ -190,21 +191,23 @@ def get_rls_notes(self, line_marks: Optional[list[str]] = None) -> str:
190191

191192
# shared methods
192193

193-
def added_into_chapters(self) -> None:
194+
def add_to_chapter_presence(self, chapter_id: str) -> None:
194195
"""
195-
Increments the count of chapters in which the record is present.
196-
Returns: None
196+
Marks this record as present in the given chapter.
197+
198+
Parameters:
199+
chapter_id (str): The unique identifier of the chapter.
197200
"""
198-
# TODO - fix in #191
199-
self._present_in_chapters += 1
201+
self._chapters_present_in.add(chapter_id)
200202

201-
def present_in_chapters(self) -> int:
203+
def chapter_presence_count(self) -> int:
202204
"""
203-
Gets the count of chapters in which the record is present.
205+
Gets the number of unique chapters in which the record is present.
206+
204207
Returns:
205-
int: The count of chapters in which the record is present.
208+
int: The count of unique chapters containing this record.
206209
"""
207-
return self._present_in_chapters
210+
return len(self._chapters_present_in)
208211

209212
def contains_min_one_label(self, labels: list[str]) -> bool:
210213
"""

tests/integration/test_release_notes_snapshot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ def __init__(self, _id: str, _labels: list[str]):
1111
self.labels = _labels
1212
self.skip = False
1313
self.is_present_in_chapters = False
14+
self._chapters = set()
1415

1516
def contains_change_increment(self): # matches code expectation in populate
1617
return True
1718

1819
def to_chapter_row(self, _include_prs: bool): # simplified row rendering
1920
return f"{self.id} row"
2021

22+
def add_to_chapter_presence(self, chapter_id: str): # track chapter additions
23+
self._chapters.add(chapter_id)
24+
2125
return R(record_id, labels)
2226

2327

tests/unit/release_notes_generator/model/test_commit_record.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ def test_to_chapter_row_duplicate_with_icon(monkeypatch, mock_commit):
3434
lambda: "[D]",
3535
)
3636
rec = CommitRecord(mock_commit)
37+
# Simulate adding to first chapter
38+
rec.add_to_chapter_presence("chapter1")
3739
first = rec.to_chapter_row(True)
40+
# Simulate adding to second chapter
41+
rec.add_to_chapter_presence("chapter2")
3842
second = rec.to_chapter_row(True)
3943
assert not first.startswith("[D] ")
4044
assert second.startswith("[D] ")
41-
assert rec.present_in_chapters() == 2
45+
assert rec.chapter_presence_count() == 2
4246

4347
def test_to_chapter_row_with_release_notes_injected(monkeypatch, mock_commit):
4448
# Force contains_release_notes to True and provide fake release notes

tests/unit/release_notes_generator/model/test_record.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def contains_change_increment(self) -> bool:
7070
def test_is_present_in_chapters():
7171
rec = DummyRecord()
7272
assert not rec.is_present_in_chapters
73-
rec.added_into_chapters()
73+
rec.add_to_chapter_presence("chapter1")
7474
assert rec.is_present_in_chapters
7575

7676
def test_skip_property():
@@ -81,10 +81,20 @@ def test_skip_property():
8181

8282
def test_present_in_chapters_count():
8383
rec = DummyRecord()
84-
assert rec.present_in_chapters() == 0
85-
rec.added_into_chapters()
86-
rec.added_into_chapters()
87-
assert rec.present_in_chapters() == 2
84+
assert rec.chapter_presence_count() == 0
85+
rec.add_to_chapter_presence("chapter1")
86+
rec.add_to_chapter_presence("chapter2")
87+
assert rec.chapter_presence_count() == 2
88+
89+
def test_present_in_chapters_unique():
90+
"""Test that adding the same chapter multiple times doesn't increase the count."""
91+
rec = DummyRecord()
92+
assert rec.chapter_presence_count() == 0
93+
rec.add_to_chapter_presence("chapter1")
94+
rec.add_to_chapter_presence("chapter1") # Same chapter again
95+
assert rec.chapter_presence_count() == 1 # Should still be 1
96+
rec.add_to_chapter_presence("chapter2")
97+
assert rec.chapter_presence_count() == 2
8898

8999
def test_contains_min_one_label():
90100
rec = DummyRecord(labels=["bug", "feature"])

0 commit comments

Comments
 (0)