|
1 | 1 | import * as core from '@actions/core'
|
| 2 | +import * as fs from 'fs' |
| 3 | +import { graphql } from '@octokit/graphql' |
| 4 | +import type { |
| 5 | + Repository, |
| 6 | + Ref, |
| 7 | + Commit, |
| 8 | + FileChanges |
| 9 | +} from '@octokit/graphql-schema' |
2 | 10 | import {
|
3 | 11 | createOrUpdateBranch,
|
4 | 12 | getWorkingBaseAndType,
|
@@ -32,6 +40,7 @@ export interface Inputs {
|
32 | 40 | teamReviewers: string[]
|
33 | 41 | milestone: number
|
34 | 42 | draft: boolean
|
| 43 | + signCommit: boolean |
35 | 44 | }
|
36 | 45 |
|
37 | 46 | export async function createPullRequest(inputs: Inputs): Promise<void> {
|
@@ -192,11 +201,180 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
192 | 201 | core.startGroup(
|
193 | 202 | `Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
194 | 203 | )
|
195 |
| - await git.push([ |
196 |
| - '--force-with-lease', |
197 |
| - branchRemoteName, |
198 |
| - `${inputs.branch}:refs/heads/${inputs.branch}` |
199 |
| - ]) |
| 204 | + if (inputs.signCommit) { |
| 205 | + core.info(`Use API to push a signed commit`) |
| 206 | + const graphqlWithAuth = graphql.defaults({ |
| 207 | + headers: { |
| 208 | + authorization: 'token ' + inputs.token, |
| 209 | + }, |
| 210 | + }); |
| 211 | + |
| 212 | + let repoOwner = process.env.GITHUB_REPOSITORY!.split("/")[0] |
| 213 | + if (inputs.pushToFork) { |
| 214 | + const forkName = await githubHelper.getRepositoryParent(baseRemote.repository) |
| 215 | + if (!forkName) { repoOwner = forkName! } |
| 216 | + } |
| 217 | + const repoName = process.env.GITHUB_REPOSITORY!.split("/")[1] |
| 218 | + |
| 219 | + core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`) |
| 220 | + const refQuery = ` |
| 221 | + query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) { |
| 222 | + repository(owner: $repoOwner, name: $repoName){ |
| 223 | + id |
| 224 | + ref(qualifiedName: $branchName){ |
| 225 | + id |
| 226 | + name |
| 227 | + prefix |
| 228 | + target{ |
| 229 | + id |
| 230 | + oid |
| 231 | + commitUrl |
| 232 | + commitResourcePath |
| 233 | + abbreviatedOid |
| 234 | + } |
| 235 | + } |
| 236 | + }, |
| 237 | + } |
| 238 | + ` |
| 239 | + |
| 240 | + let branchRef = await graphqlWithAuth<{repository: Repository}>( |
| 241 | + refQuery, |
| 242 | + { |
| 243 | + repoOwner: repoOwner, |
| 244 | + repoName: repoName, |
| 245 | + branchName: inputs.branch |
| 246 | + } |
| 247 | + ) |
| 248 | + core.debug( `Fetched information for branch '${inputs.branch}' - '${JSON.stringify(branchRef)}'`) |
| 249 | + |
| 250 | + // if the branch does not exist, then first we need to create the branch from base |
| 251 | + if (branchRef.repository.ref == null) { |
| 252 | + core.debug( `Branch does not exist - '${inputs.branch}'`) |
| 253 | + branchRef = await graphqlWithAuth<{repository: Repository}>( |
| 254 | + refQuery, |
| 255 | + { |
| 256 | + repoOwner: repoOwner, |
| 257 | + repoName: repoName, |
| 258 | + branchName: inputs.base |
| 259 | + } |
| 260 | + ) |
| 261 | + core.debug( `Fetched information for base branch '${inputs.base}' - '${JSON.stringify(branchRef)}'`) |
| 262 | + |
| 263 | + core.info( `Creating new branch '${inputs.branch}' from '${inputs.base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`) |
| 264 | + if (branchRef.repository.ref != null) { |
| 265 | + core.debug( `Send request for creating new branch`) |
| 266 | + const newBranchMutation = ` |
| 267 | + mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) { |
| 268 | + createRef(input: { |
| 269 | + name: $branchName, |
| 270 | + oid: $oid, |
| 271 | + repositoryId: $repoId |
| 272 | + }) { |
| 273 | + ref { |
| 274 | + id |
| 275 | + name |
| 276 | + prefix |
| 277 | + } |
| 278 | + } |
| 279 | + } |
| 280 | + ` |
| 281 | + let newBranch = await graphqlWithAuth<{createRef: {ref: Ref}}>( |
| 282 | + newBranchMutation, |
| 283 | + { |
| 284 | + repoId: branchRef.repository.id, |
| 285 | + oid: branchRef.repository.ref.target!.oid, |
| 286 | + branchName: 'refs/heads/' + inputs.branch |
| 287 | + } |
| 288 | + ) |
| 289 | + core.debug(`Created new branch '${inputs.branch}': '${JSON.stringify(newBranch.createRef.ref)}'`) |
| 290 | + } |
| 291 | + } |
| 292 | + core.info( `Hash ref of branch '${inputs.branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`) |
| 293 | + |
| 294 | + // switch to input-branch for reading updated file contents |
| 295 | + await git.checkout(inputs.branch) |
| 296 | + |
| 297 | + let changedFiles = await git.getChangedFiles(branchRef.repository.ref!.target!.oid, ['--diff-filter=M']) |
| 298 | + let deletedFiles = await git.getChangedFiles(branchRef.repository.ref!.target!.oid, ['--diff-filter=D']) |
| 299 | + let fileChanges = <FileChanges>{additions: [], deletions: []} |
| 300 | + |
| 301 | + core.debug(`Changed files: '${JSON.stringify(changedFiles)}'`) |
| 302 | + core.debug(`Deleted files: '${JSON.stringify(deletedFiles)}'`) |
| 303 | + |
| 304 | + for (var file of changedFiles) { |
| 305 | + fileChanges.additions!.push({ |
| 306 | + path: file, |
| 307 | + contents: btoa(fs.readFileSync(file, 'utf8')), |
| 308 | + }) |
| 309 | + } |
| 310 | + |
| 311 | + for (var file of deletedFiles) { |
| 312 | + fileChanges.deletions!.push({ |
| 313 | + path: file, |
| 314 | + }) |
| 315 | + } |
| 316 | + |
| 317 | + const pushCommitMutation = ` |
| 318 | + mutation PushCommit( |
| 319 | + $repoNameWithOwner: String!, |
| 320 | + $branchName: String!, |
| 321 | + $headOid: GitObjectID!, |
| 322 | + $commitMessage: String!, |
| 323 | + $fileChanges: FileChanges |
| 324 | + ) { |
| 325 | + createCommitOnBranch(input: { |
| 326 | + branch: { |
| 327 | + repositoryNameWithOwner: $repoNameWithOwner, |
| 328 | + branchName: $branchName, |
| 329 | + } |
| 330 | + fileChanges: $fileChanges |
| 331 | + message: { |
| 332 | + headline: $commitMessage |
| 333 | + } |
| 334 | + expectedHeadOid: $headOid |
| 335 | + }){ |
| 336 | + clientMutationId |
| 337 | + ref{ |
| 338 | + id |
| 339 | + name |
| 340 | + prefix |
| 341 | + } |
| 342 | + commit{ |
| 343 | + id |
| 344 | + abbreviatedOid |
| 345 | + oid |
| 346 | + } |
| 347 | + } |
| 348 | + } |
| 349 | + ` |
| 350 | + const pushCommitVars = { |
| 351 | + branchName: inputs.branch, |
| 352 | + repoNameWithOwner: repoOwner + '/' + repoName, |
| 353 | + headOid: branchRef.repository.ref!.target!.oid, |
| 354 | + commitMessage: inputs.commitMessage, |
| 355 | + fileChanges: fileChanges, |
| 356 | + } |
| 357 | + |
| 358 | + core.info(`Push commit with payload: '${JSON.stringify(pushCommitVars)}'`) |
| 359 | + |
| 360 | + const commit = await graphqlWithAuth<{createCommitOnBranch: {ref: Ref, commit: Commit} }>( |
| 361 | + pushCommitMutation, |
| 362 | + pushCommitVars, |
| 363 | + ); |
| 364 | + |
| 365 | + core.debug( `Pushed commit - '${JSON.stringify(commit)}'`) |
| 366 | + core.info( `Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`) |
| 367 | + |
| 368 | + // switch back to previous branch/state since we are done with reading the changed file contents |
| 369 | + await git.checkout('-') |
| 370 | + |
| 371 | + } else { |
| 372 | + await git.push([ |
| 373 | + '--force-with-lease', |
| 374 | + branchRemoteName, |
| 375 | + `${inputs.branch}:refs/heads/${inputs.branch}` |
| 376 | + ]) |
| 377 | + } |
200 | 378 | core.endGroup()
|
201 | 379 | }
|
202 | 380 |
|
|
0 commit comments