123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- #!/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()
|