Add workflow to automatically cherry-pick commits to development branches (#6082)
Co-authored-by: Pawel Winogrodzki <pawelwi@microsoft.com>
This commit is contained in:
Родитель
a6bb77e691
Коммит
72f75c44f0
|
@ -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 }}
|
|
@ -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"
|
Загрузка…
Ссылка в новой задаче