-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
221 lines (197 loc) · 6.86 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
const express = require('express');
const travis = require('./lib/travis.js');
const github = require('./lib/github.js');
const prune = require('./lib/pruning.js');
const util = require('util');
const dedent = require('dedent');
const BALLET_CONFIG_FILE = 'ballet.yml';
/**
* This is the main entrypoint to your Probot app
* @param {import('probot').Application} app
*/
module.exports = (app) => {
// Status check
const router = app.route('/ballet-bot');
router.use(express.static('public'));
router.get('/status', (req, res) => {
res.send('OK');
});
app.on('check_run.completed', async context => {
context.log.info(`Responding to ${context.event} (id=${context.id})`);
const repoUrl = context.payload.repository.html_url;
const detailsUrl = context.payload.check_run.details_url;
// Only respond to travis builds!
const slug = context.payload.check_run.app.slug;
if (slug !== 'travis-ci') {
context.log.debug(`Not responding to event from non-Travis app (slug=${slug})`);
return;
}
const repoDir = await github.downloadRepo(repoUrl);
const config = await loadConfig(context);
const travisBuildId = travis.getBuildIdFromDetailsUrl(detailsUrl);
const travisBuild = await travis.getBuildFromId(travisBuildId);
logImportantInformation(context, travisBuild);
const shouldPrune = shouldPruneRedundantFeatures(
context,
config,
travisBuildId
);
const shouldMerge = shouldMergeAcceptedFeature(
context,
config,
travisBuild
);
const shouldClose = shouldCloseRejectedFeature(
context,
config,
travisBuild
);
const shouldPruneResult = await shouldPrune;
if (shouldPruneResult.result) {
context.log.info('Pruning features...');
await pruneRedundantFeatures(context, repoDir.name, config, travisBuild);
} else {
context.log.info(
`Not acting to prune features because ${shouldPruneResult.reason}`
);
}
const shouldMergeResult = await shouldMerge;
if (shouldMergeResult.shouldMerge) {
context.log.info('Merging PR...');
const { omittedFeature } = travis.getTravisAcceptanceMetadata(travisBuildId);
const omittedFeatureMessage = omittedFeature
? `We found that your feature provided more information than another feature: ${omittedFeature}`
: 'We found that your feature added valuable information on top of all the existing features';
const message = dedent`
After validation, your feature was accepted. ${omittedFeatureMessage}. It will be automatically merged into the project.
`;
await github.commentOnPullRequest(
context,
travisBuild.pull_request_number,
message
);
await github.mergePullRequest(context, travisBuild.pull_request_number);
await github.closePullRequest(context, travisBuild.pull_request_number);
} else {
context.log.info(
`Not acting to merge PR because ${shouldMergeResult.reason}`
);
}
const shouldCloseResult = await shouldClose;
if (shouldCloseResult.result) {
context.log.info('Closing PR...');
const message = dedent`
After validation, your feature was rejected. Your pull request will be closed. For more details about failures in the validation process, check out the Travis CI build logs.
`;
await github.commentOnPullRequest(
context,
travisBuild.pull_request_number,
message
);
await github.closePullRequest(context, travisBuild.pull_request_number);
} else {
context.log.info(
`Not acting to close PR because ${shouldCloseResult.reason}`
);
}
repoDir.removeCallback();
});
};
const loadConfig = async (context) => {
let config = await context.config(`../${BALLET_CONFIG_FILE}`);
// This is to accommodate old config file formats from previous ballet
// versions. Previously, ballet.yml had a root key "default"
// (https://ballet.github.io/ballet/history.html#id14)
if (config.default) {
config = config.default
}
if (config) {
const s = util.inspect(config, { depth: 5, breakLength: Infinity });
context.log.debug(`Loaded config from ballet.yml:default: ${s}`);
return config;
}
};
const logImportantInformation = (context, travisBuild) => {
context.log.info(`Getting a check from branch: ${travisBuild.branch.name}`);
context.log.info(`On commit: ${travisBuild.commit.message}`);
};
const shouldMergeAcceptedFeature = async (context, config, build) => {
let shouldMerge, reason;
if (build.event_type !== 'pull_request') {
shouldMerge = false;
reason = 'not a PR';
} else if (!(await travis.doesBuildPassAllChecks(build.id))) {
shouldMerge = false;
reason = 'Travis build did not pass';
} else if (
!(await github.isPullRequestProposingFeature(
context,
build.pull_request_number
))
) {
shouldMerge = false;
reason = 'PR does not propose a feature';
} else if (config.github.auto_merge_accepted_features === 'no') {
shouldMerge = false;
reason = 'auto_merge_accepted_features disabled in config';
} else {
shouldMerge = true;
reason = '<n/a>';
}
return { shouldMerge, reason };
};
const shouldPruneRedundantFeatures = async (context, config, buildId) => {
let result, reason;
if (!(await github.isOnMasterAfterMerge(context))) {
result = false;
reason = 'not on master branch after merge commit';
} else if (!(await travis.doesBuildPassAllChecks(buildId))) {
result = false;
reason = 'Travis build is failing';
} else if (config.github.pruning_action === 'no_action') {
result = false;
reason = 'pruning_action set to no_action in config';
} else {
result = true;
reason = '<n/a>';
}
return { result, reason };
};
const pruneRedundantFeatures = async (context, repoDir, config, build) => {
const redundantFeatures = await travis.getTravisRedundantFeatures(build);
context.log.info('FOUND REDUNDANT FEATURES: ');
context.log.info(redundantFeatures.join('\n'));
if (redundantFeatures.length) {
return prune.removeRedundantFeatures(
context,
repoDir,
config,
redundantFeatures
);
}
};
const shouldCloseRejectedFeature = async (context, config, build) => {
let result, reason;
if (build.event_type !== 'pull_request') {
result = false;
reason = 'not a pull request';
} else if (await travis.doesBuildPassAllChecks(build.id)) {
result = false;
reason = 'passed all checks on CI';
} else if (
!(await github.isPullRequestProposingFeature(
context,
build.pull_request_number
))
) {
result = false;
reason = 'PR does not propose a feature';
} else if (config.github.auto_close_rejected_features === 'no') {
result = false;
reason = 'auto_close_rejected_features disabled in config';
} else {
result = true;
reason = '<n/a>';
}
return { result, reason };
};