-
Notifications
You must be signed in to change notification settings - Fork 18
160 lines (141 loc) · 7.15 KB
/
approve-and-merge.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
name: approve-and-merge
on:
pull_request:
branches: [main, dotnet-vnext]
env:
REVIEWER_LOGIN: ${{ vars.REVIEWER_USER_NAME }}
permissions:
contents: read
jobs:
review-pull-request:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == vars.UPDATER_COMMIT_USER_NAME }}
steps:
- name: Generate GitHub application token
id: generate-application-token
uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 # v3.0.0
with:
application_id: ${{ secrets.REVIEWER_APPLICATION_ID }}
application_private_key: ${{ secrets.REVIEWER_APPLICATION_PRIVATE_KEY }}
permissions: "contents:write, pull_requests:write"
- name: Install powershell-yaml
shell: pwsh
run: Install-Module -Name powershell-yaml -Force -MaximumVersion "0.4.7"
- name: Check which dependencies were updated
id: check-dependencies
env:
# This list of trusted package prefixes needs to stay in sync with include-nuget-packages in the update-dotnet-sdk workflow.
INCLUDE_NUGET_PACKAGES: "Microsoft.AspNetCore.,Microsoft.NET.Test.Sdk"
GH_TOKEN: ${{ steps.generate-application-token.outputs.token }}
shell: pwsh
run: |
# Replicate the logic in the dependabot/fetch-metadata action.
# See https://github.com/dependabot/fetch-metadata/blob/aea2135c95039f05c64436f1d14638c300e10b2b/src/dependabot/update_metadata.ts#L29-L68.
# Query the GitHub API to get the commits in the pull request.
$commits = gh api `
/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/commits `
--jq '.[] | { author: .author.login, message: .commit.message }' | ConvertFrom-Json
# We should only approve pull requests that only contain commits from
# the GitHub user we expected and only commits that contain the metadata
# we need to determine what dependencies were updated by the other workflow.
$expectedUser = "${{ vars.UPDATER_COMMIT_USER_NAME }}"
$onlyDependencyUpdates = $True
$onlyChangesFromUser = $True
$dependencies = @()
foreach ($commit in $commits) {
if ($commit.Author -ne $expectedUser) {
# Some other commit is in the pull request
$onlyChangesFromUser = $False
}
# Extract the YAML metadata block from the commit message.
$match = [Regex]::Match($commit.Message, '(?m)^-{3}\s(?<dependencies>[\S|\s]*?)\s^\.{3}$')
if ($match.Success -eq $True) {
# Extract the names and update type from each dependency.
$metadata = ($match.Value | ConvertFrom-Yaml -Ordered)
$updates = $metadata["updated-dependencies"]
if ($updates) {
foreach ($update in $updates) {
$dependencies += @{
Name = $update['dependency-name'];
Type = $update['update-type'];
}
}
}
}
else {
# The pull request contains a commit that we didn't expect as the metadata is missing.
$onlyDependencyUpdates = $False
}
}
# Did we find at least one dependency?
$isPatch = $dependencies.Length -gt 0
$onlyTrusted = $dependencies.Length -gt 0
$trustedPackages = $env:INCLUDE_NUGET_PACKAGES.Split(',')
foreach ($dependency in $dependencies) {
$isPatch = $isPatch -And $dependency.Type -eq "version-update:semver-patch"
$onlyTrusted = $onlyTrusted -And
(
($dependency.Name -eq "Microsoft.NET.Sdk") -Or
(($trustedPackages | Where-Object { $dependency.Name.StartsWith($_) }).Count -gt 0)
)
}
# We only trust the pull request to approve and auto-merge it
# if it only contains commits which change the .NET SDK and
# Microsoft-published NuGet packages that were made by the GitHub
# login we expect to make those changes in the other workflow.
$isTrusted = (($onlyTrusted -And $isPatch) -And $onlyChangesFromUser) -And $onlyDependencyUpdates
"is-trusted-update=$isTrusted" >> $env:GITHUB_OUTPUT
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
# As long as it's not already approved, approve the pull request and enable auto-merge.
# Our CI tests coupled with required statuses should ensure that the changes compile
# and that the application is still functional after the update; any bug that might be
# introduced by the update should be caught by the tests. If that happens, the build
# workflow will fail and the preconditions for the auto-merge to happen won't be met.
- name: Approve pull request and enable auto-merge
if: ${{ steps.check-dependencies.outputs.is-trusted-update == 'true' }}
env:
GH_TOKEN: ${{ steps.generate-application-token.outputs.token }}
PR_URL: ${{ github.event.pull_request.html_url }}
shell: pwsh
run: |
$approvals = gh api /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews | ConvertFrom-Json
$approvals = $approvals | Where-Object { $_.user.login -eq $env:REVIEWER_LOGIN }
$approvals = $approvals | Where-Object { $_.state -eq "APPROVED" }
if ($approvals.Length -eq 0) {
gh pr checkout "$env:PR_URL"
gh pr review --approve "$env:PR_URL"
gh pr merge --auto --squash "$env:PR_URL"
}
else {
Write-Host "PR already approved.";
}
# If something was present in the pull request that isn't expected, then disable
# auto-merge so that a human is required to look at the pull request and make a
# decision to merge it or not. This is to prevent the pull request from being merged
# automatically if there's an unexpected change introduced. Any existing review
# approvals that were made by the bot are also dismissed so human approval is required.
- name: Disable auto-merge and dismiss approvals
if: ${{ steps.check-dependencies.outputs.is-trusted-update != 'true' }}
env:
GH_TOKEN: ${{ steps.generate-application-token.outputs.token }}
PR_URL: ${{ github.event.pull_request.html_url }}
shell: pwsh
run: |
$approvals = gh api /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews | ConvertFrom-Json
$approvals = $approvals | Where-Object { $_.user.login -eq $env:REVIEWER_LOGIN }
$approvals = $approvals | Where-Object { $_.state -eq "APPROVED" }
if ($approvals.Length -gt 0) {
gh pr checkout "$env:PR_URL"
gh pr merge --disable-auto "$env:PR_URL"
foreach ($approval in $approvals) {
gh api `
--method PUT `
/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/$($approval.id)/dismissals `
-f message='Cannot approve as other changes have been introduced.' `
-f event='DISMISS'
}
}
else {
Write-Host "PR not already approved.";
}