diff --git a/.github/workflows/auto-backport.yml b/.github/workflows/auto-backport.yml deleted file mode 100644 index b0bfc4bf2387f5d761caea0c6f4bbf6a4a5f1b20..0000000000000000000000000000000000000000 --- a/.github/workflows/auto-backport.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Creates a pull request with the latest release branch as a target with a cherry-picked commit if an associated pull request has `backport` label -name: AutoBackport - -on: - push: - branches: - - master - -jobs: - pr_info: - name: Check if the commit should be backported - runs-on: ubuntu-latest - outputs: - title: ${{ fromJson(steps.collect_pr_info.outputs.result).title }} - number: ${{ fromJson(steps.collect_pr_info.outputs.result).pullRequestNumber }} - author: ${{ fromJson(steps.collect_pr_info.outputs.result).author }} - should_backport: ${{ fromJson(steps.collect_pr_info.outputs.result).hasBackportLabel }} - steps: - - uses: actions/github-script@v4 - id: collect_pr_info - with: - script: | - const commitMessage = context.payload.commits[0].message; - const pullRequestNumbers = Array.from(commitMessage.matchAll(/\(#(.*?)\)/g)) - - if (pullRequestNumbers.length === 0) { - return; - } - - if (pullRequestNumbers > 1) { - throw "Multiple PRs are associated with this commit"; - } - - const pullRequestNumber = pullRequestNumbers[0][1]; - - const { data } = await github.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pullRequestNumber - }); - - const hasBackportLabel = data.labels.some((label) => label.name === 'backport'); - const { title, user } = data - - console.log(`PR #${pullRequestNumber}: "${title}" hasBackportLabel=${hasBackportLabel}`) - - return { - author: user.login, - pullRequestNumber, - title: data.title, - hasBackportLabel - } - - get_latest_release_branch: - name: Get latest release branch - runs-on: ubuntu-latest - outputs: - branch_name: ${{ steps.get_branch_name.outputs.result }} - steps: - - uses: actions/github-script@v4 - id: get_branch_name - with: - result-encoding: string - script: | - const releaseBranches = await github.git.listMatchingRefs({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: "heads/release-x.", - }); - - const getVersionFromBranch = branch => { - const match = branch.match(/release-x\.(.*?)\.x/); - return match && parseInt(match[1]) - }; - const latestReleaseBranch = releaseBranches.data - .filter(branch => getVersionFromBranch(branch.ref) !== null) - .reduce((prev, current) => getVersionFromBranch(prev.ref) > getVersionFromBranch(current.ref) ? prev : current); - const latestReleaseBranchName = latestReleaseBranch.ref.replace(/^refs\/heads\//, ""); - - console.log(`Latest release branch: ${latestReleaseBranchName}`) - - return latestReleaseBranchName; - - create_backport_pull_request: - runs-on: ubuntu-latest - name: Create a backport PR with the commit - needs: [pr_info, get_latest_release_branch] - if: ${{ needs.pr_info.outputs.should_backport == 'true' }} - env: - RELEASE_BRANCH: ${{ needs.get_latest_release_branch.outputs.branch_name }} - ORIGINAL_PR_TITLE: ${{ needs.pr_info.outputs.title }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v2 - name: Checkout - with: - fetch-depth: 0 - - run: | - git config --global user.email "metabase-github-automation@metabase.com" - git config --global user.name "$GITHUB_ACTOR" - - PR_BRANCH="backport-$GITHUB_SHA" - - git fetch --all - git checkout -b "${PR_BRANCH}" origin/"${RELEASE_BRANCH}" - git cherry-pick "${GITHUB_SHA}" - git push -u origin "${PR_BRANCH}" - - hub pull-request -b "${RELEASE_BRANCH}" -h "${PR_BRANCH}" -l "auto-created-backport" -a "${GITHUB_ACTOR}" -m "backported \"${ORIGINAL_PR_TITLE}\"" - - notify_when_failed: - runs-on: ubuntu-latest - name: Notify about failure - needs: [pr_info, create_backport_pull_request] - if: ${{ failure() }} - steps: - - uses: actions/github-script@v4 - with: - script: | - github.issues.createComment({ - issue_number: ${{ needs.pr_info.outputs.number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: '@${{ needs.pr_info.outputs.author }} could not automatically create a backport PR 😩' - }) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000000000000000000000000000000000000..d0c6466bd219f521efc1f0690ca9020cb7250fac --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,131 @@ +# Cherry-picks commits from current branch to a specified one in a command "@metabase-bot backport release-x.40.x" +name: Backport + +on: + issue_comment: + types: [created] + +jobs: + create_pull_request: + name: Creates a pull request + if: contains(github.event.comment.body, '@metabase-bot backport') + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v4 + id: branch_info + with: + script: | + // Example: @metabase-bot backport release-x.40.x + const [_botName, _command, targetBranch] = context.payload.comment.body.split(" "); + console.log(`Target branch is ${targetBranch}`); + + const { data: originalPullRequest } = await github.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number, + }); + + const { data: commits } = await github.pulls.listCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number, + }); + + const targetRef = await github.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${targetBranch}`, + }); + + const backportBranch = `backport-${originalPullRequest.head.ref}` + + try { + await github.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${backportBranch}`, + }); + } catch(e) { + if (e.status === 404) { + await github.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${backportBranch}`, + sha: targetRef.data.object.sha, + }); + } + } + + return { + backportBranch, + targetBranch, + originalPullRequest, + startSha: commits[0].sha, + endSha: commits[commits.length - 1].sha + } + - uses: actions/checkout@v2 + name: Cherry-pick commits and create PR + with: + fetch-depth: 0 + - run: | + git config --global user.email "metabase-github-automation@metabase.com" + git config --global user.name "$GITHUB_ACTOR" + + git fetch --all + + git checkout "${BACKPORT_BRANCH}" + git reset --hard origin/${TARGET_BRANCH} + + if [[ -z $(git ls-remote --heads origin ${ORIGINAL_HEAD_REF}) ]]; then + echo "PR has been merged, searching for a squashed commit in the base branch" + echo "searching for a commit in a ${ORIGINAL_BASE_REF} that contains pull request number ${ORIGINAL_PULL_REQUEST_NUMBER}" + SQUASHED_COMMIT=$(env -i git log ${ORIGINAL_BASE_REF} --grep="(#${ORIGINAL_PULL_REQUEST_NUMBER})" --format="%H") + echo "found commit ${SQUASHED_COMMIT}" + git cherry-pick ${SQUASHED_COMMIT} + else + echo "PR has not been merged, copying all commits" + git cherry-pick ${ORIGINAL_BASE_SHA}..${ORIGINAL_HEAD_SHA} + fi + + git push origin "${BACKPORT_BRANCH}" --force-with-lease + + if [[ $(hub pr list -b "${TARGET_BRANCH}" -h "${BACKPORT_BRANCH}" -s "open") ]]; then + echo "PR already exists" + else + hub pull-request -b "${TARGET_BRANCH}" -h "${BACKPORT_BRANCH}" -l "auto-backported" -a "${GITHUB_ACTOR}" -F- <<<"🤖 backported \"${ORIGINAL_TITLE}\" + + #${ORIGINAL_PULL_REQUEST_NUMBER}" + echo "New PR has been created" + fi + env: + ORIGINAL_TITLE: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.title }} + ORIGINAL_BASE_REF: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.base.ref }} + ORIGINAL_BASE_SHA: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.base.sha }} + ORIGINAL_HEAD_REF: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.head.ref }} + ORIGINAL_HEAD_SHA: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.head.sha }} + ORIGINAL_PULL_REQUEST_NUMBER: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.number }} + TARGET_BRANCH: ${{ fromJson(steps.branch_info.outputs.result).targetBranch }} + BACKPORT_BRANCH: ${{ fromJson(steps.branch_info.outputs.result).backportBranch }} + START_SHA: ${{ fromJson(steps.branch_info.outputs.result).startSha }} + END_SHA: ${{ fromJson(steps.branch_info.outputs.result).endSha }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + notify_when_failed: + runs-on: ubuntu-latest + name: Notify about failure + needs: create_pull_request + if: ${{ failure() }} + steps: + - uses: actions/github-script@v4 + with: + script: | + const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID} = process.env; + const runUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}` + const author = context.payload.comment.user.login; + + github.issues.createComment({ + issue_number: context.payload.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `@${author} could not automatically a backport PR 😩 [[Logs]](${runUrl})` + })