From 912774cb1a52fd1dd8ddfdb4681c0c733f96630f Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Mon, 19 Aug 2024 15:05:55 -0600 Subject: [PATCH 1/6] Modify issue-closing script to work without projects or standard merge messages. --- .github/scripts/branch_pr_issue_closer.py | 461 ++++++--------------- .github/workflows/branch_push_workflow.yml | 8 +- 2 files changed, 130 insertions(+), 339 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 1ad48ebe..fc4a7087 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Script name: branch_PR_issue_closer.py @@ -21,52 +21,15 @@ import re import sys -import subprocess -import shlex import argparse from github import Github +from github import Auth ################# #HELPER FUNCTIONS ################# -#+++++++++++++++++++++++++++++++++++++++++ -#Curl command needed to move project cards -#+++++++++++++++++++++++++++++++++++++++++ - -def project_card_move(oa_token, column_id, card_id): - - """ - Currently pyGithub doesn't contain the methods required - to move project cards from one column to another, so - the unix curl command must be called directly, which is - what this function does. - - The specific command-line call made is: - - curl -H "Authorization: token OA_token" -H \ - "Accept: application/vnd.github.inertia-preview+json" \ - -X POST -d '{"position":"top", "column_id":}' \ - https://api.github.com/projects/columns/cards//moves - - """ - - #create required argument strings from inputs: - github_oa_header = f''' "Authorization: token {oa_token}" ''' - github_url_str = f'''https://api.github.com/projects/columns/cards/{card_id}/moves''' - json_post_inputs = f''' '{{"position":"top", "column_id":{column_id}}}' ''' - - #Create curl command line string: - curl_cmdline = '''curl -H '''+github_oa_header+''' -H "Accept: application/vnd.github.inertia-preview+json" -X POST -d '''+\ - json_post_inputs+''' '''+github_url_str - - #Split command line string into argument list: - curl_arg_list = shlex.split(curl_cmdline) - - #Run command using subprocess: - subprocess.run(curl_arg_list, check=True) - #++++++++++++++++++++++++++++++ #Input Argument parser function #++++++++++++++++++++++++++++++ @@ -135,176 +98,162 @@ def _main_prog(): #Log-in to github API using token #++++++++++++++++++++++++++++++++ - ghub = Github(token) + auth = Auth.Token(token) + ghub = Github(auth=auth) - #+++++++++++++++++++++ + #+++++++++++++++++++++++++ #Open ESCOMP/CAM-SIMA repo - #+++++++++++++++++++++ + #+++++++++++++++++++++++++ cam_repo = ghub.get_repo("ESCOMP/CAM-SIMA") - #+++++++++++++++++++++++++++++ - #Get triggering commit message - #+++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++ + #Get PRs associated with commit + #++++++++++++++++++++++++++++++ github_commit = cam_repo.get_commit(trigger_sha) - commit_message = github_commit.commit.message + commit_prs = github_commit.get_pulls() - #+++++++++++++++++++++++++++++++ - #Search for github PR merge text - #+++++++++++++++++++++++++++++++ + pr_nums = [pr.number for pr in commit_prs] - #Compile Pull Request merge text expression: - pr_merge_pattern = re.compile(r'Merge pull request ') - #Search for merge text, starting at beginning of message: - commit_msg_match = pr_merge_pattern.match(commit_message) + #If list is empty, then no PRs are associated + #with this commit, so go ahead and close: + if not pr_nums: + endmsg = f"No PRs associated with commit:\n{trigger_sha}\n" + endmsg += " so issue-closing script is stopping here." + end_script(endmsg) - #Check if match exists: - if commit_msg_match is not None: - #If it does then pull out text immediately after message: - post_msg_text = commit_message[commit_msg_match.end():] + #++++++++++++++++++++++++++++ + #Loop over all associated PRs + #++++++++++++++++++++++++++++ - #Split text into individual words: - post_msg_word_list = post_msg_text.split() + for pr_num in pr_nums: - #Extract first word: - first_word = post_msg_word_list[0] + #+++++++++++++++++++++++++++++++++++++ + #Check that PR has in fact been merged + #+++++++++++++++++++++++++++++++++++++ - #Print merged pr number to screen: - print(f"Merged PR: {first_word}") + #Extract pull request info: + merged_pull = cam_repo.get_pull(pr_num) - try: - #Try assuming the word is just a number: - pr_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If the conversion fails, then this is likely not a real PR merge, so end the script: - endmsg = "No Pull Request number was found in the commit message, so there is nothing for the script to do." + #If pull request has not been merged, then exit script: + if not merged_pull.merged: + endmsg = f"Pull request associated with commit:\n{trigger_sha}\n" + endmsg += "was not actually merged, so the script will not close anything." end_script(endmsg) - else: - endmsg = "This push commit does not appear to be a merged pull request, so the script will do nothing." - end_script(endmsg) - - #+++++++++++++++++++++++++++++++++++++ - #Check that PR has in fact been merged - #+++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++++ + #Check that PR was not for default branch + #++++++++++++++++++++++++++++++++++++++++ - #Extract pull request info: - merged_pull = cam_repo.get_pull(pr_num) + #Determine default branch on repo: + default_branch = cam_repo.default_branch - #If pull request has not been merged, then exit script: - if not merged_pull.merged: - endmsg = "Pull request in commit message was not actually merged, so the script will not close anything." - end_script(endmsg) - - #++++++++++++++++++++++++++++++++++++++++ - #Check that PR was not for default branch - #++++++++++++++++++++++++++++++++++++++++ - - #Determine default branch on repo: - default_branch = cam_repo.default_branch - - #Extract merged branch from latest Pull request: - merged_branch = merged_pull.base.ref + #Extract merged branch from latest Pull request: + merged_branch = merged_pull.base.ref - #If PR was to default branch, then exit script (as github will handle it automatically): - if merged_branch == default_branch: - endmsg = "Pull request ws merged into default repo branch. Thus issue is closed automatically" - end_script(endmsg) + #If PR was to default branch, then exit script (as github will handle it automatically): + if merged_branch == default_branch: + endmsg = "Pull request was merged into default repo branch. " + endmsg += "Thus issue is closed automatically" + end_script(endmsg) - #++++++++++++++++++++++++++++++++++++++ - #Create integer list of all open issues: - #++++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++ + #Create integer list of all open issues: + #++++++++++++++++++++++++++++++++++++++ - #Extract list of open issues from repo: - open_repo_issues = cam_repo.get_issues(state='open') + #Extract list of open issues from repo: + open_repo_issues = cam_repo.get_issues(state='open') - #Collect all open repo issues: - open_issues = [issue.number for issue in open_repo_issues] + #Collect all open repo issues: + open_issues = [issue.number for issue in open_repo_issues] - #+++++++++++++++++++++++++++++++++++++++++++++ - #Create integer list of all open pull requests - #+++++++++++++++++++++++++++++++++++++++++++++ + #+++++++++++++++++++++++++++++++++++++++++++++ + #Create integer list of all open pull requests + #+++++++++++++++++++++++++++++++++++++++++++++ - #Extract list of open PRs from repo: - open_repo_pulls = cam_repo.get_pulls(state='open') + #Extract list of open PRs from repo: + open_repo_pulls = cam_repo.get_pulls(state='open') - #Collect all open pull requests: - open_pulls = [pr.number for pr in open_repo_pulls] + #Collect all open pull requests: + open_pulls = [pr.number for pr in open_repo_pulls] - #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Check if one of the keywords exists in PR message - #+++++++++++++++++++++++++++++++++++++++++++++++++ + #+++++++++++++++++++++++++++++++++++++++++++++++++ + #Check if one of the keywords exists in PR message + #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Keywords are: - #close, closes, closed - #fix, fixes, fixed - #resolve, resolves, resolved + #Keywords are: + #close, closes, closed + #fix, fixes, fixed + #resolve, resolves, resolved - #Create regex pattern to find keywords: - keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') + #Create regex pattern to find keywords: + keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') - #Extract (lower case) Pull Request message: - pr_msg_lower = merged_pull.body.lower() + #Extract (lower case) Pull Request message: + pr_msg_lower = merged_pull.body.lower() - #search for at least one keyword: - if keyword_pattern.search(pr_msg_lower) is not None: - #If at least one keyword is found, then determine location of every keyword instance: - word_matches = keyword_pattern.finditer(pr_msg_lower) - else: - endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." - end_script(endmsg) + #search for at least one keyword: + word_matches = [] + if keyword_pattern.search(pr_msg_lower) is not None: + #If at least one keyword is found, then determine location of every keyword instance: + word_matches = keyword_pattern.finditer(pr_msg_lower) + else: + endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." + end_script(endmsg) - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Extract issue and PR numbers associated with found keywords in merged PR message - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #Extract issue and PR numbers associated with found keywords in merged PR message + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #create issue pattern ("the number symbol {#} + a number"), - #which ends with either a space, a comma, a period, or - #the end of the string itself: - issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') + #create issue pattern ("the number symbol {#} + a number"), + #which ends with either a space, a comma, a period, or + #the end of the string itself: + issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') - #Create new "close" issues list: - close_issues = [] + #Create new "close" issues list: + close_issues = [] - #Create new "closed" PR list: - close_pulls = [] + #Create new "closed" PR list: + close_pulls = [] - #Search text right after keywords for possible issue numbers: - for match in word_matches: + #Search text right after keywords for possible issue numbers: + for match in word_matches: - #create temporary string starting at end of match: - tmp_msg_str = pr_msg_lower[match.end():] + #create temporary string starting at end of match: + tmp_msg_str = pr_msg_lower[match.end():] - #Check if first word matches issue pattern: - if issue_pattern.match(tmp_msg_str) is not None: + #Check if first word matches issue pattern: + if issue_pattern.match(tmp_msg_str) is not None: - #If so, then look for an issue number immediately following - first_word = tmp_msg_str.split()[0] + #If so, then look for an issue number immediately following + first_word = tmp_msg_str.split()[0] - #Extract issue number from first word: - try: - #First try assuming the string is just a number - issue_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If not, then ignore last letter: + #Extract issue number from first word: try: - issue_num = int(first_word[1:-1]) + #First try assuming the string is just a number + issue_num = int(first_word[1:]) #ignore "#" symbol except ValueError: - #If ignoring the first and last letter doesn't work, - #then the match was likely a false positive, - #so set the issue number to one that will never be found: - issue_num = -9999 - - #Check if number is actually for a PR (as opposed to an issue): - if issue_num in open_pulls: - #Add PR number to "close pulls" list: - close_pulls.append(issue_num) - elif issue_num in open_issues: - #If in fact an issue, then add to "close issues" list: - close_issues.append(issue_num) + #If not, then ignore last letter: + try: + issue_num = int(first_word[1:-1]) + except ValueError: + #If ignoring the first and last letter doesn't work, + #then the match was likely a false positive, + #so set the issue number to one that will never be found: + issue_num = -9999 + + #Check if number is actually for a PR (as opposed to an issue): + if issue_num in open_pulls: + #Add PR number to "close pulls" list: + close_pulls.append(issue_num) + elif issue_num in open_issues: + #If in fact an issue, then add to "close issues" list: + close_issues.append(issue_num) + + #+++END REFERENCED PR LOOP+++ #If no issue numbers are present after any of the keywords, then exit script: if not close_issues and not close_pulls: @@ -321,177 +270,19 @@ def _main_prog(): print("PRs referenced by the merged PR: "+", ".join(\ str(pull) for pull in close_pulls)) - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Determine name of project associated with merged Pull Request - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - #Pull-out all projects from repo: - projects = cam_repo.get_projects() - - #Initalize modified project name: - proj_mod_name = None - - #Loop over all repo projects: - for project in projects: + #++++++++++++++++++++++++++++++++++++++++++++++ + #Attempt to close all referenced issues and PRs + #++++++++++++++++++++++++++++++++++++++++++++++ - #Pull-out columns from each project: - proj_columns = project.get_columns() - - #Loop over columns: - for column in proj_columns: - - #check if column name is "Completed tags" - if column.name == "Completed tags": - #If so, then extract cards: - cards = column.get_cards() - - #Loop over cards: - for card in cards: - #Extract card content: - card_content = card.get_content() - - #Next, check if card number exists and matches merged PR number: - if card_content is not None and card_content.number == pr_num: - #If so, and if Project name is None, then set string: - if proj_mod_name is None: - proj_mod_name = project.name - #Break out of card loop: - break - - #If already set, then somehow merged PR is in two different projects, - #which is not what this script is expecting, so just exit: - endmsg = "Merged Pull Request found in two different projects, so script will do nothing." - end_script(endmsg) - - #Print project name associated with merged PR: - print(f"merged PR project name: {proj_mod_name}") - - #++++++++++++++++++++++++++++++++++++++++ - #Extract repo project "To do" card issues - #++++++++++++++++++++++++++++++++++++++++ - - #Initalize issue counting dictionary: - proj_issues_count = {} - - #Initalize issue id to project card id dictionary: - proj_issue_card_ids = {} - - #Initialize list for issues that have already been closed: - already_closed_issues = [] - - #Loop over all repo projects: - for project in projects: - - #Next, pull-out columns from each project: - proj_columns = project.get_columns() - - #Loop over columns: - for column in proj_columns: - #Check if column name is "To do" - if column.name == "To do": - #If so, then extract cards: - cards = column.get_cards() - - #Loop over cards: - for card in cards: - #Extract card content: - card_content = card.get_content() - - #Next, check if card issue number matches any of the "close" issue numbers from the PR: - if card_content is not None and card_content.number in close_issues: - - #If so, then check if issue number is already in proj_issues_count: - if card_content.number in proj_issues_count: - #Add one to project issue counter: - proj_issues_count[card_content.number] += 1 - - #Also add issue id and card id to id dictionary used for card move, if in relevant project: - if project.name == proj_mod_name: - proj_issue_card_ids[card_content.number] = card.id - - else: - #If not, then append to project issues count dictionary: - proj_issues_count[card_content.number] = 1 - - #Also add issue id and card id to id dictionary used for card move, if in relevant project: - if project.name == proj_mod_name: - proj_issue_card_ids[card_content.number] = card.id - - #Otherwise, check if column name matches "closed issues" column: - elif column.name == "closed issues" and project.name == proj_mod_name: - #Save column id: - column_target_id = column.id - - #Extract cards: - closed_cards = column.get_cards() - - #Loop over cards: - for closed_card in closed_cards: - #Extract card content: - closed_card_content = closed_card.get_content() - - #Check if card issue number matches any of the "close" issue numbers from the PR: - if closed_card_content is not None and closed_card_content.number in close_issues: - #If issue number matches, then it likely means the same - #commit message or issue number reference was used in multiple - #pushes to the same repo (e.g., for a PR and then a tag). Thus - #the issue should be marked as "already closed": - already_closed_issues.append(closed_card_content.number) - - #Remove all issues from issue dictionary that are "already closed": - for already_closed_issue_num in already_closed_issues: - if already_closed_issue_num in proj_issues_count: - proj_issues_count.pop(already_closed_issue_num) - - #If no project cards are found that match the issue, then exit script: - if not proj_issues_count: - endmsg = "No project cards match the issue being closed, so the script will do nothing." - end_script(endmsg) - - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Check if the number of "To-do" project cards matches the total number - #of merged PRs for each 'close' issue. - # - #Then, close all issues for which project cards equals merged PRs - # - #If not, then simply move the project card to the relevant project's - #"closed issues" column. - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - #Loop over project issues and counts that have been "closed" by merged PR: - for issue_num, issue_count in proj_issues_count.items(): - - #If issue count is just one, then close issue: - if issue_count == 1: - #Extract github issue object: - cam_issue = cam_repo.get_issue(number=issue_num) - #Close issue: - cam_issue.edit(state='closed') - print(f"Issue #{issue_num} has been closed.") - else: - #Extract card id from id dictionary: - if issue_num in proj_issue_card_ids: - card_id = proj_issue_card_ids[issue_num] - else: - #If issue isn't in dictionary, then it means the issue - #number was never found in the "To do" column, which - #likely means the user either referenced the wrong - #issue number, or the issue was never assigned to the - #project. Warn user and then exit with a non-zero - #error so that the Action fails: - endmsg = 'Issue #{} was not found in the "To Do" Column of the "{}" project.\n' \ - 'Either the wrong issue number was referenced, or the issue was never ' \ - 'attached to the project.'.format(issue_num, proj_mod_name) - print(endmsg) - sys.exit(1) - - #Then move the card on the relevant project page to the "closed issues" column: - project_card_move(token.strip(), column_target_id, card_id) - - #++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Finally, close all Pull Requests in "close_pulls" list: - #++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #Loop over referenced issues: + for issue_num in close_issues: + #Extract github issue object: + cam_issue = cam_repo.get_issue(number=issue_num) + #Close issue: + cam_issue.edit(state='closed') + print(f"Issue #{issue_num} has been closed.") + #Loop over referenced PRs: for pull_num in close_pulls: #Extract Pull request object: cam_pull = cam_repo.get_pull(number=pull_num) diff --git a/.github/workflows/branch_push_workflow.yml b/.github/workflows/branch_push_workflow.yml index 94f4414a..d06af346 100644 --- a/.github/workflows/branch_push_workflow.yml +++ b/.github/workflows/branch_push_workflow.yml @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest steps: # Acquire github action routines - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Acquire specific version of python - - name: Set up Python 3.10 - uses: actions/setup-python@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 with: - python-version: '3.10' # Semantic version range syntax or exact version of a Python version + python-version: '3.11' # Semantic version range syntax or exact version of a Python version # Install required python packages - name: Install dependencies run: | From ba0a9661aa8d7fcfb6f0a68e53ca2eab1b687a46 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 20 Aug 2024 13:40:02 -0600 Subject: [PATCH 2/6] Address reviewer comments. --- .github/scripts/branch_pr_issue_closer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index fc4a7087..10a0211f 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -140,7 +140,7 @@ def _main_prog(): #If pull request has not been merged, then exit script: if not merged_pull.merged: - endmsg = f"Pull request associated with commit:\n{trigger_sha}\n" + endmsg = f"Pull request #{pr_num} associated with commit:\n{trigger_sha}\n" endmsg += "was not actually merged, so the script will not close anything." end_script(endmsg) @@ -156,7 +156,7 @@ def _main_prog(): #If PR was to default branch, then exit script (as github will handle it automatically): if merged_branch == default_branch: - endmsg = "Pull request was merged into default repo branch. " + endmsg = f"Pull request #{pr_num} was merged into default repo branch. " endmsg += "Thus issue is closed automatically" end_script(endmsg) @@ -201,7 +201,8 @@ def _main_prog(): #If at least one keyword is found, then determine location of every keyword instance: word_matches = keyword_pattern.finditer(pr_msg_lower) else: - endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." + endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " + endmsg += "Thus there are no issues to close." end_script(endmsg) #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From 669dd987ca48f5fbbc442fa1cb7e977e6241f168 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 20 Aug 2024 15:08:35 -0600 Subject: [PATCH 3/6] Add comment explaining try/except purpose. --- .github/scripts/branch_pr_issue_closer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 10a0211f..0f17bb38 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -237,7 +237,9 @@ def _main_prog(): #First try assuming the string is just a number issue_num = int(first_word[1:]) #ignore "#" symbol except ValueError: - #If not, then ignore last letter: + #If not, then ignore the last character, in case the user + #included punctutation (i.e. a space, comma, or period) + #after the PR number: try: issue_num = int(first_word[1:-1]) except ValueError: From eb4e376749e50cad650ad90ecc37a01555a0baaa Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:16:33 -0600 Subject: [PATCH 4/6] Implement new regex patterns suggested by Michael W. plus code clean-up. --- .github/scripts/branch_pr_issue_closer.py | 86 ++++++++++------------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 0f17bb38..a2a45911 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -26,6 +26,25 @@ from github import Github from github import Auth +############### +#REGEX PATTERNS +############### + +#Issue-closing Keywords are: +#close, closes, closed +#fix, fixes, fixed +#resolve, resolves, resolved + +#Create relevant regex patterns: +_CLOSE_KEY = r'close[sd]?' +_FIX_KEY = r'fix(e[sd])?' +_RESOLVE_KEY = r'resolve[sd]?' +_KEYWORDS = rf'{_CLOSE_KEY}|{_FIX_KEY}|{_RESOLVE_KEY}' +_KEYWORDS_CAPTURE_GROUP = rf'(?P{_KEYWORDS})' +_ID_NUMBER = r'\d+' +_ID_CAPTURE_GROUP = rf'(?P{_ID_NUMBER})' +_LINKED_ISSUE_PATTERN = rf'{_KEYWORDS_CAPTURE_GROUP}\s*#{_ID_CAPTURE_GROUP}' + ################# #HELPER FUNCTIONS ################# @@ -184,36 +203,25 @@ def _main_prog(): #Check if one of the keywords exists in PR message #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Keywords are: - #close, closes, closed - #fix, fixes, fixed - #resolve, resolves, resolved - - #Create regex pattern to find keywords: - keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') + #Compile regex patterns into object: + keyword_pattern = re.compile(_LINKED_ISSUE_PATTERN) #Extract (lower case) Pull Request message: pr_msg_lower = merged_pull.body.lower() - #search for at least one keyword: - word_matches = [] - if keyword_pattern.search(pr_msg_lower) is not None: - #If at least one keyword is found, then determine location of every keyword instance: - word_matches = keyword_pattern.finditer(pr_msg_lower) - else: + #End script if no keywords found: + if keyword_pattern.search(pr_msg_lower) is None: endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " endmsg += "Thus there are no issues to close." end_script(endmsg) + #search for at least one keyword in PR message: + word_matches = keyword_pattern.finditer(pr_msg_lower, re.IGNORECASE) + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #Extract issue and PR numbers associated with found keywords in merged PR message #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #create issue pattern ("the number symbol {#} + a number"), - #which ends with either a space, a comma, a period, or - #the end of the string itself: - issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') - #Create new "close" issues list: close_issues = [] @@ -223,38 +231,16 @@ def _main_prog(): #Search text right after keywords for possible issue numbers: for match in word_matches: - #create temporary string starting at end of match: - tmp_msg_str = pr_msg_lower[match.end():] - - #Check if first word matches issue pattern: - if issue_pattern.match(tmp_msg_str) is not None: - - #If so, then look for an issue number immediately following - first_word = tmp_msg_str.split()[0] - - #Extract issue number from first word: - try: - #First try assuming the string is just a number - issue_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If not, then ignore the last character, in case the user - #included punctutation (i.e. a space, comma, or period) - #after the PR number: - try: - issue_num = int(first_word[1:-1]) - except ValueError: - #If ignoring the first and last letter doesn't work, - #then the match was likely a false positive, - #so set the issue number to one that will never be found: - issue_num = -9999 - - #Check if number is actually for a PR (as opposed to an issue): - if issue_num in open_pulls: - #Add PR number to "close pulls" list: - close_pulls.append(issue_num) - elif issue_num in open_issues: - #If in fact an issue, then add to "close issues" list: - close_issues.append(issue_num) + issue_dict = match.groupdict() + issue_num = int(issue_dict['id'].lstrip('0')) + + #Check if number is actually for a PR (as opposed to an issue): + if issue_num in open_pulls: + #Add PR number to "close pulls" list: + close_pulls.append(issue_num) + elif issue_num in open_issues: + #If in fact an issue, then add to "close issues" list: + close_issues.append(issue_num) #+++END REFERENCED PR LOOP+++ From 81e7d562e019c343974ed72780b0302dcc868a5c Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:29:53 -0600 Subject: [PATCH 5/6] Update comment about keyword match loop. --- .github/scripts/branch_pr_issue_closer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index a2a45911..53f0352d 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -228,7 +228,7 @@ def _main_prog(): #Create new "closed" PR list: close_pulls = [] - #Search text right after keywords for possible issue numbers: + #Go through all matches to pull out PR and issue numbers: for match in word_matches: issue_dict = match.groupdict() From 31f6846af943783aea7cacd2ab4118aeea6830dc Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:42:37 -0600 Subject: [PATCH 6/6] Add comment and URL about Github keywords. --- .github/scripts/branch_pr_issue_closer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 53f0352d..43da6831 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -35,6 +35,12 @@ #fix, fixes, fixed #resolve, resolves, resolved +#The keywords are designed to match +#the keywords that exist in Github +#already for default branches, which +#can be found here: +#https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue + #Create relevant regex patterns: _CLOSE_KEY = r'close[sd]?' _FIX_KEY = r'fix(e[sd])?'