milestone_check.py 5.6 KB

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