diff --git a/utils/github/__main__.py b/utils/github/__main__.py index 8e0be565468..fb87ece0474 100644 --- a/utils/github/__main__.py +++ b/utils/github/__main__.py @@ -22,12 +22,14 @@ from . import local, query import argparse +import re import sys CHECK_MARK = 'šŸ—ø' CROSS_MARK = 'šŸ—™' WARN_MARK = 'āš ' WAIT_MARK = 'āŠ™' +LABEL_MARK = 'šŸ·' parser = argparse.ArgumentParser(description='Helper for the ClickHouse Release machinery') @@ -45,7 +47,7 @@ args = parser.parse_args() github = query.Query(args.token) repo = local.Local(args.repo, args.remote, github.get_default_branch()) -stables = repo.get_stables()[-args.number:] # [(branch, base, head)] +stables = repo.get_stables()[-args.number:] # [(branch, base)] if not stables: sys.exit('No stable branches found!') else: @@ -55,7 +57,7 @@ else: first_commit = stables[0][1] pull_requests = github.get_pull_requests(first_commit) -good_commits = set(oid[0] for oid in pull_requests.values()) +good_commits = set(pull_request['mergeCommit']['oid'] for pull_request in pull_requests) bad_commits = [] # collect and print them in the end from_commit = repo.get_head_commit() @@ -68,23 +70,23 @@ for i in reversed(range(len(stables))): bad_pull_requests = [] # collect and print if not empty need_backporting = [] -for num, value in pull_requests.items(): +for pull_request in pull_requests: label_found = False - for label in value[1]: + for label in github.get_labels(pull_request): if label[0].startswith('pr-'): label_found = True if label[1] == 'ff0000': - need_backporting.append(num) + need_backporting.append(pull_request) break if not label_found: - bad_pull_requests.append(num) + bad_pull_requests.append(pull_request) if bad_pull_requests: print('\nPull-requests without description label:') - for bad in reversed(sorted(bad_pull_requests)): - print(f'{CROSS_MARK} {bad}') + for bad in reversed(sorted(bad_pull_requests, key = lambda x : x['number'])): + print(f'{CROSS_MARK} {bad["number"]}: {bad["url"]}') # FIXME: compatibility logic, until the direct modification of master is not prohibited. if bad_commits: @@ -95,12 +97,48 @@ if bad_commits: print(f'{CROSS_MARK} {bad}') bad_authors.add(bad.author) - print('\nTell these authors not to push without pull-request and not to merge with rebase:') + print('\nTell these authors not to push without pull-request and not to merge with rebase :)') for author in sorted(bad_authors, key=lambda x : x.name): print(f'{WARN_MARK} {author}') # TODO: check backports. if need_backporting: + re_vlabel = re.compile(r'^v\d+\.\d+$') + re_stable_num = re.compile(r'\d+\.\d+$') + print('\nPull-requests need to be backported:') - for bad in reversed(sorted(need_backporting)): - print(f'{CROSS_MARK} {bad}') + for pull_request in reversed(sorted(need_backporting, key=lambda x: x['number'])): + targets = [] # use common list for consistent order in output + good = set() + + for stable in stables: + if repo.comparator(stable[1]) < repo.comparator(pull_request['mergeCommit']['oid']): + targets.append(stable) + + # FIXME: compatibility logic - check for a manually set label, that indicates status 'backported'. + # FIXME: O(nĀ²) - no need to iterate all labels for every stable + for label in github.get_labels(pull_request): + if re_vlabel.match(label[0]): + stable_num = re_stable_num.search(stable[0].name) + if f'v{stable_num[0]}' == label[0]: + good.add(stable) + + # print pull-request's status + if len(good) == len(targets): + print(f'{CHECK_MARK}', end=' ') + else: + print(f'{CROSS_MARK}', end=' ') + print(f'{pull_request["number"]}', end=':') + for target in targets: + if target in good: + print(f'\t{LABEL_MARK} {target[0]}', end='') + else: + print(f'\t{CROSS_MARK} {target[0]}', end='') + print() + +# print legend +print('\nLegend:') +print(f'{CHECK_MARK} - good') +print(f'{CROSS_MARK} - bad') +print(f'{WARN_MARK} - pay attention!') +print(f'{LABEL_MARK} - backport is detected via label') diff --git a/utils/github/local.py b/utils/github/local.py index 55b05ecf46e..c1713da17ba 100644 --- a/utils/github/local.py +++ b/utils/github/local.py @@ -50,6 +50,6 @@ class Local: elif len(base) > 1: print(f'Branch {stable.path} has more than one base commit. Ignoring.') else: - stables.append((stable, base[0], self._repo.commit(stable))) + stables.append((stable, base[0])) return sorted(stables, key=lambda x : self.comparator(x[1])) diff --git a/utils/github/query.py b/utils/github/query.py index 48123fd743f..7e57efd5158 100644 --- a/utils/github/query.py +++ b/utils/github/query.py @@ -13,7 +13,7 @@ class Query: where "default head" is a head of default repository branch. ''' def get_pull_requests(self, until): - pull_requests = {} # number ā†’ (merge oid, labels) + pull_requests = [] query = Query._FIRST.format(max_page_size=self._max_page_size, pull_request_page_size=self._pull_request_page_size) not_end = True @@ -37,7 +37,7 @@ class Query: if(pull_request['baseRepository']['nameWithOwner'] == 'yandex/ClickHouse' and pull_request['baseRefName'] == default_branch_name and pull_request['mergeCommit']['oid'] == node['oid']): - pull_requests[pull_request['number']] = (node['oid'], self._labels(pull_request)) + pull_requests.append(pull_request) query = Query._NEXT.format(max_page_size=self._max_page_size, pull_request_page_size=self._pull_request_page_size, history_cursor=result['pageInfo']['endCursor']) @@ -47,7 +47,7 @@ class Query: def get_default_branch(self): return self._run(Query._DEFAULT)['data']['repository']['defaultBranchRef']['name'] - def _labels(self, pull_request): + def get_labels(self, pull_request): # TODO: fetch all labels return [(label['node']['name'], label['node']['color']) for label in pull_request['labels']['edges']] @@ -79,6 +79,7 @@ Query._FIRST = ''' nodes {{ ... on PullRequest {{ number + url baseRefName baseRepository {{ nameWithOwner @@ -132,6 +133,7 @@ Query._NEXT = ''' nodes {{ ... on PullRequest {{ number + url baseRefName baseRepository {{ nameWithOwner