123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- # Copyright (c) 2018 Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- # Generate a GitHub token at https://github.com/settings/tokens
- # Invoke this script using something like:
- # python scripts/milestone_check.py
- import subprocess
- import requests
- import os
- import sys
- ranges = {
- '0.35': 'origin/0.35.0 --not origin/0.34.x',
- '0.35.x': 'origin/0.35.x --not v0.35.0',
- '1.0': 'origin/1.0.x --not origin/0.35.x',
- '1.1': 'v1.1.0 --not origin/1.0.x',
- '1.1.1': 'v1.1.1 --not v1.1.0',
- '1.1.2': 'v1.1.2 --not v1.1.1',
- '1.1.3': 'v1.1.3 --not v1.1.2',
- '1.2': 'origin/1.x --not origin/1.1.x',
- '2.0': 'v2.0.0 --not origin/1.x',
- '2.0.1': 'v2.0.1 --not v2.0.0',
- '2.0.2': 'origin/2.0.x --not v2.0.1',
- '2.1': 'origin/2.1.x --not origin/2.0.x',
- '2.2': 'origin/2.2.x --not origin/2.1.x',
- '3.0': 'origin/master --not origin/2.2.x'
- }
- try:
- api_token = os.environ['GITHUB_TOKEN']
- except KeyError:
- print('Error: set the environment variable GITHUB_TOKEN to a GitHub authentication token (see https://github.com/settings/tokens)')
- exit(1)
- if len(sys.argv) != 2:
- print('Error: exactly one argument expected, the milestone.')
- exit(1)
- MILESTONE=sys.argv[1]
- if MILESTONE not in ranges:
- print('Error: I do not know about milestone %r. Possible milestones are %r'%(MILESTONE, list(ranges.keys())))
- exit(1)
- out = subprocess.run("git log {} --format='%H,%cE,%s'".format(ranges[MILESTONE]), shell=True, encoding='utf8', stdout=subprocess.PIPE)
- commits = {i[0]: (i[1], i[2]) for i in (x.split(',',2) for x in out.stdout.splitlines())}
- url = 'https://api.github.com/graphql'
- json = { 'query' : """
- query test($cursor: String) {
- search(first: 50, after: $cursor, type: ISSUE, query: "repo:jupyterlab/jupyterlab milestone:%s is:pr is:merged ") {
- issueCount
- pageInfo {
- endCursor
- hasNextPage
- }
- nodes {
- ... on PullRequest {
- title
- number
- mergeCommit {
- oid
- }
- commits(first: 100) {
- totalCount
- nodes {
- commit {
- oid
- }
- }
- }
- }
- }
- }
- }
- """%MILESTONE,
- 'variables': {
- 'cursor': None
- }
- }
- headers = {'Authorization': 'token %s' % api_token}
- # construct a commit to PR dictionary
- prs = {}
- large_prs = []
- cursor = None
- while True:
- json['variables']['cursor'] = cursor
- r = requests.post(url=url, json=json, headers=headers)
- results = r.json()['data']['search']
- total_prs = results['issueCount']
- pr_list = results['nodes']
- for pr in pr_list:
- if pr['commits']['totalCount'] > 100:
- large_prs.append(pr['number'])
- continue
- # TODO fetch commits
- prs[pr['number']] = {'mergeCommit': pr['mergeCommit']['oid'],
- 'commits': set(i['commit']['oid'] for i in pr['commits']['nodes'])}
- has_next_page = results['pageInfo']['hasNextPage']
- cursor = results['pageInfo']['endCursor']
- if not has_next_page:
- break
- prjson = {'query': """
- query test($pr:Int!, $cursor: String) {
- repository(owner: "jupyterlab", name: "jupyterlab") {
- pullRequest(number: $pr) {
- title
- number
- mergeCommit {
- oid
- }
- commits(first: 100, after: $cursor) {
- totalCount
- pageInfo {
- endCursor
- hasNextPage
- }
- nodes {
- commit {
- oid
- }
- }
- }
- }
- }
- }
- """, 'variables': {
- 'pr': None,
- 'cursor': None
- }}
- for prnumber in large_prs:
- prjson['variables']['pr']=prnumber
- pr_commits = set()
- while True:
- r = requests.post(url=url, json=prjson, headers=headers)
- pr = r.json()['data']['repository']['pullRequest']
- assert pr['number']==prnumber
- total_commits = pr['commits']['totalCount']
- pr_commits.update(i['commit']['oid'] for i in pr['commits']['nodes'])
- has_next_page = results['pageInfo']['hasNextPage']
- cursor = results['pageInfo']['endCursor']
- if not pr['commits']['pageInfo']['hasNextPage']:
- break
- prjson['variables']['cursor'] = pr['commits']['pageInfo']['endCursor']
- prs[prnumber] = {'mergeCommit': pr['mergeCommit']['oid'],
- 'commits': pr_commits}
- if total_commits > len(pr_commits):
- print("WARNING: PR %d (merge %s) has %d commits, but GitHub is only giving us %d of them"%(prnumber, pr['mergeCommit']['oid'], total_commits, len(pr_commits)))
- # Check we got all PRs
- assert len(prs) == total_prs
- # Reverse dictionary
- commits_to_prs={}
- for key,value in prs.items():
- commits_to_prs[value['mergeCommit']]=key
- for c in value['commits']:
- commits_to_prs[c]=key
- # Check to see if commits in the repo are represented in PRs
- good = set()
- notfound = set()
- for c in commits:
- if c in commits_to_prs:
- good.add(commits_to_prs[c])
- else:
- notfound.add(c)
- prs_not_represented = set(prs.keys()) - good
- print("Milestone: %s, %d merged PRs, %d commits in history"%(MILESTONE, total_prs, len(commits)))
- print()
- print('-'*40)
- print()
- if len(prs_not_represented) > 0:
- print("""
- PRs that are in the milestone, but have no commits in the version range.
- These PRs probably belong in a different milestone.
- """)
- print('\n'.join('https://github.com/jupyterlab/jupyterlab/pull/%d'%i for i in prs_not_represented))
- else:
- print('Congratulations! All PRs in this milestone have commits in the commit history for this version range, so they all probably belong in this milestone.')
- print()
- print('-'*40)
- print()
- if len(notfound):
- print("""The following commits are not included in any PR on this milestone.
- This probably means the commit's PR needs to be assigned to this milestone,
- or the commit was pushed to master directly.
- """)
- print('\n'.join('%s %s %s'%(c, commits[c][0], commits[c][1]) for c in notfound))
- prs_to_check = [c for c in notfound if 'Merge pull request #' in commits[c][1] and commits[c][0] == 'noreply@github.com']
- if len(prs_to_check)>0:
- print()
- print("Try checking these PRs. They probably should be in the milestone, but probably aren't:")
- print()
- print('\n'.join('%s %s'%(c, commits[c][1]) for c in prs_to_check))
- else:
- print('Congratulations! All commits in the commit history are included in some PR in this milestone.')
|