/
create-pull-request.ts
249 lines (234 loc) · 8.32 KB
/
create-pull-request.ts
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import * as core from '@actions/core'
import {
createOrUpdateBranch,
getWorkingBaseAndType,
WorkingBaseType
} from './create-or-update-branch'
import {GitHubHelper} from './github-helper'
import {GitCommandManager} from './git-command-manager'
import {GitAuthHelper} from './git-auth-helper'
import * as utils from './utils'
export interface Inputs {
token: string
path: string
commitMessage: string
committer: string
author: string
signoff: boolean
branch: string
deleteBranch: boolean
branchSuffix: string
base: string
pushToFork: string
title: string
body: string
labels: string[]
assignees: string[]
reviewers: string[]
teamReviewers: string[]
milestone: number
draft: boolean
}
export async function createPullRequest(inputs: Inputs): Promise<void> {
let gitAuthHelper
try {
// Get the repository path
const repoPath = utils.getRepoPath(inputs.path)
// Create a git command manager
const git = await GitCommandManager.create(repoPath)
// Save and unset the extraheader auth config if it exists
core.startGroup('Save persisted git credentials')
gitAuthHelper = new GitAuthHelper(git)
await gitAuthHelper.savePersistedAuth()
core.endGroup()
// Init the GitHub client
const githubHelper = new GitHubHelper(inputs.token)
core.startGroup('Determining the base and head repositories')
// Determine the base repository from git config
const remoteUrl = await git.tryGetRemoteUrl()
const baseRemote = utils.getRemoteDetail(remoteUrl)
// Determine the head repository; the target for the pull request branch
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
const branchRepository = inputs.pushToFork
? inputs.pushToFork
: baseRemote.repository
if (inputs.pushToFork) {
// Check if the supplied fork is really a fork of the base
const parentRepository = await githubHelper.getRepositoryParent(
branchRepository
)
if (parentRepository != baseRemote.repository) {
throw new Error(
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
)
}
// Add a remote for the fork
const remoteUrl = utils.getRemoteUrl(
baseRemote.protocol,
branchRepository
)
await git.exec(['remote', 'add', 'fork', remoteUrl])
}
core.endGroup()
core.info(
`Pull request branch target repository set to ${branchRepository}`
)
// Configure auth
if (baseRemote.protocol == 'HTTPS') {
core.startGroup('Configuring credential for HTTPS authentication')
await gitAuthHelper.configureToken(inputs.token)
core.endGroup()
}
core.startGroup('Checking the base repository state')
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
core.info(`Working base is ${workingBaseType} '${workingBase}'`)
// When in detached HEAD state (checked out on a commit), we need to
// know the 'base' branch in order to rebase changes.
if (workingBaseType == WorkingBaseType.Commit && !inputs.base) {
throw new Error(
`When the repository is checked out on a commit instead of a branch, the 'base' input must be supplied.`
)
}
// If the base is not specified it is assumed to be the working base.
const base = inputs.base ? inputs.base : workingBase
// Throw an error if the base and branch are not different branches
// of the 'origin' remote. An identically named branch in the `fork`
// remote is perfectly fine.
if (branchRemoteName == 'origin' && base == inputs.branch) {
throw new Error(
`The 'base' and 'branch' for a pull request must be different branches. Unable to continue.`
)
}
// For self-hosted runners the repository state persists between runs.
// This command prunes the stale remote ref when the pull request branch was
// deleted after being merged or closed. Without this the push using
// '--force-with-lease' fails due to "stale info."
// https://github.com/peter-evans/create-pull-request/issues/633
await git.exec(['remote', 'prune', branchRemoteName])
core.endGroup()
// Apply the branch suffix if set
if (inputs.branchSuffix) {
switch (inputs.branchSuffix) {
case 'short-commit-hash':
// Suffix with the short SHA1 hash
inputs.branch = `${inputs.branch}-${await git.revParse('HEAD', [
'--short'
])}`
break
case 'timestamp':
// Suffix with the current timestamp
inputs.branch = `${inputs.branch}-${utils.secondsSinceEpoch()}`
break
case 'random':
// Suffix with a 7 character random string
inputs.branch = `${inputs.branch}-${utils.randomString()}`
break
default:
throw new Error(
`Branch suffix '${inputs.branchSuffix}' is not a valid value. Unable to continue.`
)
}
}
// Output head branch
core.info(
`Pull request branch to create or update set to '${inputs.branch}'`
)
// Configure the committer and author
core.startGroup('Configuring the committer and author')
const parsedAuthor = utils.parseDisplayNameEmail(inputs.author)
const parsedCommitter = utils.parseDisplayNameEmail(inputs.committer)
git.setIdentityGitOptions([
'-c',
`author.name=${parsedAuthor.name}`,
'-c',
`author.email=${parsedAuthor.email}`,
'-c',
`committer.name=${parsedCommitter.name}`,
'-c',
`committer.email=${parsedCommitter.email}`
])
core.info(
`Configured git committer as '${parsedCommitter.name} <${parsedCommitter.email}>'`
)
core.info(
`Configured git author as '${parsedAuthor.name} <${parsedAuthor.email}>'`
)
core.endGroup()
// Create or update the pull request branch
core.startGroup('Create or update the pull request branch')
const result = await createOrUpdateBranch(
git,
inputs.commitMessage,
inputs.base,
inputs.branch,
branchRemoteName,
inputs.signoff
)
core.endGroup()
if (['created', 'updated'].includes(result.action)) {
// The branch was created or updated
core.startGroup(
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
)
await git.push([
'--force-with-lease',
branchRemoteName,
`HEAD:refs/heads/${inputs.branch}`
])
core.endGroup()
}
// Set the base. It would have been '' if not specified as an input
inputs.base = result.base
if (result.hasDiffWithBase) {
// Create or update the pull request
core.startGroup('Create or update the pull request')
const pull = await githubHelper.createOrUpdatePullRequest(
inputs,
baseRemote.repository,
branchRepository
)
core.endGroup()
// Set outputs
core.startGroup('Setting outputs')
core.setOutput('pull-request-number', pull.number)
core.setOutput('pull-request-url', pull.html_url)
if (pull.created) {
core.setOutput('pull-request-operation', 'created')
} else if (result.action == 'updated') {
core.setOutput('pull-request-operation', 'updated')
}
// Deprecated
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
core.endGroup()
} else {
// There is no longer a diff with the base
// Check we are in a state where a branch exists
if (['updated', 'not-updated'].includes(result.action)) {
core.info(
`Branch '${inputs.branch}' no longer differs from base branch '${inputs.base}'`
)
if (inputs.deleteBranch) {
core.info(`Deleting branch '${inputs.branch}'`)
await git.push([
'--delete',
'--force',
branchRemoteName,
`refs/heads/${inputs.branch}`
])
// Set outputs
core.startGroup('Setting outputs')
core.setOutput('pull-request-operation', 'closed')
core.endGroup()
}
}
}
} catch (error: any) {
core.setFailed(error.message)
} finally {
// Remove auth and restore persisted auth config if it existed
core.startGroup('Restore persisted git credentials')
await gitAuthHelper.removeAuth()
await gitAuthHelper.restorePersistedAuth()
core.endGroup()
}
}