| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 | #!/usr/bin/env python3## Copyright (c) 2019 Intel Corporation## SPDX-License-Identifier: Apache-2.0# Lists all closes issues since a given dateimport argparseimport sysimport osimport reimport timeimport threadingimport requestsargs = Noneclass 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 Falseclass 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()
 |