144 lines
4.8 KiB
Python
Executable File
144 lines
4.8 KiB
Python
Executable File
#!/usr/bin/python3
|
|
#
|
|
# This script parses a git log from stdin, which should be given with:
|
|
# $ git log [<options>] -z --format="- %s (%an)%n%b" [<range>] [[--] <path>...] | ...
|
|
# And then outputs to stdout a trimmed changelog for use with rpm packaging
|
|
#
|
|
# Author: Herton R. Krzesinski <herton@redhat.com>
|
|
# Copyright (C) 2021 Red Hat, Inc.
|
|
#
|
|
# This software may be freely redistributed under the terms of the GNU
|
|
# General Public License (GPL).
|
|
|
|
"""Parses a git log from stdin, and output a log entry for an rpm."""
|
|
|
|
import re
|
|
import sys
|
|
|
|
def find_ticket_in_line(line, prefix, tkt_re, tkt_groups):
|
|
"""Return ticket referenced in the given line"""
|
|
_tkts = set()
|
|
if not line.startswith(f"{prefix}: "):
|
|
return _tkts
|
|
for match in tkt_re.finditer(line[len(f"{prefix}:"):]):
|
|
for group in tkt_groups:
|
|
if match.group(group):
|
|
tid = match.group(group).strip()
|
|
_tkts.add(tid)
|
|
return _tkts
|
|
|
|
def find_bz_in_line(line, prefix):
|
|
bznum_re = re.compile(r'(?P<bug_ids> \d{4,8})|'
|
|
r'( http(s)?://bugzilla\.redhat\.com/(show_bug\.cgi\?id=)?(?P<url_bugs>\d{4,8}))')
|
|
return find_ticket_in_line(line, prefix, bznum_re, [ 'bug_ids', 'url_bugs' ])
|
|
|
|
def find_ji_in_line(line, prefix):
|
|
ji_re = re.compile(r' https://issues\.redhat\.com/(?:browse|projects/RHEL/issues)/(?P<jira_id>RHEL-\d{1,8})\s*$')
|
|
return find_ticket_in_line(line, prefix, ji_re, [ 'jira_id' ])
|
|
|
|
def find_cve_in_line(line):
|
|
"""Return cve number from properly formated CVE: line."""
|
|
# CVEs must begin with 'CVE: '
|
|
cve_set = set()
|
|
if not line.startswith("CVE: "):
|
|
return cve_set
|
|
_cves = line[len("CVE: "):].split()
|
|
pattern = "(?P<cve>CVE-[0-9]+-[0-9]+)"
|
|
cve_re = re.compile(pattern)
|
|
for cve_item in _cves:
|
|
cve = cve_re.match(cve_item)
|
|
if cve:
|
|
cve_set.add(cve.group('cve'))
|
|
return cve_set
|
|
|
|
|
|
def parse_commit(commit):
|
|
"""Extract metadata from a commit log message."""
|
|
lines = commit.split('\n')
|
|
|
|
# Escape '%' as it will be used inside the rpm spec changelog
|
|
log_entry = lines[0].replace("%","%%")
|
|
|
|
cve_set = set()
|
|
bug_set = set()
|
|
zbug_set = set()
|
|
jira_set = set()
|
|
zjira_set = set()
|
|
for line in lines[1:]:
|
|
# Metadata in git notes has priority over commit log
|
|
# If we found any BZ/ZBZ/JIRA/ZJIRA/CVE in git notes,
|
|
# we ignore the commit log
|
|
if line == "^^^NOTES-END^^^":
|
|
if bug_set or zbug_set or jira_set or zjira_set or cve_set:
|
|
break
|
|
|
|
# Process Bugzilla and ZStream Bugzilla entries
|
|
bug_set.update(find_bz_in_line(line, 'Bugzilla'))
|
|
zbug_set.update(find_bz_in_line(line, 'Z-Bugzilla'))
|
|
|
|
# Grab CVE tags if they are present
|
|
cve_set.update(find_cve_in_line(line))
|
|
|
|
# Process Jira issues
|
|
jira_set.update(find_ji_in_line(line, 'JIRA'))
|
|
zjira_set.update(find_ji_in_line(line, 'Z-JIRA'))
|
|
|
|
return (log_entry, cve_set, bug_set, zbug_set, jira_set, zjira_set)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
all_bzs = set()
|
|
all_zbzs = set()
|
|
all_jiras = set()
|
|
all_zjiras = set()
|
|
commits = sys.stdin.read().split('\0')
|
|
for c in commits:
|
|
if not c:
|
|
continue
|
|
log_item, cves, bugs, zbugs, jiras, zjiras = parse_commit(c)
|
|
entry = f"{log_item}"
|
|
if bugs or zbugs or jiras or zjiras:
|
|
entry += " ["
|
|
if zbugs:
|
|
entry += " ".join(sorted(zbugs))
|
|
all_zbzs.update(zbugs)
|
|
if bugs:
|
|
entry += " " if zbugs else ""
|
|
entry += " ".join(sorted(bugs))
|
|
all_bzs.update(bugs)
|
|
if zjiras:
|
|
entry += " " if zbugs or bugs else ""
|
|
entry += " ".join(sorted(zjiras))
|
|
all_zjiras.update(zjiras)
|
|
if jiras:
|
|
entry += " " if zbugs or bugs or zjiras else ""
|
|
entry += " ".join(sorted(jiras))
|
|
all_jiras.update(jiras)
|
|
entry += "]"
|
|
if cves:
|
|
entry += " {" + " ".join(sorted(cves)) + "}"
|
|
print(entry)
|
|
|
|
# If we are doing Z-Stream work, we are addressing Z-Stream tickets
|
|
# and not Y-Stream tickets, so we must make sure to list on Resolves
|
|
# line only the Z-Stream tickets
|
|
resolved_bzs = set()
|
|
resolved_jiras = set()
|
|
if all_zbzs or all_zjiras:
|
|
resolved_bzs = all_zbzs
|
|
resolved_jiras = all_zjiras
|
|
else:
|
|
resolved_bzs = all_bzs
|
|
resolved_jiras = all_jiras
|
|
print("Resolves: ", end="")
|
|
for i, bzid in enumerate(sorted(resolved_bzs)):
|
|
if i:
|
|
print(", ", end="")
|
|
print(f"rhbz#{bzid}", end="")
|
|
for j, jid in enumerate(sorted(resolved_jiras)):
|
|
if j or resolved_bzs:
|
|
print(", ", end="")
|
|
print(f"{jid}", end="")
|
|
print("\n")
|
|
|