88variables that are available in the Github Action environment. Specifically:
99
1010* GITHUB_WORKSPACE: directory where the git clone is located
11- * GITHUB_SHA: the git commit SHA of the artificial Github PR test merge commit
1211* GITHUB_BASE_REF: the git ref for the base branch
12+ * GITHUB_HEAD_REF: the git commit ref of the head branch
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
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
1820GITHUB_BASE_REF multiple ways:
1921
20221. Ensure that the committer and author do not match any bad patterns (e.g.,
5355NACP = "bot:notacherrypick"
5456
5557GITHUB_WORKSPACE = os .environ .get ('GITHUB_WORKSPACE' )
56- GITHUB_SHA = os .environ .get ('GITHUB_SHA' )
5758GITHUB_BASE_REF = os .environ .get ('GITHUB_BASE_REF' )
59+ GITHUB_HEAD_REF = os .environ .get ('GITHUB_HEAD_REF' )
5860GITHUB_TOKEN = os .environ .get ('GITHUB_TOKEN' )
5961GITHUB_REPOSITORY = os .environ .get ('GITHUB_REPOSITORY' )
6062GITHUB_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' )
6166
6267# Sanity check
6368if (GITHUB_WORKSPACE is None or
64- GITHUB_SHA is None or
6569 GITHUB_BASE_REF is None or
70+ GITHUB_HEAD_REF is None or
6671 GITHUB_TOKEN is None or
6772 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 ):
6977 print ("Error: this script is designed to run as a Github Action" )
7078 exit (1 )
7179
@@ -85,6 +93,50 @@ def make_commit_message(repo, hash):
8593
8694#----------------------------------------------------------------------------
8795
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+
88140"""
89141The results dictionary is in the following format:
90142
@@ -242,15 +294,15 @@ def _is_entirely_submodule_updates(repo, commit):
242294#----------------------------------------------------------------------------
243295
244296def 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}".
248303 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 ()
254306
255307 #------------------------------------------------------------------------
256308
@@ -292,15 +344,7 @@ def check_all_commits(config, repo):
292344If "bot:notacherrypick" is in the PR description, then disable the
293345cherry-pick message requirement.
294346"""
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 ):
304348 if pr .body and NACP in pr .body :
305349 config ['cherry pick required' ] = False
306350
@@ -334,11 +378,17 @@ def load_config():
334378
335379def main ():
336380 config = load_config ()
337- check_github_pr_description (config )
338381
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 )
342392
343393 if len (results [BAD ]) == 0 :
344394 print ("\n Test passed: everything was good!" )
0 commit comments