2020"""
2121
2222import logging
23+ import sys
2324
2425from typing import Optional
26+ import semver
27+
2528from 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
2734from release_notes_generator .model .custom_chapters import CustomChapters
2835from release_notes_generator .model .record import Record
29- from release_notes_generator .builder import ReleaseNotesBuilder
3036from release_notes_generator .record .record_factory import RecordFactory
31- from release_notes_generator .action_inputs import ActionInputs
3237from release_notes_generator .utils .constants import ISSUE_STATE_ALL
33-
3438from release_notes_generator .utils .decorators import safe_call_decorator
35- from release_notes_generator .utils .utils import get_change_url
3639from release_notes_generator .utils .github_rate_limiter import GithubRateLimiter
40+ from release_notes_generator .utils .utils import get_change_url
3741
3842logger = 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
0 commit comments