Add workflow to automatically cherry-pick commits to development branches (#6082)

Co-authored-by: Pawel Winogrodzki <pawelwi@microsoft.com>
This commit is contained in:
Trung 2023-08-31 11:23:42 -07:00 коммит произвёл GitHub
Родитель a6bb77e691
Коммит 72f75c44f0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 214 добавлений и 0 удалений

129
.github/workflows/cherry-pick.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,129 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# This action cherry-picks CVE fix commits from fast-track branches to corresponding
# development branches for each version of Mariner. By default, the workflow is run
# whenever a CVE fix PR to a fast-track branch is merged. Additional target branches
# for cherry-pick can be specified by adding a comment to the original PR with the
# following syntax:
# /cherry-pick <target-branch-1> <target-branch-2>
# Note the list of target branches is space-separated, and the commenter must be a
# member or owner of the repository for the action to work.
# After cherry-pick succeeds for a branch, a new PR will be created to merge the commit
# to that target branch, and a label will be added to the original PR to indicate that
# the commit has been cherry-picked to that branch.
name: Cherry pick commits from PR
on:
pull_request:
types:
- closed
branches:
- 'fasttrack/*'
permissions:
contents: read
pull-requests: read
defaults:
run:
shell: bash
env:
CHERRY_PICK_BRANCH_MAPPING: '{"fasttrack/2.0": "main"}'
jobs:
# Scans the PR for cherry pick comments, in addition to the default cherry pick target
collect_target_branches:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
name: Collect target branches from Pull Request
outputs:
cherry_pick_target_branches: ${{ steps.target_branches.outputs.cherry_pick_target_branches }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
# Collect target branches in a list. The list includes:
# - default target branch
# - any branch specified in PR comments with the syntax
# /cherry-pick <branch-1> <branch-2>
# The final list will only contain unique branches.
# The jq filter does the following from a JSON list of comments
# [{"author": "a", "authorAssociation": "MEMBER", "body": "hello"},
# {"author": "c", "authorAssociation": "CONTRIBUTOR", "body": "hi"},
# {"author": "a", "authorAssociation": "MEMBER", "body": "/cherry-pick a b c"},
# {"author": "b", "authorAssociation": "MEMBER", "body": "/cherry-pick a b"}]
# - Select only comments where the author is a member or owner of the repository
# - Collect all comments body to a list of string
# - Select only comments that starts with "/cherry-pick "
# - Remove the "/cherry-pick " prefix from each comment
# ["a b c", "a b"]
# - Split the remaining content of each comment by whitespace into a list
# [["a", "b", "c"], ["a", "b"]]
# - Flatten the list
# ["a", "b", "c", "a", "b"]
# - Add the default target branch to the list
# ["a", "b", "c", "a", "b", "default-target"]
# - Remove all duplicate values
# ["a", "b", "c", "default-target"]
- name: Create list of target branches
id: target_branches
run: |
cherry_pick_branches_json=$(gh pr view ${{ github.event.number }} \
--repo ${{ github.repository }} \
--json comments \
--jq '.comments
| map(select(.authorAssociation == "MEMBER" or .authorAssociation == "OWNER")
| .body
| select(startswith("/cherry-pick "))
| ltrimstr("/cherry-pick ")
| split(" "))
| flatten
| . += ["${{ fromJSON(env.CHERRY_PICK_BRANCH_MAPPING)[github.base_ref] }}"]
| unique')
branches_list=$(echo $cherry_pick_branches_json | jq -r @sh | tr -d "'")
echo "cherry_pick_target_branches=$branches_list" >> $GITHUB_OUTPUT
# Actual cherry-pick work for each target branch
cherry_pick_commit:
needs: collect_target_branches
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
name: Cherry pick
steps:
- name: Workflow trigger checkout
uses: actions/checkout@v3
with:
ref: ${{ github.base_ref }}
fetch-depth: 0
- name: Configure local git repo
run: |
git config --local user.email "cblmargh@microsoft.com"
git config --local user.name "CBL-Mariner Servicing Account"
# Run cherry-pick for each target branch and create new PR for it. If cherry-pick fails,
# output conflicts to the action logs and notify in the original PR. We want to run the
# cherry-pick script for all target branch, even if one fails. This step will succeed if
# all cherry-picks succeed.
- name: Run cherry-pick action
run: |
cp toolkit/scripts/cherry_pick.sh ${{ runner.temp }}/cherry_pick.sh
for target_branch in ${{ needs.collect_target_branches.outputs.cherry_pick_target_branches }}; do
if ! ${{ runner.temp }}/cherry_pick.sh \
-r "${{ github.repository }}" \
-p "${{ github.event.pull_request.number }}" \
-t "$target_branch" \
-w $RUN_URL; then
failed=1
fi
echo "================================================================================"
done
if [[ $failed == 1 ]]; then
echo "Cherry-pick failed for at least 1 target branch"
exit 1
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

85
toolkit/scripts/cherry_pick.sh Executable file
Просмотреть файл

@ -0,0 +1,85 @@
#!/bin/bash
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
set -e
function help {
echo "Cherry-pick commit to a specific branch and create Github PR"
echo "Usage:"
echo "[MANDATORY] -r REPOSITORY -> name of the repository in the format <owner>/<repo-name>"
echo "[MANDATORY] -p PR_NUMBER -> number of the PR to cherry-pick commit from"
echo "[MANDATORY] -t TARGET_BRANCH -> target branch to cherry-pick commit to"
echo "[MANDATORY] -w WORKFLOW_RUN_URL -> URL of the workflow run that triggers this script"
}
function cherry_pick_from_pull_request {
repo=$1
pr_number=$2
target_branch=$3
workflow_run_url=$4
# Collect merge commit hash and title from the original PR
commit_hash=$(gh pr view $pr_number --repo $repo --json mergeCommit --jq '.mergeCommit.oid')
original_pr_title=$(gh pr view $pr_number --repo $repo --json title --jq '.title')
tmp_branch="cherry-pick-$target_branch-$commit_hash"
echo "Cherry picking commit ($commit_hash) to target branch ($target_branch)"
# reset the current working tree to clean state
git reset --hard
git clean -df
git checkout -- .
# create a temporary branch from target branch to perform cherry pick
git fetch --all
git checkout -b "$tmp_branch" origin/"$target_branch"
if ! git cherry-pick -x "$commit_hash"; then
echo "Cherry pick failed. Displaying conflicts below"
git diff --diff-filter=U
gh pr comment "$pr_number" \
--repo "$repo" \
--body "Cherry-pick failed for branch \`$target_branch\`. See run logs for more details: $workflow_run_url"
exit 1
fi
echo "Pushing to remote"
git push -u origin "$tmp_branch"
echo "Done pushing to remote"
new_pr=$(gh pr create \
-B "$target_branch" \
-H "$tmp_branch" \
--repo "$repo" \
--title "[AUTO-CHERRY-PICK] $original_pr_title - branch $target_branch" \
--body "This is an auto-generated pull request to cherry pick commit $commit_hash to $target_branch. Original PR: #$pr_number")
gh pr comment "$pr_number" \
--repo "$repo" \
--body "Cherry-pick succeeded for branch \`$target_branch\`. See pull request #$new_pr"
gh pr edit "$pr_number" \
--repo "$repo" \
--add-label "cherry_pick-$target_branch"
}
repo=
pr_number=
target_branch=
workflow_run_url=
while getopts "r:p:t:w:" opt; do
case ${opt} in
r ) repo="$OPTARG" ;;
p ) pr_number="$OPTARG" ;;
t ) target_branch="$OPTARG" ;;
w ) workflow_run_url="${OPTARG,,}" ;;
? ) echo -e "ERROR: Invalid option.\n\n"; help; exit 1 ;;
esac
done
if [[ -z "$repo" ]] || [[ -z "$pr_number" ]] || [[ -z "$target_branch" ]] || [[ -z "$workflow_run_url" ]] ; then
echo "Error: missing required arguments"
help
exit 1
fi
cherry_pick_from_pull_request "$repo" "$pr_number" "$target_branch" "$workflow_run_url"