8
8
variables that are available in the Github Action environment. Specifically:
9
9
10
10
* GITHUB_WORKSPACE: directory where the git clone is located
11
- * GITHUB_SHA: the git commit SHA of the artificial Github PR test merge commit
12
11
* GITHUB_BASE_REF: the git ref for the base branch
12
+ * GITHUB_HEAD_REF: the git commit ref of the head branch
13
13
* GITHUB_TOKEN: token authorizing Github API usage
14
14
* GITHUB_REPOSITORY: "org/repo" name of the Github repository of this PR
15
15
* 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
16
18
17
- This script tests each git commit between (and not including) GITHUB_SHA and
19
+ This script tests each git commit between (and not including) GITHUB_HEAD_REF and
18
20
GITHUB_BASE_REF multiple ways:
19
21
20
22
1. Ensure that the committer and author do not match any bad patterns (e.g.,
53
55
NACP = "bot:notacherrypick"
54
56
55
57
GITHUB_WORKSPACE = os .environ .get ('GITHUB_WORKSPACE' )
56
- GITHUB_SHA = os .environ .get ('GITHUB_SHA' )
57
58
GITHUB_BASE_REF = os .environ .get ('GITHUB_BASE_REF' )
59
+ GITHUB_HEAD_REF = os .environ .get ('GITHUB_HEAD_REF' )
58
60
GITHUB_TOKEN = os .environ .get ('GITHUB_TOKEN' )
59
61
GITHUB_REPOSITORY = os .environ .get ('GITHUB_REPOSITORY' )
60
62
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' )
65
+ PR_NUM = os .environ .get ('PR_NUM' )
61
66
62
67
# Sanity check
63
68
if (GITHUB_WORKSPACE is None or
64
- GITHUB_SHA is None or
65
69
GITHUB_BASE_REF is None or
70
+ GITHUB_HEAD_REF is None or
66
71
GITHUB_TOKEN is None or
67
72
GITHUB_REPOSITORY is None or
68
- GITHUB_REF is None ):
73
+ GITHUB_REF is None or
74
+ GITHUB_RUN_ID is None or
75
+ GITHUB_SERVER_URL is None or
76
+ PR_NUM is None ):
69
77
print ("Error: this script is designed to run as a Github Action" )
70
78
exit (1 )
71
79
@@ -85,6 +93,50 @@ def make_commit_message(repo, hash):
85
93
86
94
#----------------------------------------------------------------------------
87
95
96
+ """
97
+ Iterate through the BAD results, collect the error messages, and send a nicely
98
+ formatted comment to the PR.
99
+
100
+ For the structure of the results dictionary, see comment for print_results()
101
+ below.
102
+
103
+ """
104
+ def comment_on_pr (pr , results , repo ):
105
+ # If there are no BAD results, just return without posting a comment to the
106
+ # GitHub PR.
107
+ if len (results [BAD ]) == 0 :
108
+ return
109
+
110
+ comment = "Hello! The Git Commit Checker CI bot found a few problems with this PR:"
111
+ for hash , entry in results [BAD ].items ():
112
+ comment += f"\n \n **{ hash [:8 ]} : { make_commit_message (repo , hash )} **"
113
+ for check_name , message in entry .items ():
114
+ if message is not None :
115
+ comment += f"\n * *{ check_name } : { message } *"
116
+ comment_footer = "\n \n Please fix these problems and, if necessary, force-push new commits back up to the PR branch. Thanks!"
117
+
118
+ # GitHub says that 65536 characters is the limit of comment messages, so
119
+ # check if our comment is over that limit. If it is, truncate it to fit, and
120
+ # add a message explaining with a link to the full error list.
121
+ comment_char_limit = 65536
122
+ if len (comment + comment_footer ) >= comment_char_limit :
123
+ run_url = f"{ GITHUB_SERVER_URL } /{ GITHUB_REPOSITORY } /actions/runs/{ GITHUB_RUN_ID } ?check_suite_focus=true"
124
+ truncation_message = f"\n \n **Additional errors could not be shown...\n [Please click here for a full list of errors.]({ run_url } )**"
125
+ # Cut the comment down so we can get the comment itself, and the new
126
+ # message in.
127
+ comment = comment [:(comment_char_limit - len (comment_footer + truncation_message ))]
128
+ # In case a newline gets split in half, remove the leftover '\' (if
129
+ # there is one). (This is purely an aesthetics choice).
130
+ comment = comment .rstrip ("\\ " )
131
+ comment += truncation_message
132
+
133
+ comment += comment_footer
134
+ pr .create_issue_comment (comment )
135
+
136
+ return
137
+
138
+ #----------------------------------------------------------------------------
139
+
88
140
"""
89
141
The results dictionary is in the following format:
90
142
@@ -242,15 +294,15 @@ def _is_entirely_submodule_updates(repo, commit):
242
294
#----------------------------------------------------------------------------
243
295
244
296
def check_all_commits (config , repo ):
245
- # Get a list of commits that we'll be examining. Use the progromatic form
246
- # of "git log GITHUB_BASE_REF..GITHUB_SHA" (i.e., "git log ^GITHUB_BASE_REF
247
- # GITHUB_SHA") to do the heavy lifting to find that set of commits.
297
+ # Get a list of commits that we'll be examining. Use the programmatic form
298
+ # of "git log GITHUB_BASE_REF..GITHUB_HEAD_REF" (i.e., "git log
299
+ # ^GITHUB_BASE_REF GITHUB_HEAD_REF") to do the heavy lifting to find that
300
+ # set of commits. Because we're using pull_request_target, GITHUB_BASE_REF
301
+ # is already checked out. GITHUB_HEAD_REF has never been checked out, so we
302
+ # specify "origin/{GITHUB_HEAD_REF}".
248
303
git_cli = git .cmd .Git (GITHUB_WORKSPACE )
249
- hashes = git_cli .log (f"--pretty=format:%h" , f"origin/{ GITHUB_BASE_REF } ..{ GITHUB_SHA } " ).splitlines ()
250
-
251
- # The first entry in the list will be the artificial Github merge commit for
252
- # this PR. We don't want to examine this commit.
253
- del hashes [0 ]
304
+ hashes = git_cli .log (f"--pretty=format:%h" ,
305
+ f"{ GITHUB_BASE_REF } ..origin/{ GITHUB_HEAD_REF } " ).splitlines ()
254
306
255
307
#------------------------------------------------------------------------
256
308
@@ -292,15 +344,7 @@ def check_all_commits(config, repo):
292
344
If "bot:notacherrypick" is in the PR description, then disable the
293
345
cherry-pick message requirement.
294
346
"""
295
- def check_github_pr_description (config ):
296
- g = Github (GITHUB_TOKEN )
297
- repo = g .get_repo (GITHUB_REPOSITORY )
298
-
299
- # Extract the PR number from GITHUB_REF
300
- match = re .search ("/(\d+)/" , GITHUB_REF )
301
- pr_num = int (match .group (1 ))
302
- pr = repo .get_pull (pr_num )
303
-
347
+ def check_github_pr_description (config , pr ):
304
348
if pr .body and NACP in pr .body :
305
349
config ['cherry pick required' ] = False
306
350
@@ -334,11 +378,17 @@ def load_config():
334
378
335
379
def main ():
336
380
config = load_config ()
337
- check_github_pr_description (config )
338
381
339
- repo = git .Repo (GITHUB_WORKSPACE )
340
- results , hashes = check_all_commits (config , repo )
341
- print_results (results , repo , hashes )
382
+ g = Github (GITHUB_TOKEN )
383
+ github_repo = g .get_repo (GITHUB_REPOSITORY )
384
+ pr_num = int (PR_NUM )
385
+ pr = github_repo .get_pull (pr_num )
386
+ check_github_pr_description (config , pr )
387
+
388
+ local_repo = git .Repo (GITHUB_WORKSPACE )
389
+ results , hashes = check_all_commits (config , local_repo )
390
+ print_results (results , local_repo , hashes )
391
+ comment_on_pr (pr , results , local_repo )
342
392
343
393
if len (results [BAD ]) == 0 :
344
394
print ("\n Test passed: everything was good!" )
0 commit comments