Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 96 additions & 67 deletions gitlab_release_notes/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,115 +4,144 @@
import sys
from .version import __version__

def generate_release_notes(project_id, endstr = ' <br>', since=None, quiet=False, target_branch=None, **config):
def generate_release_notes(project_id, endstr=' <br>', since=None, target_branch=None, **config):
"""
Generate the release notes of a gitlab project from the last release
Generate the release notes of a gitlab project.

Parameters
----------
project_id: int
ID of the project
endstr: str
String to use for line endings (e.g., ' <br>' for HTML, '\n' for plain text)
since: Optional[str]
A date string (YYYY-MM-DDTHH:MM:SSZ) or a tag/reference.
If provided, release notes will be generated for MRs merged after this point.
Overrides fetching MRs since the last release.
target_branch: Optional[str]
The target branch to filter merge requests by.
config: dict
Additional keyword arguments for the gitlab.Gitlab client, such as:
url: Optional[str] = None,
private_token: Optional[str] = None,
oauth_token: Optional[str] = None,
job_token: Optional[str] = None,
ssl_verify: Union[bool, str] = True,
http_username: Optional[str] = None,
http_password: Optional[str] = None,
timeout: Optional[float] = None,
api_version: str = '4',
session: Optional[requests.sessions.Session] = None,
per_page: Optional[int] = None,
pagination: Optional[str] = None,
order_by: Optional[str] = None,
user_agent: str = 'python-gitlab/3.1.0',
retry_transient_errors: bool = False,
# ... (other gitlab.Gitlab config options)
"""

gl = gitlab.Gitlab(**config)
project = gl.projects.get(project_id)

if not project.mergerequests.list(get_all=False,state='merged', target_branch=target_branch):
raise ValueError(f"There is no merged merge request for project {project_id} {project.name}")

log = ""
# Initial check for any merged MRs (can be refined if needed)
# This check doesn't use target_branch yet, it's a general check.
# Consider if this check should also incorporate target_branch if provided.
# For now, keeping it as is.
initial_mr_check_params = {'state': 'merged', 'per_page': 1}
if target_branch: # Optionally make the initial check more specific
initial_mr_check_params['target_branch'] = target_branch

if not project.mergerequests.list(get_all=False, **initial_mr_check_params):
error_message = f"There are no merged merge requests for project {project_id} ({project.name})"
if target_branch:
error_message += f" on branch '{target_branch}'"
raise ValueError(error_message)

if since:
log_pending = f"Changelog of {project.name} since {since}:{endstr}"
last_date = since
elif not project.releases.list(get_all=False):
log_pending = f"Changelog of {project.name}:{endstr}"
last_date = '0000-01-01T00:00:00Z'
log = f"Changelog of {project.name}"
if target_branch:
log += f" for branch '{target_branch}'"
log += f" since {since}:{endstr}"
last_date = since # Assuming 'since' is a date string 'YYYY-MM-DDTHH:MM:SSZ'
# If 'since' can be a tag, you'd need to resolve its date
else:
last_release = project.releases.list(get_all=False)[0]
log_pending = f"Changelog since release {last_release.name} of {project.name}:{endstr}"
last_date = last_release.released_at
releases = project.releases.list()
if not releases:
log = f"Changelog of {project.name}"
if target_branch:
log += f" for branch '{target_branch}'"
log += f":{endstr}"
last_date = '0000-01-01T00:00:00Z'
else:
last_release = releases[0]
log = f"Changelog since release {last_release.name} of {project.name}"
if target_branch:
log += f" for branch '{target_branch}'"
log += f":{endstr}"
last_date = last_release.released_at

page = 1
list_mrs = project.mergerequests.list(state='merged',
get_all=False,
order_by='updated_at',
updated_after=last_date,
target_branch=target_branch,
page=page)
if not list_mrs:
if not quiet:
log += log_pending
log += f"There is no merged merge request after {last_date}{endstr}"
return log

log += log_pending
list_mrs_params = {
'state': 'merged',
'order_by': 'updated_at',
'updated_after': last_date,
# 'scope': 'all' # Ensure we get MRs the user has access to, default is 'created_by_me' or 'assigned_to_me'
# The python-gitlab default for list() is all MRs the user can view.
}
if target_branch:
list_mrs_params['target_branch'] = target_branch

# Fetch the first page
list_mrs = project.mergerequests.list(page=page, get_all=False, **list_mrs_params)

found_mrs = False
while list_mrs:
found_mrs = True
for mr in list_mrs:
line = f" * {mr.title} (@{mr.author['username']}){endstr}"
log += line

page += 1
list_mrs = project.mergerequests.list(state='merged',
get_all=False,
order_by='updated_at',
updated_after=last_date,
target_branch=target_branch,
page=page
)
list_mrs = project.mergerequests.list(page=page, get_all=False, **list_mrs_params)

if not found_mrs:
no_mrs_message = "There is no new merged merge request"
if target_branch:
no_mrs_message += f" for branch '{target_branch}'"
if since:
no_mrs_message += f" since {since}."
else:
no_mrs_message += f" after {last_date}."
log += no_mrs_message
return log

return log


def main():
import argparse
parser = argparse.ArgumentParser(os.path.basename(sys.argv[0]),
description="Generate release notes for a gitlab repository \
based on merge requests titles since last release")
parser = argparse.ArgumentParser("Generate release notes for a gitlab repository \
based on merge requests titles since last release")

# Required
parser.add_argument("project_id", type=int)
# Optional
parser.add_argument("--url", default="https://gitlab.com", required=False)
parser.add_argument("--private_token", type=str, required=False, default=None)
parser.add_argument('--version', action='version', version=__version__)
parser.add_argument('--html', action='store_true')
parser.add_argument('--since', type=datetime.date.fromisoformat, required=False, default=None)
parser.add_argument('--target_branch', type=str, required=False, default=None)
parser.add_argument('--quiet', action='store_true')
parser.add_argument("--url", default="https://gitlab.com", required=False, help="GitLab base URL, e.g., https://gitlab.com")
parser.add_argument("--private_token", type=str, required=False, default=None, help="GitLab private token")
parser.add_argument('--version', action='version', version=__version__, help="Show the version and exit")
parser.add_argument('--html', action='store_true', help="Generate HTML output")
# Add CLI arguments for since and target_branch if you want to use them from CLI
parser.add_argument("--since", type=str, required=False, default=None,
help="Generate notes since this date (YYYY-MM-DDTHH:MM:SSZ) or tag.")
parser.add_argument("--target_branch", type=str, required=False, default=None,
help="Filter merge requests by target branch.")


args = parser.parse_args()

if args.html:
endstr = ' <br>'
else:
endstr = '\n'
notes = generate_release_notes(args.project_id,
url=args.url,
endstr=endstr,
since=args.since,
target_branch=args.target_branch,
quiet=args.quiet,
private_token=args.private_token,
)
if notes:
print(notes)

# Pass since and target_branch to generate_release_notes
notes = generate_release_notes(
args.project_id,
url=args.url,
endstr=endstr,
private_token=args.private_token,
since=args.since,
target_branch=args.target_branch
)
print(notes)


if __name__ == "__main__":
main()