Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 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() |