123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- # 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 os
- import subprocess
- import sys
- import requests
- 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",
- # 6507205805 is a commit in the debugger ancestor tree before merging
- "3.0": "origin/master ^origin/2.2.x ^6507205805",
- }
- 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."
- )
|