Skip to content

Commit 4d9a1bd

Browse files
committed
Merge branch 'master' into bugfix/85-Wrong-type-used-in-chapters-definition
# Conflicts: # .github/workflows/release_draft.yml # examples/release_draft.yml # release_notes_generator/action_inputs.py # release_notes_generator/utils/pull_reuqest_utils.py # requirements.txt # tests/test_action_inputs.py
2 parents e110fc3 + 303a57a commit 4d9a1bd

File tree

13 files changed

+335
-22
lines changed

13 files changed

+335
-22
lines changed

.github/pull_request_template.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## Release Notes:
2+
> Provide a concise summary of changes for the release notes. Include any new features, bug fixes, or other significant updates. Keep it brief and to the point.
3+
4+
## Checklist:
5+
> Ensure the following tasks are completed before submission.
6+
- [ ] My code follows the project style guidelines.
7+
- [ ] I have performed a self-review of my code.
8+
- [ ] I have commented my code, particularly in hard-to-understand areas.
9+
- [ ] I have added tests that prove my fix is effective or my feature works.
10+
- [ ] New and existing tests pass locally with my changes.
11+
- [ ] I have used a code formatting tool to ensure consistent style.
12+
- [ ] I have run a linter to check for code quality issues.
13+
14+
## Additional Context:
15+
> Add any other context about the pull request here.
16+
17+
---
18+
> **Note:** Remember to link this PR to the related issue by adding `Fixes #issue_number` to the description above.
19+
20+
Fixes **or** Closes #(issue)

.github/workflows/release_draft.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ on:
2121
tag-name:
2222
description: 'Name of git tag to be created, and then draft release created. Syntax: "v[0-9]+.[0-9]+.[0-9]+".'
2323
required: true
24+
from-tag-name:
25+
description: 'Name of the git tag from which to detect changes from. Default value: latest tag. Syntax: "v[0-9]+.[0-9]+.[0-9]+".'
26+
required: false
2427

2528
jobs:
2629
release-draft:
@@ -37,13 +40,24 @@ jobs:
3740

3841
- name: Check format of received tag
3942
id: check-version-tag
40-
uses: AbsaOSS/version-tag-check@v0.2.0
43+
uses: AbsaOSS/version-tag-check@v0.3.0
4144
env:
4245
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4346
with:
4447
github-repository: ${{ github.repository }}
4548
version-tag: ${{ github.event.inputs.tag-name }}
4649

50+
- name: Check format of received from tag
51+
if: ${{ github.event.inputs.from-tag-name }}
52+
id: check-version-from-tag
53+
uses: AbsaOSS/version-tag-check@v0.3.0
54+
env:
55+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56+
with:
57+
github-repository: ${{ github.repository }}
58+
version-tag: ${{ github.event.inputs.from-tag-name }}
59+
should-exist: true
60+
4761
- name: Generate Release Notes
4862
id: generate_release_notes
4963
uses: AbsaOSS/generate-release-notes@master

action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ inputs:
2828
description: 'Allow duplicity of issue lines in chapters. Scopes: custom, service, both, none.'
2929
required: false
3030
default: 'both'
31+
from-tag-name:
32+
description: 'The tag name of the previous release to use as a start reference point for the current release notes.'
33+
required: false
34+
default: ''
3135
duplicity-icon:
3236
description: 'Icon to be used for duplicity warning. Icon is placed before the record line.'
3337
required: false
@@ -116,6 +120,7 @@ runs:
116120
INPUT_GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}
117121
INPUT_TAG_NAME: ${{ inputs.tag-name }}
118122
INPUT_CHAPTERS: ${{ inputs.chapters }}
123+
INPUT_FROM_TAG_NAME: ${{ inputs.from-tag-name }}
119124
INPUT_DUPLICITY_SCOPE: ${{ inputs.duplicity-scope }}
120125
INPUT_DUPLICITY_ICON: ${{ inputs.duplicity-icon }}
121126
INPUT_WARNINGS: ${{ inputs.warnings }}

examples/release_draft.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
tag-name:
66
description: 'Name of git tag to be created, and then draft release created. Syntax: "v[0-9]+.[0-9]+.[0-9]+".'
77
required: true
8+
from-tag-name:
9+
description: 'Name of the git tag from which to detect changes from. Default value: latest tag. Syntax: "v[0-9]+.[0-9]+.[0-9]+".'
10+
required: false
811

912
jobs:
1013
release-draft:
@@ -23,14 +26,24 @@ jobs:
2326

2427
- name: Check format of received tag
2528
id: check-version-tag
26-
uses: AbsaOSS/version-tag-check@v0.1.0
29+
uses: AbsaOSS/version-tag-check@v0.3.0
2730
env:
2831
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2932
with:
3033
github-repository: ${{ github.repository }}
31-
branch: 'master'
3234
version-tag: ${{ github.event.inputs.tag-name }}
3335

36+
- name: Check format of received from tag
37+
if: ${{ github.event.inputs.from-tag-name }}
38+
id: check-version-from-tag
39+
uses: AbsaOSS/version-tag-check@v0.3.0
40+
env:
41+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
with:
43+
github-repository: ${{ github.repository }}
44+
version-tag: ${{ github.event.inputs.from-tag-name }}
45+
should-exist: true
46+
3447
- name: Generate Release Notes
3548
id: generate_release_notes
3649
uses: AbsaOSS/generate-release-notes@v0.3.0

release_notes_generator/action_inputs.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
RELEASE_NOTES_TITLE,
4747
RELEASE_NOTE_TITLE_DEFAULT,
4848
SUPPORTED_ROW_FORMAT_KEYS,
49+
FROM_TAG_NAME,
4950
)
5051
from release_notes_generator.utils.enums import DuplicityScopeEnum
5152
from release_notes_generator.utils.gh_action import get_action_input
@@ -84,6 +85,21 @@ def get_tag_name() -> str:
8485
"""
8586
return get_action_input(TAG_NAME)
8687

88+
@staticmethod
89+
def get_from_tag_name() -> str:
90+
"""
91+
Get the from-tag name from the action inputs.
92+
"""
93+
return get_action_input(FROM_TAG_NAME, default="")
94+
95+
@staticmethod
96+
def is_from_tag_name_defined() -> bool:
97+
"""
98+
Check if the from-tag name is defined in the action inputs.
99+
"""
100+
value = ActionInputs.get_from_tag_name()
101+
return value.strip() != ""
102+
87103
@staticmethod
88104
def get_chapters() -> Optional[list[dict[str, str]]]:
89105
"""
@@ -136,8 +152,9 @@ def get_skip_release_notes_labels() -> str:
136152
"""
137153
Get the skip release notes label from the action inputs.
138154
"""
139-
user_choice = [item.strip() for item in get_action_input(SKIP_RELEASE_NOTES_LABELS, "").split(",")]
140-
if len(user_choice) > 0:
155+
user_input = get_action_input(SKIP_RELEASE_NOTES_LABELS, "")
156+
user_choice = [item.strip() for item in user_input.split(",")] if user_input else []
157+
if user_choice:
141158
return user_choice
142159
return ["skip-release-notes"]
143160

@@ -238,6 +255,10 @@ def validate_inputs() -> None:
238255
if not isinstance(tag_name, str) or not tag_name.strip():
239256
errors.append("Tag name must be a non-empty string.")
240257

258+
from_tag_name = ActionInputs.get_from_tag_name()
259+
if not isinstance(from_tag_name, str):
260+
errors.append("From tag name must be a string.")
261+
241262
chapters = ActionInputs.get_chapters()
242263
if chapters is None:
243264
errors.append("Chapters must be a valid yaml array.")

release_notes_generator/builder.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
logger = logging.getLogger(__name__)
3030

3131

32-
# TODO - reduce to function only after implementing the features. Will be supported more build ways?
3332
# pylint: disable=too-few-public-methods
3433
class ReleaseNotesBuilder:
3534
"""

release_notes_generator/generator.py

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,24 @@
2020
"""
2121

2222
import logging
23+
import sys
2324

2425
from typing import Optional
26+
import semver
27+
2528
from github import Github
29+
from github.GitRelease import GitRelease
30+
from github.Repository import Repository
2631

32+
from release_notes_generator.action_inputs import ActionInputs
33+
from release_notes_generator.builder import ReleaseNotesBuilder
2734
from release_notes_generator.model.custom_chapters import CustomChapters
2835
from release_notes_generator.model.record import Record
29-
from release_notes_generator.builder import ReleaseNotesBuilder
3036
from release_notes_generator.record.record_factory import RecordFactory
31-
from release_notes_generator.action_inputs import ActionInputs
3237
from release_notes_generator.utils.constants import ISSUE_STATE_ALL
33-
3438
from release_notes_generator.utils.decorators import safe_call_decorator
35-
from release_notes_generator.utils.utils import get_change_url
3639
from release_notes_generator.utils.github_rate_limiter import GithubRateLimiter
40+
from release_notes_generator.utils.utils import get_change_url
3741

3842
logger = logging.getLogger(__name__)
3943

@@ -72,15 +76,13 @@ def generate(self) -> Optional[str]:
7276
7377
@return: The generated release notes as a string, or None if the repository could not be found.
7478
"""
79+
# get the repository
7580
repo = self._safe_call(self.github_instance.get_repo)(ActionInputs.get_github_repository())
7681
if repo is None:
7782
return None
7883

79-
rls = self._safe_call(repo.get_latest_release)()
80-
if rls is None:
81-
logger.info("Latest release not found for %s. 1st release for repository!", repo.full_name)
82-
else:
83-
logger.debug("RLS created_at: %s, published_at: %s", rls.created_at, rls.published_at)
84+
# get the latest release
85+
rls: GitRelease = self.get_latest_release(repo)
8486

8587
# default is repository creation date if no releases OR created_at of latest release
8688
since = rls.created_at if rls else repo.created_at
@@ -97,12 +99,12 @@ def generate(self) -> Optional[str]:
9799

98100
# filter out closed Issues before the date
99101
issues = list(
100-
filter(lambda issue: issue.closed_at is not None and issue.closed_at > since, list(issues_all))
102+
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= since, list(issues_all))
101103
)
102104
logger.debug("Count of issues reduced from %d to %d", len(list(issues_all)), len(issues))
103105

104106
# filter out merged PRs and commits before the date
105-
pulls = list(filter(lambda pull: pull.merged_at is not None and pull.merged_at > since, list(pulls_all)))
107+
pulls = list(filter(lambda pull: pull.merged_at is not None and pull.merged_at >= since, list(pulls_all)))
106108
logger.debug("Count of pulls reduced from %d to %d", len(list(pulls_all)), len(pulls))
107109

108110
commits = list(filter(lambda commit: commit.commit.author.date > since, list(commits_all)))
@@ -125,3 +127,59 @@ def generate(self) -> Optional[str]:
125127
)
126128

127129
return release_notes_builder.build()
130+
131+
def get_latest_release(self, repo: Repository) -> Optional[GitRelease]:
132+
"""
133+
Get the latest release of the repository.
134+
135+
@param repo: The repository to get the latest release from.
136+
@return: The latest release of the repository, or None if no releases are found.
137+
"""
138+
# check if from-tag name is defined
139+
if ActionInputs.is_from_tag_name_defined():
140+
logger.info("Getting latest release by from-tag name %s", ActionInputs.get_tag_name())
141+
rls: GitRelease = self._safe_call(repo.get_release)(ActionInputs.get_from_tag_name())
142+
143+
if rls is None:
144+
logger.info("Latest release not found for received tag %s. Ending!", ActionInputs.get_from_tag_name())
145+
sys.exit(1)
146+
147+
else:
148+
logger.info("Getting latest release by semantic ordering (could not be the last one by time).")
149+
gh_releases: list = list(self._safe_call(repo.get_releases)())
150+
rls: GitRelease = self.__get_latest_semantic_release(gh_releases)
151+
152+
if rls is None:
153+
logger.info("Latest release not found for %s. 1st release for repository!", repo.full_name)
154+
155+
if rls is not None:
156+
logger.debug(
157+
"Latest release with tag:'%s' created_at: %s, published_at: %s",
158+
rls.tag_name,
159+
rls.created_at,
160+
rls.published_at,
161+
)
162+
163+
return rls
164+
165+
def __get_latest_semantic_release(self, releases) -> Optional[GitRelease]:
166+
published_releases = [release for release in releases if not release.draft and not release.prerelease]
167+
latest_version: Optional[semver.Version] = None
168+
rls: Optional[GitRelease] = None
169+
170+
for release in published_releases:
171+
try:
172+
version_str = release.tag_name.lstrip("v")
173+
current_version: Optional[semver.Version] = semver.VersionInfo.parse(version_str)
174+
except ValueError:
175+
logger.debug("Skipping invalid value of version tag: %s", release.tag_name)
176+
continue
177+
except TypeError:
178+
logger.debug("Skipping invalid type of version tag: %s", release.tag_name)
179+
continue
180+
181+
if latest_version is None or current_version > latest_version:
182+
latest_version = current_version
183+
rls = release
184+
185+
return rls

release_notes_generator/record/record_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def create_record_for_issue(r: Repository, i: Issue) -> None:
6868
@return: None
6969
"""
7070
# check for skip labels presence and skip when detected
71-
issue_labels = [label.name for label in issue.labels]
71+
issue_labels = [label.name for label in i.labels]
7272
skip_record = any(item in issue_labels for item in ActionInputs.get_skip_release_notes_labels())
7373
records[i.number] = Record(r, i, skip=skip_record)
7474

release_notes_generator/utils/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
GITHUB_REPOSITORY = "GITHUB_REPOSITORY"
2323
GITHUB_TOKEN = "github-token"
2424
TAG_NAME = "tag-name"
25+
FROM_TAG_NAME = "from-tag-name"
2526
CHAPTERS = "chapters"
2627
DUPLICITY_SCOPE = "duplicity-scope"
2728
DUPLICITY_ICON = "duplicity-icon"

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ pylint==3.2.6
66
requests==2.31.0
77
black==24.8.0
88
PyYAML==6.0.2
9+
semver==3.0.2

0 commit comments

Comments
 (0)