Skip to content

Commit 08d0430

Browse files
committed
git-commit-checks: comment on PR with error(s)
This adds comments onto the pull request containing what caused the commit checks to fail, if any, and suggests fixes to the user. If no errors are raised, no comment is made. GitHub says there's a limit of 65536 characters on comments. If the bot's comment is over that limit, it will truncate the comment to fit, and add a message explaining where the remaining errors can be found. Unfortunately, the GitHub API doesn't seem to provide a job's unique ID for linking to a job run (this is different than an action run: ".../runs/..." vs ".../actions/runs/...", respectively), so we can't directly link to the error messages printed to the console. Additionally, to create this link, two new environment variables are used: GITHUB_RUN_ID and GITHUB_SERVER_URL. Because we need the PR object twice, check_github_pr_description() was also changed to have the PR object passed into it; the PR object is gotten with a new function, get_github_pr(). Signed-off-by: Joe Downs <joe@dwns.dev>
1 parent f0101dc commit 08d0430

File tree

1 file changed

+62
-5
lines changed

1 file changed

+62
-5
lines changed

.github/workflows/git-commit-checks.py

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* GITHUB_TOKEN: token authorizing Github API usage
1414
* GITHUB_REPOSITORY: "org/repo" name of the Github repository of this PR
1515
* GITHUB_REF: string that includes this Github PR number
16+
* GITHUB_RUN_ID: unique ID for each workflow run
17+
* GITHUB_SERVER_URL: the URL of the GitHub server
1618
1719
This script tests each git commit between (and not including) GITHUB_SHA and
1820
GITHUB_BASE_REF multiple ways:
@@ -58,14 +60,18 @@
5860
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
5961
GITHUB_REPOSITORY = os.environ.get('GITHUB_REPOSITORY')
6062
GITHUB_REF = os.environ.get('GITHUB_REF')
63+
GITHUB_RUN_ID = os.environ.get('GITHUB_RUN_ID')
64+
GITHUB_SERVER_URL = os.environ.get('GITHUB_SERVER_URL')
6165

6266
# Sanity check
6367
if (GITHUB_WORKSPACE is None or
6468
GITHUB_SHA is None or
6569
GITHUB_BASE_REF is None or
6670
GITHUB_TOKEN is None or
6771
GITHUB_REPOSITORY is None or
68-
GITHUB_REF is None):
72+
GITHUB_REF is None or
73+
GITHUB_RUN_ID is None or
74+
GITHUB_SERVER_URL is None):
6975
print("Error: this script is designed to run as a Github Action")
7076
exit(1)
7177

@@ -85,6 +91,50 @@ def make_commit_message(repo, hash):
8591

8692
#----------------------------------------------------------------------------
8793

94+
"""
95+
Iterate through the BAD results, collect the error messages, and send a nicely
96+
formatted comment to the PR.
97+
98+
For the structure of the results dictionary, see comment for print_results()
99+
below.
100+
101+
"""
102+
def comment_on_pr(pr, results, repo):
103+
# If there are no BAD results, just return without posting a comment to the
104+
# GitHub PR.
105+
if len(results[BAD]) == 0:
106+
return
107+
108+
comment = "Hello! The Git Commit Checker CI bot found a few problems with this PR:"
109+
for hash, entry in results[BAD].items():
110+
comment += f"\n\n**{hash[:8]}: {make_commit_message(repo, hash)}**"
111+
for check_name, message in entry.items():
112+
if message is not None:
113+
comment += f"\n * *{check_name}: {message}*"
114+
comment_footer = "\n\nPlease fix these problems and, if necessary, force-push new commits back up to the PR branch. Thanks!"
115+
116+
# GitHub says that 65536 characters is the limit of comment messages, so
117+
# check if our comment is over that limit. If it is, truncate it to fit, and
118+
# add a message explaining with a link to the full error list.
119+
comment_char_limit = 65536
120+
if len(comment + comment_footer) >= comment_char_limit:
121+
run_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/actions/runs/{GITHUB_RUN_ID}?check_suite_focus=true"
122+
truncation_message = f"\n\n**Additional errors could not be shown...\n[Please click here for a full list of errors.]({run_url})**"
123+
# Cut the comment down so we can get the comment itself, and the new
124+
# message in.
125+
comment = comment[:(comment_char_limit - len(comment_footer + truncation_message))]
126+
# In case a newline gets split in half, remove the leftover '\' (if
127+
# there is one). (This is purely an aesthetics choice).
128+
comment = comment.rstrip("\\")
129+
comment += truncation_message
130+
131+
comment += comment_footer
132+
pr.create_issue_comment(comment)
133+
134+
return
135+
136+
#----------------------------------------------------------------------------
137+
88138
"""
89139
The results dictionary is in the following format:
90140
@@ -292,7 +342,13 @@ def check_all_commits(config, repo):
292342
If "bot:notacherrypick" is in the PR description, then disable the
293343
cherry-pick message requirement.
294344
"""
295-
def check_github_pr_description(config):
345+
def check_github_pr_description(config, pr):
346+
if pr.body and NACP in pr.body:
347+
config['cherry pick required'] = False
348+
349+
#----------------------------------------------------------------------------
350+
351+
def get_github_pr():
296352
g = Github(GITHUB_TOKEN)
297353
repo = g.get_repo(GITHUB_REPOSITORY)
298354

@@ -301,8 +357,7 @@ def check_github_pr_description(config):
301357
pr_num = int(match.group(1))
302358
pr = repo.get_pull(pr_num)
303359

304-
if pr.body and NACP in pr.body:
305-
config['cherry pick required'] = False
360+
return pr
306361

307362
#----------------------------------------------------------------------------
308363

@@ -334,11 +389,13 @@ def load_config():
334389

335390
def main():
336391
config = load_config()
337-
check_github_pr_description(config)
392+
pr = get_github_pr()
393+
check_github_pr_description(config, pr)
338394

339395
repo = git.Repo(GITHUB_WORKSPACE)
340396
results, hashes = check_all_commits(config, repo)
341397
print_results(results, repo, hashes)
398+
comment_on_pr(pr, results, repo)
342399

343400
if len(results[BAD]) == 0:
344401
print("\nTest passed: everything was good!")

0 commit comments

Comments
 (0)