chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,585 @@
|
||||
# Copyright (c) DeepSpeed Team.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
name: DCO / required
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
checks: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
dco_required:
|
||||
name: DCO / required
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Validate commit signoffs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
# GitHub App ID for https://github.com/apps/dco.
|
||||
PROBOT_DCO_APP_ID = 1861
|
||||
|
||||
|
||||
def load_event(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def fail(message):
|
||||
print(f"::error::{message}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def extract_pr_numbers_from_refs(*refs):
|
||||
numbers = []
|
||||
text = "\n".join(value for value in refs if value)
|
||||
for match in re.finditer(r"(?:^|[/-])pr-(\d+)(?=$|[/-])", text):
|
||||
number = int(match.group(1))
|
||||
if number not in numbers:
|
||||
numbers.append(number)
|
||||
return numbers
|
||||
|
||||
|
||||
def extract_pull_request_number_from_refs(*refs):
|
||||
text = "\n".join(value for value in refs if value)
|
||||
match = re.search(r"(?:^|/)pull/(\d+)(?=/|$)", text)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
def discover_pr_numbers(event_name, event, github_ref):
|
||||
if event_name == "pull_request":
|
||||
number = event.get("number")
|
||||
if number is not None:
|
||||
try:
|
||||
return [int(number)]
|
||||
except (TypeError, ValueError):
|
||||
fail(f"pull_request event had non-integer number: {number!r}")
|
||||
|
||||
try:
|
||||
return [int(event["pull_request"]["number"])]
|
||||
except (KeyError, TypeError, ValueError):
|
||||
ref_number = extract_pull_request_number_from_refs(
|
||||
os.environ.get("GITHUB_REF"),
|
||||
github_ref,
|
||||
event.get("ref"),
|
||||
)
|
||||
if ref_number is not None:
|
||||
return [ref_number]
|
||||
fail("pull_request event did not include a pull request number")
|
||||
|
||||
if event_name == "merge_group":
|
||||
merge_group = event.get("merge_group", {})
|
||||
numbers = extract_pr_numbers_from_refs(
|
||||
merge_group.get("head_ref"),
|
||||
os.environ.get("GITHUB_REF_NAME"),
|
||||
github_ref,
|
||||
event.get("ref"),
|
||||
)
|
||||
if numbers:
|
||||
return numbers
|
||||
|
||||
fail(
|
||||
"merge_group event did not include a parseable PR number. "
|
||||
f"head_ref={merge_group.get('head_ref')!r} "
|
||||
f"GITHUB_REF_NAME={os.environ.get('GITHUB_REF_NAME')!r} "
|
||||
f"GITHUB_REF={github_ref!r}"
|
||||
)
|
||||
|
||||
fail(f"Unsupported event for DCO check: {event_name}")
|
||||
|
||||
|
||||
def graphql_request(query, variables, token):
|
||||
body = json.dumps({"query": query, "variables": variables}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"https://api.github.com/graphql",
|
||||
data=body,
|
||||
headers={
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "deepspeed-dco-check",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
fail(f"GitHub GraphQL request failed: HTTP {exc.code} {detail}")
|
||||
except urllib.error.URLError as exc:
|
||||
fail(f"GitHub GraphQL request failed: {exc}")
|
||||
|
||||
if payload.get("errors"):
|
||||
fail(f"GitHub GraphQL returned errors: {payload['errors']}")
|
||||
return payload["data"]
|
||||
|
||||
|
||||
def rest_request(path, token, fatal=True):
|
||||
url = f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}{path}"
|
||||
items = []
|
||||
while url:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "deepspeed-dco-check",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
data = json.loads(response.read().decode("utf-8"))
|
||||
link = response.headers.get("Link", "")
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
message = (
|
||||
f"GitHub REST request failed for {path}: "
|
||||
f"HTTP {exc.code} {detail}"
|
||||
)
|
||||
if fatal:
|
||||
fail(message)
|
||||
print(f"::warning::{message}")
|
||||
return None
|
||||
except urllib.error.URLError as exc:
|
||||
message = f"GitHub REST request failed for {path}: {exc}"
|
||||
if fatal:
|
||||
fail(message)
|
||||
print(f"::warning::{message}")
|
||||
return None
|
||||
|
||||
if isinstance(data, list):
|
||||
items.extend(data)
|
||||
else:
|
||||
commits = data.get("commits")
|
||||
if isinstance(commits, list):
|
||||
items.extend(commits)
|
||||
else:
|
||||
return data
|
||||
|
||||
next_url = None
|
||||
for part in link.split(","):
|
||||
if 'rel="next"' in part:
|
||||
next_url = part[part.find("<") + 1:part.find(">")]
|
||||
break
|
||||
url = next_url
|
||||
return items
|
||||
|
||||
|
||||
def fetch_compare_commits(base_sha, head_sha, token):
|
||||
base = urllib.parse.quote(base_sha, safe="")
|
||||
head = urllib.parse.quote(head_sha, safe="")
|
||||
return rest_request(f"/compare/{base}...{head}?per_page=100", token)
|
||||
|
||||
|
||||
def fetch_commit(sha, token):
|
||||
ref = urllib.parse.quote(sha, safe="")
|
||||
return rest_request(f"/commits/{ref}", token, fatal=False)
|
||||
|
||||
|
||||
def has_successful_probot_dco(head_sha, token):
|
||||
if not head_sha:
|
||||
return False
|
||||
|
||||
ref = urllib.parse.quote(head_sha, safe="")
|
||||
payload = rest_request(
|
||||
f"/commits/{ref}/check-runs?check_name=DCO&filter=latest",
|
||||
token,
|
||||
fatal=False,
|
||||
)
|
||||
if not payload:
|
||||
return False
|
||||
|
||||
for check_run in payload.get("check_runs", []):
|
||||
app = check_run.get("app") or {}
|
||||
is_probot_dco = app.get("slug") == "dco" or app.get("id") == PROBOT_DCO_APP_ID
|
||||
if (
|
||||
check_run.get("name") == "DCO"
|
||||
and is_probot_dco
|
||||
and check_run.get("status") == "completed"
|
||||
and check_run.get("conclusion") == "success"
|
||||
):
|
||||
print(
|
||||
"Found successful Probot DCO check for PR head "
|
||||
f"{head_sha}; accepting Probot result."
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def fetch_pr_commits(owner, repo, number, token):
|
||||
query = """
|
||||
query($owner: String!, $repo: String!, $number: Int!, $cursor: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
PULL_REQUEST_FIELD(number: $number) {
|
||||
baseRefName
|
||||
baseRepository {
|
||||
nameWithOwner
|
||||
}
|
||||
headRefOid
|
||||
commits(first: 100, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
commit {
|
||||
oid
|
||||
message
|
||||
author {
|
||||
name
|
||||
email
|
||||
user {
|
||||
login
|
||||
}
|
||||
}
|
||||
committer {
|
||||
name
|
||||
email
|
||||
user {
|
||||
login
|
||||
}
|
||||
}
|
||||
parents(first: 2) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".replace("PULL_REQUEST_FIELD", "pull" + "Request")
|
||||
cursor = None
|
||||
commits = []
|
||||
base_ref = None
|
||||
base_repo = None
|
||||
head_sha = None
|
||||
|
||||
while True:
|
||||
data = graphql_request(
|
||||
query,
|
||||
{"owner": owner, "repo": repo, "number": number, "cursor": cursor},
|
||||
token,
|
||||
)
|
||||
pull_request = data["repository"]["pull" + "Request"]
|
||||
if pull_request is None:
|
||||
fail(f"PR #{number} was not found")
|
||||
|
||||
base_ref = pull_request["baseRefName"]
|
||||
base_repo = pull_request["baseRepository"]["nameWithOwner"]
|
||||
head_sha = pull_request["headRefOid"]
|
||||
connection = pull_request["commits"]
|
||||
commits.extend(connection["nodes"])
|
||||
|
||||
page_info = connection["pageInfo"]
|
||||
if not page_info["hasNextPage"]:
|
||||
break
|
||||
cursor = page_info["endCursor"]
|
||||
if not cursor:
|
||||
fail(f"PR #{number} pagination did not return an end cursor")
|
||||
|
||||
return {
|
||||
"base_ref": base_ref,
|
||||
"base_repo": base_repo,
|
||||
"head_sha": head_sha,
|
||||
"commits": commits,
|
||||
}
|
||||
|
||||
|
||||
def has_signed_off_by(message):
|
||||
return bool(
|
||||
re.search(
|
||||
r"^Signed-off-by:\s+\S.*$",
|
||||
message,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def commit_subject(message):
|
||||
return message.splitlines()[0] if message.splitlines() else "(empty subject)"
|
||||
|
||||
|
||||
def actor_from_graphql(git_actor):
|
||||
git_actor = git_actor or {}
|
||||
user = git_actor.get("user") or {}
|
||||
return {
|
||||
"login": user.get("login") or "",
|
||||
"type": "",
|
||||
"name": git_actor.get("name") or "",
|
||||
"email": git_actor.get("email") or "",
|
||||
}
|
||||
|
||||
|
||||
def actor_from_rest(api_actor, git_actor):
|
||||
api_actor = api_actor or {}
|
||||
git_actor = git_actor or {}
|
||||
return {
|
||||
"login": api_actor.get("login") or "",
|
||||
"type": api_actor.get("type") or "",
|
||||
"name": git_actor.get("name") or "",
|
||||
"email": git_actor.get("email") or "",
|
||||
}
|
||||
|
||||
|
||||
def is_trusted_bot_actor(actor):
|
||||
actor = actor or {}
|
||||
return str(actor.get("type", "")).lower() == "bot"
|
||||
|
||||
|
||||
def has_bot_marker(actor):
|
||||
actor = actor or {}
|
||||
for key in ("login", "name", "email"):
|
||||
value = str(actor.get(key, "")).lower()
|
||||
if "[bot]" in value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def actor_label(actor):
|
||||
actor = actor or {}
|
||||
return (
|
||||
actor.get("login")
|
||||
or actor.get("name")
|
||||
or actor.get("email")
|
||||
or "unknown actor"
|
||||
)
|
||||
|
||||
|
||||
def is_verified_bot_authored(record, token):
|
||||
author = record.get("author") or {}
|
||||
if is_trusted_bot_actor(author):
|
||||
return True
|
||||
|
||||
if not token or not has_bot_marker(author):
|
||||
return False
|
||||
|
||||
commit = fetch_commit(record["sha"], token)
|
||||
if not commit:
|
||||
return False
|
||||
|
||||
api_author = commit.get("author") or {}
|
||||
if str(api_author.get("type", "")).lower() != "bot":
|
||||
return False
|
||||
|
||||
author["login"] = api_author.get("login") or author.get("login") or ""
|
||||
author["type"] = api_author.get("type") or author.get("type") or ""
|
||||
return True
|
||||
|
||||
|
||||
def validate_records(records, seen, token=None, skip_sha=None):
|
||||
failures = []
|
||||
checked = []
|
||||
skipped = []
|
||||
accepted = []
|
||||
for record in records:
|
||||
oid = record["sha"]
|
||||
if oid in seen:
|
||||
continue
|
||||
seen.add(oid)
|
||||
|
||||
if skip_sha and oid == skip_sha:
|
||||
skipped.append(oid)
|
||||
print(f"Skipping merge group head commit {oid}")
|
||||
continue
|
||||
|
||||
if record["parent_count"] > 1:
|
||||
skipped.append(oid)
|
||||
print(f"Skipping merge commit {oid}")
|
||||
continue
|
||||
|
||||
if is_verified_bot_authored(record, token):
|
||||
skipped.append(oid)
|
||||
print(
|
||||
"Skipping bot-authored commit "
|
||||
f"{oid} ({actor_label(record.get('author'))})"
|
||||
)
|
||||
continue
|
||||
|
||||
checked.append(oid)
|
||||
message = record.get("message") or ""
|
||||
if not has_signed_off_by(message):
|
||||
failures.append({"sha": oid, "subject": commit_subject(message)})
|
||||
|
||||
return {
|
||||
"checked": checked,
|
||||
"skipped": skipped,
|
||||
"accepted": accepted,
|
||||
"failures": failures,
|
||||
}
|
||||
|
||||
|
||||
def validate_pr(owner, repo, number, token, seen):
|
||||
print(f"Validating DCO trailers for PR #{number}")
|
||||
pull_request = fetch_pr_commits(owner, repo, number, token)
|
||||
expected_base = f"{owner}/{repo}"
|
||||
if (
|
||||
pull_request["base_repo"] != expected_base
|
||||
or pull_request["base_ref"] != "master"
|
||||
):
|
||||
fail(
|
||||
f"PR #{number} targets "
|
||||
f"{pull_request['base_repo']}:{pull_request['base_ref']}, "
|
||||
f"expected {expected_base}:master"
|
||||
)
|
||||
|
||||
records = []
|
||||
for node in pull_request["commits"]:
|
||||
commit = node["commit"]
|
||||
records.append(
|
||||
{
|
||||
"sha": commit["oid"],
|
||||
"message": commit.get("message") or "",
|
||||
"author": actor_from_graphql(commit.get("author")),
|
||||
"committer": actor_from_graphql(commit.get("committer")),
|
||||
"parent_count": commit["parents"]["totalCount"],
|
||||
}
|
||||
)
|
||||
|
||||
if not records:
|
||||
return {
|
||||
"checked": [],
|
||||
"skipped": [],
|
||||
"accepted": [],
|
||||
"failures": [
|
||||
{
|
||||
"sha": f"PR #{number}",
|
||||
"subject": "no commits returned by pull request commits API",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
if has_successful_probot_dco(pull_request["head_sha"], token):
|
||||
accepted = []
|
||||
for record in records:
|
||||
oid = record["sha"]
|
||||
if oid not in seen:
|
||||
seen.add(oid)
|
||||
accepted.append(oid)
|
||||
return {
|
||||
"checked": [],
|
||||
"skipped": [],
|
||||
"accepted": accepted,
|
||||
"failures": [],
|
||||
}
|
||||
|
||||
return validate_records(records, seen, token=token)
|
||||
|
||||
|
||||
def verify_merge_group_range_coverage(event, token, seen):
|
||||
merge_group = event.get("merge_group", {})
|
||||
base_sha = merge_group.get("base_sha")
|
||||
head_sha = merge_group.get("head_sha") or os.environ.get("GITHUB_SHA")
|
||||
if not base_sha or not head_sha:
|
||||
fail(
|
||||
"merge_group event did not include base_sha and head_sha. "
|
||||
f"base_sha={base_sha!r} head_sha={head_sha!r} "
|
||||
f"GITHUB_SHA={os.environ.get('GITHUB_SHA')!r}"
|
||||
)
|
||||
|
||||
print(f"Checking merge group range coverage {base_sha}...{head_sha}")
|
||||
commits = fetch_compare_commits(base_sha, head_sha, token)
|
||||
records = []
|
||||
for commit in commits:
|
||||
git_commit = commit.get("commit", {}) or {}
|
||||
records.append(
|
||||
{
|
||||
"sha": commit.get("sha", ""),
|
||||
"message": git_commit.get("message", "") or "",
|
||||
"author": actor_from_rest(
|
||||
commit.get("author"),
|
||||
git_commit.get("author"),
|
||||
),
|
||||
"committer": actor_from_rest(
|
||||
commit.get("committer"),
|
||||
git_commit.get("committer"),
|
||||
),
|
||||
"parent_count": len(commit.get("parents", [])),
|
||||
}
|
||||
)
|
||||
|
||||
if not commits:
|
||||
fail(f"merge_group compare range {base_sha}...{head_sha} returned no commits")
|
||||
|
||||
return validate_records(records, seen, token=token, skip_sha=head_sha)
|
||||
|
||||
|
||||
def main():
|
||||
repository = os.environ["GITHUB_REPOSITORY"]
|
||||
owner, repo = repository.split("/", 1)
|
||||
event_name = os.environ["GITHUB_EVENT_NAME"]
|
||||
github_ref = os.environ.get("GITHUB_REF")
|
||||
token = os.environ["GITHUB_TOKEN"]
|
||||
event = load_event(os.environ["GITHUB_EVENT_PATH"])
|
||||
|
||||
pull_numbers = discover_pr_numbers(event_name, event, github_ref)
|
||||
failures = []
|
||||
checked = set()
|
||||
skipped = set()
|
||||
accepted = set()
|
||||
seen = set()
|
||||
|
||||
for number in sorted(pull_numbers):
|
||||
result = validate_pr(owner, repo, number, token, seen)
|
||||
failures.extend(result["failures"])
|
||||
checked.update(result["checked"])
|
||||
skipped.update(result["skipped"])
|
||||
accepted.update(result.get("accepted", []))
|
||||
|
||||
if event_name == "merge_group":
|
||||
result = verify_merge_group_range_coverage(
|
||||
event,
|
||||
token,
|
||||
seen,
|
||||
)
|
||||
failures.extend(result["failures"])
|
||||
checked.update(result["checked"])
|
||||
skipped.update(result["skipped"])
|
||||
accepted.update(result.get("accepted", []))
|
||||
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"::error::{failure['sha']}: {failure['subject']}")
|
||||
fail(
|
||||
f"{len(failures)} commit(s) are missing a Signed-off-by trailer."
|
||||
)
|
||||
|
||||
print(
|
||||
"DCO validation passed for "
|
||||
f"{len(pull_numbers)} pull request(s): "
|
||||
f"checked {len(checked)} commit(s), "
|
||||
f"accepted {len(accepted)} commit(s) via Probot DCO, "
|
||||
f"skipped {len(skipped)} merge, bot, or synthetic commit(s)."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
PY
|
||||
Reference in New Issue
Block a user