| ... | @@ -124,25 +124,49 @@ jobs: | ... | @@ -124,25 +124,49 @@ jobs: |
| 124 | group: pr-size-${{ github.event.pull_request.number }} | 124 | group: pr-size-${{ github.event.pull_request.number }} |
| 125 | cancel-in-progress: true | 125 | cancel-in-progress: true |
| 126 | steps: | 126 | steps: |
| | 127 | - name: Checkout base repository |
| | 128 | uses: actions/checkout@v4 |
| | 129 | with: |
| | 130 | fetch-depth: 0 |
| 127 | - name: Sync PR size label | 131 | - name: Sync PR size label |
| 128 | uses: actions/github-script@v8 | 132 | uses: actions/github-script@v8 |
| 129 | env: | 133 | env: |
| 130 | PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} | 134 | PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} |
| 131 | with: | 135 | with: |
| 132 | script: | | 136 | script: | |
| | 137 | const { execFileSync } = require("node:child_process"); |
| | 138 | |
| 133 | const issueNumber = context.payload.pull_request.number; | 139 | const issueNumber = context.payload.pull_request.number; |
| | 140 | const baseSha = context.payload.pull_request.base.sha; |
| | 141 | const headSha = context.payload.pull_request.head.sha; |
| | 142 | const headTrackingRef = `refs/remotes/pr-size/${issueNumber}`; |
| 134 | const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); | 143 | const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); |
| 135 | const managedLabelNames = new Set(managedLabels.map((label) => label.name)); | 144 | const managedLabelNames = new Set(managedLabels.map((label) => label.name)); |
| 136 | // Keep this aligned with the repo's test entrypoints and test-only support files. | 145 | // Keep this aligned with the repo's test entrypoints and test-only support files. |
| 137 | const testFilePatterns = [ | 146 | const testExcludePathspecs = [ |
| 138 | /(^|\/)__tests__(\/|$)/, | 147 | ":(glob,exclude)**/__tests__/**", |
| 139 | /(^|\/)tests?(\/|$)/, | 148 | ":(glob,exclude)**/test/**", |
| 140 | /^apps\/server\/integration\//, | 149 | ":(glob,exclude)**/tests/**", |
| 141 | /\.(test|spec|browser|integration)\.[^.\/]+$/, | 150 | ":(glob,exclude)apps/server/integration/**", |
| | 151 | ":(glob,exclude)**/*.test.*", |
| | 152 | ":(glob,exclude)**/*.spec.*", |
| | 153 | ":(glob,exclude)**/*.browser.*", |
| | 154 | ":(glob,exclude)**/*.integration.*", |
| 142 | ]; | 155 | ]; |
| 143 | | 156 | |
| 144 | const isTestFile = (filename) => | 157 | const sumNumstat = (text) => |
| 145 | testFilePatterns.some((pattern) => pattern.test(filename)); | 158 | text |
| | 159 | .split("\n") |
| | 160 | .filter(Boolean) |
| | 161 | .reduce((total, line) => { |
| | 162 | const [insertionsRaw = "0", deletionsRaw = "0"] = line.split("\t"); |
| | 163 | const additions = |
| | 164 | insertionsRaw === "-" ? 0 : Number.parseInt(insertionsRaw, 10) || 0; |
| | 165 | const deletions = |
| | 166 | deletionsRaw === "-" ? 0 : Number.parseInt(deletionsRaw, 10) || 0; |
| | 167 | |
| | 168 | return total + additions + deletions; |
| | 169 | }, 0); |
| 146 | | 170 | |
| 147 | const resolveSizeLabel = (totalChangedLines) => { | 171 | const resolveSizeLabel = (totalChangedLines) => { |
| 148 | if (totalChangedLines < 10) { | 172 | if (totalChangedLines < 10) { |
| ... | @@ -168,40 +192,53 @@ jobs: | ... | @@ -168,40 +192,53 @@ jobs: |
| 168 | return "size:XXL"; | 192 | return "size:XXL"; |
| 169 | }; | 193 | }; |
| 170 | | 194 | |
| 171 | const files = await github.paginate( | 195 | execFileSync("git", ["fetch", "--no-tags", "origin", baseSha], { |
| 172 | github.rest.pulls.listFiles, | 196 | stdio: "inherit", |
| | 197 | }); |
| | 198 | |
| | 199 | execFileSync( |
| | 200 | "git", |
| | 201 | ["fetch", "--no-tags", "origin", `+refs/pull/${issueNumber}/head:${headTrackingRef}`], |
| 173 | { | 202 | { |
| 174 | owner: context.repo.owner, | 203 | stdio: "inherit", |
| 175 | repo: context.repo.repo, | | |
| 176 | pull_number: issueNumber, | | |
| 177 | per_page: 100, | | |
| 178 | }, | 204 | }, |
| 179 | (response) => response.data, | | |
| 180 | ); | 205 | ); |
| 181 | | 206 | |
| 182 | if (files.length >= 3000) { | 207 | const resolvedHeadSha = execFileSync("git", ["rev-parse", headTrackingRef], { |
| | 208 | encoding: "utf8", |
| | 209 | }).trim(); |
| | 210 | |
| | 211 | if (resolvedHeadSha !== headSha) { |
| 183 | core.warning( | 212 | core.warning( |
| 184 | "The GitHub pull request files API may truncate results at 3,000 files; PR size may be undercounted.", | 213 | `Fetched head SHA ${resolvedHeadSha} does not match pull request head SHA ${headSha}; using fetched ref for sizing.`, |
| 185 | ); | 214 | ); |
| 186 | } | 215 | } |
| 187 | | 216 | |
| 188 | let testChangedLines = 0; | 217 | execFileSync("git", ["cat-file", "-e", `${baseSha}^{commit}`], { |
| 189 | let nonTestChangedLines = 0; | 218 | stdio: "inherit", |
| 190 | | 219 | }); |
| 191 | for (const file of files) { | | |
| 192 | const changedLinesForFile = (file.additions ?? 0) + (file.deletions ?? 0); | | |
| 193 | | | |
| 194 | if (changedLinesForFile === 0) { | | |
| 195 | continue; | | |
| 196 | } | | |
| 197 | | 220 | |
| 198 | if (isTestFile(file.filename)) { | 221 | const diffArgs = [ |
| 199 | testChangedLines += changedLinesForFile; | 222 | "diff", |
| 200 | continue; | 223 | "--numstat", |
| 201 | } | 224 | "--ignore-all-space", |
| | 225 | "--ignore-blank-lines", |
| | 226 | `${baseSha}...${resolvedHeadSha}`, |
| | 227 | ]; |
| 202 | | 228 | |
| 203 | nonTestChangedLines += changedLinesForFile; | 229 | const totalChangedLines = sumNumstat( |
| 204 | } | 230 | execFileSync( |
| | 231 | "git", |
| | 232 | diffArgs, |
| | 233 | { encoding: "utf8" }, |
| | 234 | ), |
| | 235 | ); |
| | 236 | const nonTestChangedLines = sumNumstat( |
| | 237 | execFileSync("git", [...diffArgs, "--", ".", ...testExcludePathspecs], { |
| | 238 | encoding: "utf8", |
| | 239 | }), |
| | 240 | ); |
| | 241 | const testChangedLines = Math.max(0, totalChangedLines - nonTestChangedLines); |
| 205 | | 242 | |
| 206 | const changedLines = nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines; | 243 | const changedLines = nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines; |
| 207 | const nextLabelName = resolveSizeLabel(changedLines); | 244 | const nextLabelName = resolveSizeLabel(changedLines); |