milestone_check.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # Copyright (c) 2018 Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. # Generate a GitHub token at https://github.com/settings/tokens
  4. # Invoke this script using something like:
  5. # python scripts/milestone_check.py
  6. import subprocess
  7. import requests
  8. import os
  9. try:
  10. api_token = os.environ['GITHUB_TOKEN']
  11. except KeyError:
  12. print('Error: set the environment variable GITHUB_TOKEN to a GitHub authentication token (see https://github.com/settings/tokens)')
  13. exit(1)
  14. MILESTONE='1.0'
  15. ranges = {
  16. 18: 'origin/0.35.0 --not origin/0.34.x', #0.35.0
  17. 20: 'origin/0.35.x --not v0.35.0', #0.35.x
  18. '1.0': 'origin/master --not origin/0.35.x',
  19. }
  20. out = subprocess.run("git log {} --format='%H,%cE,%s'".format(ranges[MILESTONE]), shell=True, encoding='utf8', stdout=subprocess.PIPE)
  21. commits = {i[0]: (i[1], i[2]) for i in (x.split(',',2) for x in out.stdout.splitlines())}
  22. url = 'https://api.github.com/graphql'
  23. json = { 'query' : """
  24. query test($cursor: String) {
  25. search(first: 50, after: $cursor, type: ISSUE, query: "repo:jupyterlab/jupyterlab milestone:%s is:pr is:merged ") {
  26. issueCount
  27. pageInfo {
  28. endCursor
  29. hasNextPage
  30. }
  31. nodes {
  32. ... on PullRequest {
  33. title
  34. number
  35. mergeCommit {
  36. oid
  37. }
  38. commits(first: 100) {
  39. totalCount
  40. nodes {
  41. commit {
  42. oid
  43. }
  44. }
  45. }
  46. }
  47. }
  48. }
  49. }
  50. """%MILESTONE,
  51. 'variables': {
  52. 'cursor': None
  53. }
  54. }
  55. headers = {'Authorization': 'token %s' % api_token}
  56. # construct a commit to PR dictionary
  57. prs = {}
  58. large_prs = []
  59. cursor = None
  60. while True:
  61. json['variables']['cursor'] = cursor
  62. r = requests.post(url=url, json=json, headers=headers)
  63. results = r.json()['data']['search']
  64. total_prs = results['issueCount']
  65. pr_list = results['nodes']
  66. for pr in pr_list:
  67. if pr['commits']['totalCount'] > 100:
  68. large_prs.append(pr['number'])
  69. continue
  70. # TODO fetch commits
  71. prs[pr['number']] = {'mergeCommit': pr['mergeCommit']['oid'],
  72. 'commits': set(i['commit']['oid'] for i in pr['commits']['nodes'])}
  73. has_next_page = results['pageInfo']['hasNextPage']
  74. cursor = results['pageInfo']['endCursor']
  75. if not has_next_page:
  76. break
  77. prjson = {'query': """
  78. query test($pr:Int!, $cursor: String) {
  79. repository(owner: "jupyterlab", name: "jupyterlab") {
  80. pullRequest(number: $pr) {
  81. title
  82. number
  83. mergeCommit {
  84. oid
  85. }
  86. commits(first: 100, after: $cursor) {
  87. totalCount
  88. pageInfo {
  89. endCursor
  90. hasNextPage
  91. }
  92. nodes {
  93. commit {
  94. oid
  95. }
  96. }
  97. }
  98. }
  99. }
  100. }
  101. """, 'variables': {
  102. 'pr': None,
  103. 'cursor': None
  104. }}
  105. for prnumber in large_prs:
  106. prjson['variables']['pr']=prnumber
  107. pr_commits = set()
  108. while True:
  109. r = requests.post(url=url, json=prjson, headers=headers)
  110. pr = r.json()['data']['repository']['pullRequest']
  111. assert pr['number']==prnumber
  112. total_commits = pr['commits']['totalCount']
  113. pr_commits.update(i['commit']['oid'] for i in pr['commits']['nodes'])
  114. has_next_page = results['pageInfo']['hasNextPage']
  115. cursor = results['pageInfo']['endCursor']
  116. if not pr['commits']['pageInfo']['hasNextPage']:
  117. break
  118. prjson['variables']['cursor'] = pr['commits']['pageInfo']['endCursor']
  119. prs[prnumber] = {'mergeCommit': pr['mergeCommit']['oid'],
  120. 'commits': pr_commits}
  121. if total_commits > len(pr_commits):
  122. 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)))
  123. # Check we got all PRs
  124. assert len(prs) == total_prs
  125. # Reverse dictionary
  126. commits_to_prs={}
  127. for key,value in prs.items():
  128. commits_to_prs[value['mergeCommit']]=key
  129. for c in value['commits']:
  130. commits_to_prs[c]=key
  131. # Check to see if commits in the repo are represented in PRs
  132. good = set()
  133. notfound = set()
  134. for c in commits:
  135. if c in commits_to_prs:
  136. good.add(commits_to_prs[c])
  137. else:
  138. notfound.add(c)
  139. prs_not_represented = set(prs.keys()) - good
  140. print("Milestone: %s, %d merged PRs, %d commits in history"%(MILESTONE, total_prs, len(commits)))
  141. print()
  142. print('-'*40)
  143. print()
  144. if len(prs_not_represented) > 0:
  145. print("""
  146. PRs that are in the milestone, but have no commits in the version range.
  147. These PRs probably belong in a different milestone.
  148. """)
  149. print('\n'.join('https://github.com/jupyterlab/jupyterlab/pull/%d'%i for i in prs_not_represented))
  150. else:
  151. 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.')
  152. print()
  153. print('-'*40)
  154. print()
  155. if len(notfound):
  156. print("""The following commits are not included in any PR on this milestone.
  157. This probably means the commit's PR needs to be assigned to this milestone,
  158. or the commit was pushed to master directly.
  159. """)
  160. print('\n'.join('%s %s %s'%(c, commits[c][0], commits[c][1]) for c in notfound))
  161. prs_to_check = [c for c in notfound if 'Merge pull request #' in commits[c][1] and commits[c][0] == 'noreply@github.com']
  162. if len(prs_to_check)>0:
  163. print()
  164. print("Try checking these PRs. They probably should be in the milestone, but probably aren't:")
  165. print()
  166. print('\n'.join('%s %s'%(c, commits[c][1]) for c in prs_to_check))
  167. else:
  168. print('Congratulations! All commits in the commit history are included in some PR in this milestone.')