-
Notifications
You must be signed in to change notification settings - Fork 76
/
index.js
289 lines (251 loc) · 11.6 KB
/
index.js
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
const getDCOStatus = require("./lib/dco.js");
const requireMembers = require("./lib/requireMembers.js");
/**
* @param {import('probot').Probot} app
*/
module.exports = (app) => {
app.on(
[
"pull_request.opened",
"pull_request.synchronize",
"check_run.rerequested",
],
check
);
async function check(context) {
const timeStart = new Date();
const config = await context.config("dco.yml", {
require: {
members: true,
},
allowRemediationCommits: {
individual: false,
thirdParty: false,
},
});
const requireForMembers = config.require.members;
const allowRemediationCommits = config.allowRemediationCommits;
const pr = context.payload.pull_request;
const compare = await context.octokit.repos.compareCommits(
context.repo({
base: pr.base.sha,
head: pr.head.sha,
})
);
const commits = compare.data.commits;
const dcoFailed = await getDCOStatus(
commits,
requireMembers(requireForMembers, context),
context.payload.pull_request.html_url,
allowRemediationCommits
);
if (!dcoFailed.length) {
await context.octokit.checks
.create(
context.repo({
name: "DCO",
head_branch: pr.head.ref,
head_sha: pr.head.sha,
status: "completed",
started_at: timeStart,
conclusion: "success",
completed_at: new Date(),
output: {
title: "DCO",
summary: "All commits are signed off!",
},
})
)
.catch(function checkFails(error) {
/* istanbul ignore next - unexpected error */
if (error.status !== 403) throw error;
context.log.info("resource not accessible, creating status instead");
// create status
const params = {
sha: pr.head.sha,
context: "DCO",
state: "success",
description: "All commits are signed off!",
target_url: "https://github.com/probot/dco#how-it-works",
};
return context.octokit.repos.createCommitStatus(context.repo(params));
});
} else {
let summary = [];
dcoFailed.forEach(function (commit) {
summary.push(
`Commit sha: [${commit.sha.substr(0, 7)}](${commit.url}), Author: ${
commit.author
}, Committer: ${commit.committer}; ${commit.message}`
);
});
summary = summary.join("\n");
summary =
handleCommits(pr, commits.length, dcoFailed, allowRemediationCommits) +
`\n\n### Summary\n\n${summary}`;
await context.octokit.checks
.create(
context.repo({
name: "DCO",
head_branch: pr.head.ref,
head_sha: pr.head.sha,
status: "completed",
started_at: timeStart,
conclusion: "action_required",
completed_at: new Date(),
output: {
title: "DCO",
summary,
},
actions: [
{
label: "Set DCO to pass",
description: "would set status to passing",
identifier: "override",
},
],
})
)
.catch(function checkFails(error) {
/* istanbul ignore next - unexpected error */
if (error.status !== 403) throw error;
context.log.info("resource not accessible, creating status instead");
// create status
const description = dcoFailed[dcoFailed.length - 1].message.substring(
0,
140
);
const params = {
sha: pr.head.sha,
context: "DCO",
state: "failure",
description,
target_url: "https://github.com/probot/dco#how-it-works",
};
return context.octokit.repos.createCommitStatus(context.repo(params));
});
}
}
// This option is only presented to users with Write Access to the repo
app.on("check_run.requested_action", setStatusPass);
async function setStatusPass(context) {
const timeStart = new Date();
await context.octokit.checks.create(
context.repo({
name: "DCO",
head_branch: context.payload.check_run.check_suite.head_branch,
head_sha: context.payload.check_run.head_sha,
status: "completed",
started_at: timeStart,
conclusion: "success",
completed_at: new Date(),
output: {
title: "DCO",
summary: "Commit sign-off was manually approved.",
},
})
);
}
};
function handleCommits(pr, commitLength, dcoFailed, allowRemediationCommits) {
let returnMessage = "";
if (dcoFailed.length === 1) {
returnMessage =
"There is one commit incorrectly signed off. This means that the author of this commit failed to include a Signed-off-by line in the commit message.\n\n";
} else {
returnMessage = `There are ${dcoFailed.length} commits incorrectly signed off. This means that the author(s) of these commits failed to include a Signed-off-by line in their commit message.\n\n`;
}
returnMessage =
returnMessage +
`To avoid having PRs blocked in the future, always include \`Signed-off-by: Author Name <[email protected]>\` in *every* commit message. You can also do this automatically by using the -s flag (i.e., \`git commit -s\`).
Here is how to fix the problem so that this code can be merged.\n\n`;
let rebaseWarning = "";
if (
allowRemediationCommits.individual ||
allowRemediationCommits.thirdParty
) {
returnMessage =
returnMessage +
`---\n\n### Preferred method: Commit author adds a DCO remediation commit
A *DCO Remediation Commit* contains special text in the commit message that applies a missing Signed-off-by line in a subsequent commit. The primary benefit of this method is that the project’s history does not change, and there is no risk of breaking someone else’s work.
These authors can unblock this PR by adding a new commit to this branch with the following text in their commit message:\n`;
let currentAuthor = "";
dcoFailed.forEach(function (commit, index, dcoFailed) {
// If the author has changed, we need to write a new section and close any old sections
if (currentAuthor !== commit.author + " <" + commit.email + ">") {
// If currentAuthor was already defined it's the end of a section, so write the Signed-off-by
if (currentAuthor) {
returnMessage =
returnMessage + `\nSigned-off-by: ${currentAuthor}\n\`\`\`\n`;
}
// Update the currentAuthor and write a new section
currentAuthor = commit.author + " <" + commit.email + ">";
returnMessage =
returnMessage +
`#### ${commit.author} <${commit.email}>\n\`\`\`\nDCO Remediation Commit for ${commit.author} <${commit.email}>\n\n`;
}
// Draft the magic DCO remediation commit text for the author
// returnMessage = returnMessage + `I, ${commit.author} <${commit.email}> ${commit.sha}\n`
returnMessage =
returnMessage +
`I, ${currentAuthor}, hereby add my Signed-off-by to this commit: ${commit.sha}\n`;
if (index === dcoFailed.length - 1) {
returnMessage =
returnMessage + `\nSigned-off-by: ${currentAuthor}\n\`\`\`\n`;
}
});
returnMessage =
returnMessage +
"\n\nPlease note: You should avoid adding empty commits (i.e., `git commit -s --allow-empty`), because these will be discarded if someone rebases the branch / repo.\n";
if (allowRemediationCommits.thirdParty) {
returnMessage =
returnMessage +
`\n---\n\n### Alternate method: An employer adds a DCO Remediation Commit
If the contents of this commit were contributed on behalf of a third party (generally, the author’s employer), an authorized individual may add a Third-Party DCO Remediation Commit. This may be necessary if the original author is unavailable to add their own DCO Remediation Commit to this branch.
If you are about to add a Third-Party DCO Remediation Commit under DCO section (b) or (c), be sure you are authorized by your employer to take this action. Generally speaking, maintainers and other project contributors cannot sign off on behalf of project contributors, unless there is some relationship which permits this action. It is your responsibility to verify this.
This PR can be unblocked by an authorized third party adding one or more new commits to this branch with the following text in the commit message. Replace YOUR_COMPANY with your company name, YOUR_NAME with the name of the authorized representative, and YOUR_EMAIL with the representative’s email address.
For the sake of clarity, please use a separate commit per author:\n`;
currentAuthor = "";
dcoFailed.forEach(function (commit, index, dcoFailed) {
// If the author has changed, we need to write a new section and close any old sections
if (currentAuthor !== commit.author + " <" + commit.email + ">") {
// If currentAuthor was already defined it's the end of a section, so write the Signed-off-by
if (currentAuthor) {
returnMessage =
returnMessage + "\nSigned-off-by: YOUR_NAME <YOUR_EMAIL>\n```\n";
}
// Update the currentAuthor and write a new section
currentAuthor = commit.author + " <" + commit.email + ">";
returnMessage =
returnMessage +
`#### On behalf of ${commit.author} <${commit.email}>\n\`\`\`\nThird-Party DCO Remediation Commit for ${commit.author} <${commit.email}>\n\n`;
}
// Draft the magic DCO remediation commit text for the author
// returnMessage = returnMessage + `Retroactive-signed-off-by: ${commit.author} <${commit.email}> ${commit.sha}\n`
returnMessage =
returnMessage +
`On behalf of ${commit.author} <${commit.email}>, I, YOUR_NAME <YOUR_EMAIL>, hereby add my Signed-off-by to this commit: ${commit.sha}\n`;
if (index === dcoFailed.length - 1) {
returnMessage =
returnMessage + "\nSigned-off-by: YOUR_NAME <YOUR_EMAIL>\n```\n";
}
});
}
rebaseWarning = "Least preferred method: ";
}
returnMessage =
returnMessage +
"\n---\n\n### " +
rebaseWarning +
`Rebase the branch
If you have a local git environment and meet the criteria below, one option is to rebase the branch and add your Signed-off-by lines in the new commits. Please note that if others have already begun work based upon the commits in this branch, this solution will rewrite history and may cause serious issues for collaborators ([described in the git documentation](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) under "The Perils of Rebasing").
You should only do this if:
* You are the only author of the commits in this branch
* You are absolutely certain nobody else is doing any work based upon this branch
* There are no empty commits in the branch (for example, a DCO Remediation Commit which was added using \`--allow-empty\`)
To add your Signed-off-by line to every commit in this branch:
1. Ensure you have a local copy of your branch by [checking out the pull request locally via command line](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally).
1. In your local branch, run: \`git rebase HEAD~${commitLength} --signoff\`
1. Force push your changes to overwrite the branch: \`git push --force-with-lease origin ${pr.head.ref}\`\n\n---\n\n`;
return returnMessage;
}