Skip to content

Commit

Permalink
Fix commit message options -- Rework Update/Merge Logic (#4)
Browse files Browse the repository at this point in the history
* Update workflow to use debug mode by choice, not required. Update action to include use of dry-run. Change test Yaml to point to special repository for testing

* Test workflow now with dry-run as an option from the workflow action

* pass flag --dry-run instead of bool

* Updating instructions. Fixing usage of Github function merge()

* Update README about test execution and purpose. Try and set output from action to enable validation of test results when action completes with a results output. Update automerge to cleanly handle buckets of PRs to pass/updated, pass/outofdate, and failed/pending scenarios


---------

Co-authored-by: devops <[email protected]>
  • Loading branch information
F-WRunTime and devops authored Mar 16, 2024
1 parent 90a7fa2 commit 915a1bb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 68 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ jobs:
value: ${{fromJson(needs.list.outputs.matrix)}}
steps:
- name: 'Check Automerge Repo to Test'
uses: actions/checkout@v4.0.0
uses: actions/checkout@v4

- name: 'Automerge runtimeverification/${{ matrix.value }}'
uses: ./ # This uses the action in the root directory
id: automerge
with:
org: 'runtimeverification'
repo: ${{ matrix.value }}
token: ${{ secrets.JENKINS_GITHUB_PAT }}
debug: --dry-run

22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Automerge PR Action
# About Automerge Action
This action is intended to be used in tandem with a CI workflow.
This workflow requires a github token with read/write access to all the repositories it will be tracking

Expand Down Expand Up @@ -120,4 +120,24 @@ $(pwd)/../src/automerge.py --org runtimeverification --repo automerger-test --dr

Recommended to first review the actions before running without. Then remove the `--dry-run` flag to run the action.

# Testing
## [test.yaml](.github/workflows/test.yaml)

### Purpose:
- The purpose of the test is to import automerger action.
- Evaluate the test Scenarios of a Live Test Setup and Report back the values

### Usage:
- The test.yaml file is used by the automerger to determine which pull requests to merge and under what conditions.
- It specifies the target repository, the specific states of the pull requests to test against, and the actions to perform.


### Note:
- Results MUST BE MANUALLY VERIFIED BEFORE MERGE
- The test.yaml file should be updated whenever there are changes to the test scenarios or configurations.
- It is important to ensure that the test.yaml file accurately reflects the desired behavior of the automerger.

For more information, please refer to the following resources:
- [Link to the repository rutimeverification/automerger-test](https://github.com/runtimeverification/automerger-test)
- [Link to the live pull requests in the repository](https://github.com/runtimeverification/automerger-test/pulls)

9 changes: 5 additions & 4 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ runs:
- name: Setup Python
uses: actions/[email protected]
with:
python-version: '3.10'
python-version: '3.10.12'

- name: Install dependencies
shell: bash {0}
run: pip install logging PyGithub argparse

- name: 'Check out repo: ${{ inputs.org }}/${{ inputs.repo }}'
uses: actions/checkout@v4.0.0
uses: actions/checkout@v4
with:
token: ${{ inputs.token }}
repository: ${{ inputs.org }}/${{ inputs.repo }}
Expand All @@ -53,5 +53,6 @@ runs:
env:
GITHUB_TOKEN: ${{ inputs.token }}
working-directory: tmp-${{ inputs.repo }}
run: python3 ${{ github.action_path }}/src/automerge.py --org ${{ inputs.org }} --repo ${{ inputs.repo }} ${{ inputs.debug }}

run: |
python3 ${{ github.action_path }}/src/automerge.py --org ${{ inputs.org }} --repo ${{ inputs.repo }} ${{ inputs.debug }}
125 changes: 63 additions & 62 deletions src/automerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
parser.add_argument('--repo', type=str, help='The repository to check.')
parser.add_argument('--org', type=str, help='The GitHub organization to check.')
parser.add_argument('--dry-run', action='store_true', default=False, help='Enable Debug/Dry-Run mode.')
parser.add_argument('--comment', action='store_true', default=False, help='Set Commit Message to Title and URL. Default to using existing PR Title / Body')
args = parser.parse_args()

_LOGGER: Final = logging.getLogger(__name__)
Expand Down Expand Up @@ -41,11 +42,12 @@ def run_git_command(command_args: str) -> subprocess.CompletedProcess:

github = Github(login_or_token=os.environ['GITHUB_TOKEN'])
repo = args.org + "/" + args.repo
repo = github.get_repo(repo)

# 1. Get PRs that are:
# - Open.
open_prs = []
for pr in github.get_repo(repo).get_pulls():
for pr in repo.get_pulls():
if pr.state == 'open':
open_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, open_prs))
Expand Down Expand Up @@ -73,72 +75,71 @@ def run_git_command(command_args: str) -> subprocess.CompletedProcess:
if approved:
automerge_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, automerge_prs))
_LOGGER.info(f' Automerge approved PRs:\n{pr_string}\n')
_LOGGER.info(f'Approved PRs:\n{pr_string}\n')
if not automerge_prs:
_LOGGER.info(' Quitting.')
sys.exit(0)

# 3. Get PRs that are:
# - Open,
# - Labelled as `automerge`,
# - Approved,
# - Up-to-date, and
# - Passing tests.
automerge_up_to_date_prs = []
# 3. Sort PRs into 3 categories
# - Up-to-date and passing
# - Up-to-date and behind
# - Pending/Blocked PRs
up_to_date_passing_prs = []
do_nothing_pending_prs = []
out_of_date_passing_prs = []
for pr in automerge_prs:
is_up_to_date = run_git_command(f'merge-base --is-ancestor {pr.base.sha} {pr.head.sha}').returncode == 0
if pr.mergeable_state == 'clean' and is_up_to_date:
automerge_up_to_date_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, automerge_up_to_date_prs))
_LOGGER.info(f' Automerge approved up-to-date PRs:\n{pr_string}\n')

# 4. Get PRs that are:
# - Open,
# - Labelled as `automerge`,
# - Approved, and
# - Up-to-date.
# If so, merge
while automerge_up_to_date_prs:
pr = automerge_up_to_date_prs[0]
_LOGGER.info(f' Merging PR:\n{pr_to_display_string(pr)}\n')
base_branch = repo.get_branch(pr.base.ref)
if base_branch.protected:
required_status_checks = base_branch.get_required_status_checks()
latest_commit = pr.get_commits().reversed[0]
latest_commit_checks = {check_run.name: check_run for check_run in latest_commit.get_check_runs()}
all_checks_passed = True
for required_check in required_status_checks.contexts:
if required_check not in latest_commit_checks:
print(f"Required check {required_check} is missing in the latest commit.")
all_checks_passed = False
else:
check_run = latest_commit_checks[required_check]
if check_run.conclusion == 'success':
print(f"Required check {required_check} passed on PR#{pr.number}")
else:
print(f"Required check {required_check} failed or is pending on PR#{pr.number}")
all_checks_passed = False
commit = [c for c in pr.get_commits() if c.sha == pr.head.sha][0]
combined_status = commit.get_combined_status().state
if pr.mergeable_state == 'clean' and all_checks_passed:
up_to_date_passing_prs.append(pr)
elif pr.mergeable_state == 'behind' or pr.mergeable_state == 'blocked':
if all_checks_passed:
out_of_date_passing_prs.append(pr)
else:
do_nothing_pending_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, up_to_date_passing_prs))
_LOGGER.info(f' Automerge Approved Up-to-Date PRs:\n{pr_string}\n')
pr_string = '\n'.join(map(pr_to_display_string, out_of_date_passing_prs))
_LOGGER.info(f' Update Out-of-Date Passing PRs:\n{pr_string}\n')
pr_string = '\n'.join(map(pr_to_display_string, do_nothing_pending_prs))
_LOGGER.info(f' Do Nothing Pending/Failing PRs:\n{pr_string}\n')

while up_to_date_passing_prs:
pr = up_to_date_passing_prs[0]
if args.dry_run:
_LOGGER.info(f'Would have merged PR:\n{pr_to_display_string(pr)}\n')
_LOGGER.info(f'With Comment: {args.comment}')
_LOGGER.info(f"With Comment Message would be: \nAutomerged: [{pr.title}]({pr.html_url})")
else:
pr.merge(merge_method='squash', commit_message=f'Automerge {pr.html_url}: {pr.title}')
automerge_up_to_date_prs.pop(0)

# 5. Get PRs that are:
# - Open,
# - Labelled as `automerge`,
# - Approved,
# - Up-to-date, and
# - Pending tests.
automerge_up_to_date_pending_prs = []
for pr in automerge_prs:
is_up_to_date = run_git_command(f'merge-base --is-ancestor {pr.base.sha} {pr.head.sha}').returncode == 0
commit = [c for c in pr.get_commits() if c.sha == pr.head.sha][0]
is_failing = commit.get_combined_status().state == 'failure'
if pr.mergeable_state == 'blocked' and is_up_to_date and not is_failing:
print(commit.get_combined_status())
automerge_up_to_date_pending_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, automerge_up_to_date_pending_prs))
_LOGGER.info(f' Waiting on approved up-to-date pending/failing PRs:\n{pr_string}\n')

if args.comment:
pr.merge(merge_method='squash', commit_message=f'Automerged {pr.html_url}: {pr.title}')
else:
pr.merge(merge_method='squash')
up_to_date_passing_prs.pop(0)

while out_of_date_passing_prs:
pr = out_of_date_passing_prs[0]
if args.dry_run:
_LOGGER.info(f'Would have updated PR:\n{pr_to_display_string(pr)}\n')
else:
pr.update_branch()
out_of_date_passing_prs.pop(0)

# 6. Get PRs that are:
# - Open,
# - Labelled as `automerge`,
# - Approved,
# - Out-of-date, and
# - Passing tests.
# If so, update the branch.
automerge_out_of_date_passing_prs = []
for pr in automerge_prs:
if pr.mergeable_state == 'behind':
automerge_out_of_date_passing_prs.append(pr)
pr_string = '\n'.join(map(pr_to_display_string, automerge_out_of_date_passing_prs))
_LOGGER.info(f' Approved out-of-date passing PRs:\n{pr_string}\n')
if automerge_out_of_date_passing_prs:
pr = automerge_out_of_date_passing_prs[0]
_LOGGER.info(f' Updating PR:\n{pr_to_display_string(pr)}\n')
pr.update_branch()
_LOGGER.info('Done.')

0 comments on commit 915a1bb

Please sign in to comment.