Update the README to explain how this project works #3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Send PR Diff Patch | |
on: | |
issue_comment: | |
branches: | |
- "main" | |
- "main-aie" | |
types: [created] | |
pull_request_review: | |
branches: | |
- "main" | |
- "main-aie" | |
types: [submitted] | |
jobs: | |
check-target-branch: | |
runs-on: self-hosted | |
outputs: | |
is_correct_branch: ${{ steps.check-branch.outputs.is_correct_branch }} | |
pr_number: ${{ steps.set-pr-number.outputs.pr_number }} # Output the PR number here for other jobs | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
- name: Set PR Number | |
continue-on-error: true | |
id: set-pr-number | |
run: | | |
if [ "${{ github.event_name }}" == "issue_comment" ]; then | |
echo "::set-output name=pr_number::${{ github.event.issue.number }}" | |
elif [ "${{ github.event_name }}" == "pull_request_review" ]; then | |
echo "::set-output name=pr_number::${{ github.event.pull_request.number }}" | |
fi | |
- name: Retrieve PR Data for Branch Check | |
id: check-branch | |
env: | |
GH_TOKEN: ${{ secrets.GHB_TKN }} | |
run: | | |
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ steps.set-pr-number.outputs.pr_number }}) | |
TARGET_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref') | |
echo "::set-output name=is_correct_branch::$(echo "$TARGET_BRANCH" | grep -E '^(main|main-aie)$')" | |
check-approvals: | |
runs-on: self-hosted | |
needs: check-target-branch | |
if: needs.check-target-branch.outputs.is_correct_branch | |
outputs: | |
all_conditions_met: ${{ steps.approval-check.outputs.all_conditions_met }} | |
first_approver_name: ${{ steps.approval-check.outputs.first_approver_name }} | |
first_approver_login: ${{ steps.approval-check.outputs.first_approver_login }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
- name: Set PR Number | |
continue-on-error: true | |
id: set-pr-number | |
run: | | |
if [ "${{ github.event_name }}" == "issue_comment" ]; then | |
echo "::set-output name=pr_number::${{ github.event.issue.number }}" | |
else | |
echo "::set-output name=pr_number::${{ github.event.pull_request.number }}" | |
fi | |
- name: Retrieve PR Data for Issue Comments | |
if: github.event_name == 'issue_comment' | |
id: retrieve-pr-data | |
env: | |
GH_TOKEN: ${{ secrets.GHB_TKN }} | |
run: | | |
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}) | |
PR_BASE_REF=$(echo "$PR_DATA" | jq -r .base.ref) | |
echo "::set-output name=target_branch::$PR_BASE_REF" | |
- name: Check if all reviewers approved | |
if: (github.event_name == 'issue_comment' && github.event.comment.body == 'approved' && (steps.retrieve-pr-data.outputs.target_branch == 'main' || steps.retrieve-pr-data.outputs.target_branch == 'main-aie')) || | |
(github.event_name == 'pull_request_review' && (github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'main-aie') && github.event.review.state == 'approved') | |
continue-on-error: true | |
id: approval-check | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
sleep 5 | |
PR_NUMBER=${{ steps.set-pr-number.outputs.pr_number }} # Use the output from set-pr-number step here | |
echo "PR Number: $PR_NUMBER" | |
REVIEWS_JSON=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews) | |
APPROVED_REVIEWERS=$(echo "$REVIEWS_JSON" | jq '[.[] | select(.state=="APPROVED") | .user.login] | unique') | |
echo "APPROVED_REVIEWERS are $APPROVED_REVIEWERS" | |
# Check if there is at least one approval | |
if [ $(echo "$APPROVED_REVIEWERS" | jq 'length') -le 0 ]; then | |
#echo "Need one developer approval and review bot approval, but not found." | |
echo "Need one developer approval, but not found." | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: The process of generating and sending the patch email failed. Atleast one developer reviewer approval needed! :x: ." | |
exit 1 | |
fi | |
# Check if developer reviewer approved | |
# FixMe Disable Review bot mandatory approve check to addressing review bot service broken issue | |
if [ $(echo "$APPROVED_REVIEWERS" | jq 'length') -eq 1 ]; then | |
if echo "$APPROVED_REVIEWERS" | jq -r '.[]' | grep -q 'bot'; then | |
echo "Developer Reviewer approvals not found." | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: The process of generating and sending the patch email failed. Review bot approval needed! :x: ." | |
exit 1 | |
fi | |
fi | |
# Fetch the login of the first approver | |
FIRST_APPROVER_LOGIN=$(echo "$REVIEWS_JSON" | jq -r '[.[] | select(.state=="APPROVED")][0].user.login') | |
echo "FIRST APPROVED LOGIN: $FIRST_APPROVER_LOGIN" | |
# Fetch the URL for the first approver's details | |
FIRST_APPROVER_URL=$(echo "$REVIEWS_JSON" | jq -r '[.[] | select(.state=="APPROVED")][0].user.url') | |
echo "FIRST APPROVED URL: $FIRST_APPROVER_URL" | |
sleep 5 | |
# Now, make a curl request to the first approver's URL to fetch their full name | |
FIRST_APPROVER_NAME=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" $FIRST_APPROVER_URL | jq -r '.name') | |
echo "FIRST APPROVED NAME: $FIRST_APPROVER_NAME" | |
sleep 5 | |
DISTINCT_REVIEWERS=$(echo "$REVIEWS_JSON" | jq '[.[] | .user.login] | unique') | |
echo "TOTAL DISTINCT REVIEWERS: $DISTINCT_REVIEWERS" | |
# Fetch PR's SHA | |
PR_SHA=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER} --jq ".head.sha") | |
echo "PR SHA: $PR_SHA" | |
# Fetch combined status of all checks for the PR's commit | |
sleep 5 | |
COMMIT_STATUS=$(gh api repos/${{ github.repository }}/commits/${PR_SHA}/status --jq ".state") | |
echo "Commit Status: $COMMIT_STATUS" | |
MAX_RETRIES=10 | |
RETRY_COUNT=0 | |
SLEEP_DURATION=40 | |
while [[ "$COMMIT_STATUS" == "pending" && $RETRY_COUNT -lt $MAX_RETRIES ]]; do | |
echo "Checks are still running. Waiting for them to complete..." | |
sleep $SLEEP_DURATION | |
COMMIT_STATUS=$(gh api repos/${{ github.repository }}/commits/${PR_SHA}/status --jq ".state") | |
echo "Commit Status after retry #${RETRY_COUNT} is ${COMMIT_STATUS}" | |
RETRY_COUNT=$((RETRY_COUNT + 1)) | |
done | |
if [[ "$RETRY_COUNT" == "$MAX_RETRIES" ]]; then | |
echo "Max retries reached. Please check the checks manually." | |
gh pr comment $PR_NUMBER --body "[GITHUB ACTIONS] :x: The process of generating and sending the patch email failed. Please ensure all checks are passing before approving. :x: " | |
exit 1 | |
fi | |
# Check if PR is mergeable | |
PR_MERGEABLE=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER} --jq ".mergeable_state") | |
echo "PR Mergeable Status: $PR_MERGEABLE" | |
if [[ "$PR_MERGEABLE" == "behind" || "$PR_MERGEABLE" == "dirty" ]]; then | |
echo "PR has merge conflicts. Source repo is behind the target repo." | |
echo "::set-output name=all_conditions_met::false" | |
if [ "${{ github.event_name }}" == "issue_comment" ]; then | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: This PR is not up-to-date with the destination. Please pull/update! :x: " | |
fi | |
exit 1 | |
fi | |
echo "APPROVED_COUNT $APPROVED_COUNT" | |
echo "TOTAL_REVIEWERS $TOTAL_REVIEWERS" | |
echo "COMMIT_STATUS $COMMIT_STATUS" | |
# Convert arrays to strings for comparison | |
APPROVED_REVIEWERS_STR="${APPROVED_REVIEWERS[*]}" | |
DISTINCT_REVIEWERS_STR="${DISTINCT_REVIEWERS[*]}" | |
echo "APPROVED_REVIEWERS_STR: $APPROVED_REVIEWERS_STR" | |
echo "DISTINCT_REVIEWERS_STR: $DISTINCT_REVIEWERS_STR" | |
# Convert arrays to strings for comparison | |
APPROVED_REVIEWERS_STR="${APPROVED_REVIEWERS[*]}" | |
DISTINCT_REVIEWERS_STR="${DISTINCT_REVIEWERS[*]}" | |
echo "APPROVED_REVIEWERS_STR: $APPROVED_REVIEWERS_STR" | |
echo "DISTINCT_REVIEWERS_STR: $DISTINCT_REVIEWERS_STR" | |
# Check if there is at least one approving review | |
if [[ -n "$APPROVED_REVIEWERS_STR" && "$COMMIT_STATUS" == "success" ]]; then | |
echo "PR CAN BE APPROVED" | |
echo "::set-output name=all_conditions_met::true" | |
echo "::set-output name=first_approver_name::$FIRST_APPROVER_NAME" | |
echo "::set-output name=first_approver_login::$FIRST_APPROVER_LOGIN" | |
elif [[ -z "$APPROVED_REVIEWERS_STR" ]]; then | |
echo "PR CANNOT BE APPROVED" | |
echo "::set-output name=all_conditions_met::false" | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: No reviewers have approved. At least one approving review is required. :x:" | |
elif [[ "$COMMIT_STATUS" != "success" ]]; then | |
echo "PR CANNOT BE APPROVED" | |
echo "::set-output name=all_conditions_met::false" | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: The commit status is not successful. Please ensure all checks are passing before approving. :x:" | |
else | |
echo "PR CANNOT BE APPROVED" | |
echo "::set-output name=all_conditions_met::false" | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: An unknown issue has occurred. Please check the PR conditions and try again. :x:" | |
fi | |
- name: Generate and Send Patch Email | |
continue-on-error: true | |
if: steps.approval-check.outputs.all_conditions_met == 'true' | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
# Extract Information from GitHub Event | |
PR_NUMBER=${{ steps.set-pr-number.outputs.pr_number }} | |
echo "PR Number: $PR_NUMBER" | |
REPO_FULL_NAME=$(jq --raw-output .repository.full_name "$GITHUB_EVENT_PATH") | |
echo "Repository Full Name: $REPO_FULL_NAME" | |
# Fetch PR details using gh API with PR_NUMBER | |
PR_DETAILS=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER) | |
# Extract Issue (PR) Title | |
ISSUE_TITLE=$(echo "$PR_DETAILS" | jq --raw-output .title) | |
echo "Issue Title: $ISSUE_TITLE" | |
# Extract Pull Request URL | |
PULL_REQ_URL=$(echo "$PR_DETAILS" | jq --raw-output .html_url) | |
echo "Pull Request URL: $PULL_REQ_URL" | |
# Get the last review from the pull request | |
LAST_REVIEW=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews -q ".[-1]") | |
APPROVER_NAME=$(echo "$LAST_REVIEW" | jq --raw-output .user.login) | |
echo "Approver Name: $APPROVER_NAME" | |
# Fetch PR Patch | |
# Extract source and target branch names | |
SOURCE_BRANCH=$(echo "$PR_DETAILS" | jq --raw-output .head.ref) | |
TARGET_BRANCH=$(echo "$PR_DETAILS" | jq --raw-output .base.ref) | |
# Clone the target repository | |
git clone https://gitenterprise.xilinx.com/${{ github.repository }}.git target-repo | |
cd target-repo | |
# Fetch PR details using gh API with PR_NUMBER | |
PR_DETAILS=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER) | |
# Extract Pull Request Description | |
PR_DESCRIPTION=$(echo "$PR_DETAILS" | jq -r '.body') | |
echo "Pull Request Description: $PR_DESCRIPTION" | |
# Extract source repository URL and source branch name | |
SOURCE_REPO_URL=$(echo "$PR_DETAILS" | jq --raw-output .head.repo.clone_url) | |
SOURCE_BRANCH=$(echo "$PR_DETAILS" | jq --raw-output .head.ref) | |
TARGET_BRANCH=$(echo "$PR_DETAILS" | jq --raw-output .base.ref) | |
# Checkout the target branch | |
git checkout $TARGET_BRANCH | |
# Fetch the PR's merge commit | |
git fetch origin pull/$PR_NUMBER/merge:pr-merge | |
# Create a diff summary and save it to a file | |
git diff --stat $TARGET_BRANCH pr-merge > ../diff-summary.txt | |
# Create a patch file with the difference between the target branch and the PR's merge commit | |
git diff $TARGET_BRANCH pr-merge > ../pr_1.patch | |
# Move back to the original directory | |
cd .. | |
# Extract all the approved reviewers' logins | |
sleep 5 | |
REVIEWS_JSON=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews) | |
APPROVED_REVIEWERS_LOGINS=$(echo "$REVIEWS_JSON" | jq -r '[.[] | select(.state=="APPROVED") | .user.login] | unique | .[]') | |
for LOGIN in $APPROVED_REVIEWERS_LOGINS; do | |
# bypass the review bot which contain the name "bot" | |
if [[ "$LOGIN" == *bot* ]]; then | |
continue | |
fi | |
EMAIL="${LOGIN}@amd.com" | |
CC_EMAILS_ARRAY+=( "--cc" "${EMAIL}" ) | |
done | |
# Fetch submitter details | |
SUBMITTER_LOGIN=$(echo "$PR_DETAILS" | jq -r ".user.login") | |
echo "Submitter Login: $SUBMITTER_LOGIN" | |
SUBMITTER_DETAILS_URL=$(echo "$PR_DETAILS" | jq -r ".user.url") | |
echo "Submitter Details URL: $SUBMITTER_DETAILS_URL" | |
sleep 5 | |
SUBMITTER_DETAILS=$(curl -H "Authorization: token $GITHUB_TOKEN" -s $SUBMITTER_DETAILS_URL) | |
echo "$SUBMITTER_DETAILS" | |
SUBMITTER_NAME=$(echo "$SUBMITTER_DETAILS" | jq -r ".name // .login") # Use login as a fallback | |
echo "Submitter Name: $SUBMITTER_NAME" | |
# Format Submitter Name, replacing spaces with dots | |
FORMATTED_SUBMITTER_NAME=$(echo "$SUBMITTER_NAME" | tr ' ' '.') | |
# Build submitter email ID | |
SUBMITTER_EMAIL="${FORMATTED_SUBMITTER_NAME}@amd.com" | |
echo "Submitter Email: $SUBMITTER_EMAIL" | |
echo "PR Details: $PR_DETAILS" | |
PR_BODY=$(echo "$PR_DETAILS" | jq -r '.body') | |
echo "PR Body: $PR_BODY" | |
ID_INFOS=$(echo "$PR_BODY" | grep -oiP '\b(cr|pr|jira)-\d+\b' | tr '\n' ',' | sed 's/,$//') | |
echo "ID Infos: $ID_INFOS" | |
if [ -z "$ID_INFOS" ]; then | |
echo "No valid ID number found in PR body" | |
# gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :x: No valid ID number was tagged in the PR body. Please tag a CR, PR or JIRA ID tag before approving. :x:" | |
# exit 1 | |
else | |
echo "ID Infos: $ID_INFOS" | |
fi | |
SIGN_OFF="Signed-off-by: ${SUBMITTER_NAME} <${SUBMITTER_EMAIL}>" | |
# Modify PR Patch with additional details | |
# Insert PR Description at the top of the patch | |
echo -e "From: ${SUBMITTER_NAME} <${SUBMITTER_EMAIL}>\nSubject: [AIENGINE PATCH] ${ISSUE_TITLE}\n" >> pr.patch | |
echo -e "PR Description:\n$PR_DESCRIPTION\n\n" >> pr.patch | |
echo -e "---\nBRANCH: ${TARGET_BRANCH}\n---\n$ID_INFOS\n---\n$SIGN_OFF\n---\nPR URL: $PULL_REQ_URL\n---\n" >> pr.patch | |
cat diff-summary.txt >> pr.patch # Write the contents of diff-summary.txt to pr.patch | |
echo -e "\n" >> pr.patch # Append a newline to pr.patch | |
cat pr_1.patch >> pr.patch # Append the contents of pr_1.patch to pr.patch | |
echo "pr.patch +++++++++++++++++++++++++++++++++++" | |
cat pr.patch | |
echo "pr.patch -----------------------------------" | |
# Store the original git config | |
ORIGINAL_GIT_USER_EMAIL=$(git config --global user.email) | |
ORIGINAL_GIT_USER_NAME=$(git config --global user.name) | |
# Configure git with the new user details | |
git config --global user.email "${SUBMITTER_EMAIL}" | |
git config --global user.name "${SUBMITTER_NAME}" | |
# Send the email with the patch | |
git send-email --confirm=never --force --to "[email protected]" "${CC_EMAILS_ARRAY[@]}" --attach pr.patch | |
# Comment on the PR | |
gh pr comment $PR_NUMBER --body "**[GITHUB ACTIONS]** :white_check_mark: Patch approved! Patch E-Mail/s Sent out! :white_check_mark:" | |
# Restore the original git config | |
git config --global user.email "${ORIGINAL_GIT_USER_EMAIL}" | |
git config --global user.name "${ORIGINAL_GIT_USER_NAME}" | |
- name: Close Pull Request | |
if: steps.approval-check.outputs.all_conditions_met == 'true' # Only run this step if the patch was approved and sent | |
run: | | |
PR_NUMBER=${{ steps.set-pr-number.outputs.pr_number }} | |
echo "Closing PR Number: $PR_NUMBER" | |
gh pr close $PR_NUMBER --repo ${{ github.repository }} | |
echo "Pull Request #$PR_NUMBER has been closed." | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |