You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
6.8 KiB
218 lines
6.8 KiB
#!/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('GITHUB_TOKEN', None) |
|
if not token: |
|
sys.exit("""Github token not set in environment, |
|
set the env. variable GITHUB_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()
|
|
|