scripts: add script for listing closed bugs
Script to be used when creating a release. For regular releases this can
be called this way:
$ list_issues.py -f issues.md -s 2019-09-01
Will list all closed issues since september 1st, 2019 and will create a
markdown file with all issues that can be added as is to the release
notes.
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
diff --git a/CODEOWNERS b/CODEOWNERS
index ff75be8..99a3411 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -326,6 +326,7 @@
/scripts/sanity_chk/expr_parser.py @nashif
/scripts/gen_app_partitions.py @andrewboie
/scripts/dts/ @ulfalizer @galak
+/scripts/release/ @nashif
/arch/x86/gen_gdt.py @andrewboie
/arch/x86/gen_idt.py @andrewboie
/scripts/gen_kobject_list.py @andrewboie
diff --git a/scripts/release/list_issues.py b/scripts/release/list_issues.py
new file mode 100755
index 0000000..df6aa42
--- /dev/null
+++ b/scripts/release/list_issues.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2019 Intel Corporation
+#
+# SPDX-License-Identifier: Apache-2.0
+
+# Lists all closes issues since a given date
+
+import argparse
+import sys
+import os
+import re
+import time
+import threading
+import requests
+
+
+args = None
+
+
+class Spinner:
+ busy = False
+ delay = 0.1
+
+ @staticmethod
+ def spinning_cursor():
+ while 1:
+ for cursor in '|/-\\':
+ yield cursor
+
+ def __init__(self, delay=None):
+ self.spinner_generator = self.spinning_cursor()
+ if delay and float(delay):
+ self.delay = delay
+
+ def spinner_task(self):
+ while self.busy:
+ sys.stdout.write(next(self.spinner_generator))
+ sys.stdout.flush()
+ time.sleep(self.delay)
+ sys.stdout.write('\b')
+ sys.stdout.flush()
+
+ def __enter__(self):
+ self.busy = True
+ threading.Thread(target=self.spinner_task).start()
+
+ def __exit__(self, exception, value, tb):
+ self.busy = False
+ time.sleep(self.delay)
+ if exception is not None:
+ return False
+
+
+class Issues:
+ def __init__(self, org, repo, token):
+ self.repo = repo
+ self.org = org
+ self.issues_url = "https://github.com/%s/%s/issues" % (
+ self.org, self.repo)
+ self.github_url = 'https://api.github.com/repos/%s/%s' % (
+ self.org, self.repo)
+
+ self.api_token = token
+ self.headers = {}
+ self.headers['Authorization'] = 'token %s' % self.api_token
+ self.headers['Accept'] = 'application/vnd.github.golden-comet-preview+json'
+ self.items = []
+
+ def get_pull(self, pull_nr):
+ url = ("%s/pulls/%s" % (self.github_url, pull_nr))
+ response = requests.get("%s" % (url), headers=self.headers)
+ if response.status_code != 200:
+ raise RuntimeError(
+ "Failed to get issue due to unexpected HTTP status code: {}".format(
+ response.status_code)
+ )
+ item = response.json()
+ return item
+
+ def get_issue(self, issue_nr):
+ url = ("%s/issues/%s" % (self.github_url, issue_nr))
+ response = requests.get("%s" % (url), headers=self.headers)
+ if response.status_code != 200:
+ return None
+
+ item = response.json()
+ return item
+
+ def list_issues(self, url):
+ response = requests.get("%s" % (url), headers=self.headers)
+ if response.status_code != 200:
+ raise RuntimeError(
+ "Failed to get issue due to unexpected HTTP status code: {}".format(
+ response.status_code)
+ )
+ self.items = self.items + response.json()
+
+ try:
+ print("Getting more items...")
+ next_issues = response.links["next"]
+ if next_issues:
+ next_url = next_issues['url']
+ self.list_issues(next_url)
+ except KeyError:
+ pass
+
+ def issues_since(self, date, state="closed"):
+ self.list_issues("%s/issues?state=%s&since=%s" %
+ (self.github_url, state, date))
+
+ def pull_requests(self, base='v1.14-branch', state='closed'):
+ self.list_issues("%s/pulls?state=%s&base=%s" %
+ (self.github_url, state, base))
+
+
+def parse_args():
+ global args
+
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ parser.add_argument("-o", "--org", default="zephyrproject-rtos",
+ help="Github organisation")
+
+ parser.add_argument("-r", "--repo", default="zephyr",
+ help="Github repository")
+
+ parser.add_argument("-f", "--file", required=True,
+ help="Name of output file.")
+
+ parser.add_argument("-s", "--issues-since",
+ help="""List issues since date where date
+ is in the format 2019-09-01.""")
+
+ parser.add_argument("-b", "--issues-in-pulls",
+ help="List issues in pulls for a given branch")
+
+ parser.add_argument("-c", "--commits-file",
+ help="""File with all commits (git log a..b) to
+ be parsed for fixed bugs.""")
+
+ args = parser.parse_args()
+
+
+def main():
+ parse_args()
+
+ token = os.environ.get('GH_TOKEN', None)
+ if not token:
+ sys.exit("""Github token not set in environment,
+set the env. variable GH_TOKEN please and retry.""")
+
+ i = Issues(args.org, args.repo, token)
+
+ if args.issues_since:
+ i.issues_since(args.issues_since)
+ count = 0
+ with open(args.file, "w") as f:
+ for issue in i.items:
+ if 'pull_request' not in issue:
+ # * :github:`8193` - STM32 config BUILD_OUTPUT_HEX fail
+ f.write("* :github:`{}` - {}\n".format(
+ issue['number'], issue['title']))
+ count = count + 1
+ elif args.issues_in_pulls:
+ i.pull_requests(base=args.issues_in_pulls)
+ count = 0
+
+ bugs = set()
+ backports = []
+ for issue in i.items:
+ if not isinstance(issue['body'], str):
+ continue
+ match = re.findall(r"(Fixes|Closes|Fixed|close):? #([0-9]+)",
+ issue['body'], re.MULTILINE)
+ if match:
+ for mm in match:
+ bugs.add(mm[1])
+ else:
+ match = re.findall(
+ r"Backport #([0-9]+)", issue['body'], re.MULTILINE)
+ if match:
+ backports.append(match[0])
+
+ # follow PRs to their origin (backports)
+ with Spinner():
+ for p in backports:
+ item = i.get_pull(p)
+ match = re.findall(r"(Fixes|Closes|Fixed|close):? #([0-9]+)",
+ item['body'], re.MULTILINE)
+ for mm in match:
+ bugs.add(mm[1])
+
+ # now open commits
+ if args.commits_file:
+ print("Open commits file and parse for fixed bugs...")
+ with open(args.commits_file, "r") as commits:
+ content = commits.read()
+ match = re.findall(r"(Fixes|Closes|Fixed|close):? #([0-9]+)",
+ str(content), re.MULTILINE)
+ for mm in match:
+ bugs.add(mm[1])
+
+ print("Create output file...")
+ with Spinner():
+ with open(args.file, "w") as f:
+ for m in sorted(bugs):
+ item = i.get_issue(m)
+ if item:
+ # * :github:`8193` - STM32 config BUILD_OUTPUT_HEX fail
+ f.write("* :github:`{}` - {}\n".format(
+ item['number'], item['title']))
+
+if __name__ == '__main__':
+ main()