-
Notifications
You must be signed in to change notification settings - Fork 0
/
merge.js
179 lines (156 loc) · 5.95 KB
/
merge.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
const request = require('request')
const asciify = require('asciify')
const messages = require('./messages.json')
const addContributor = require('./contributors.js')
const baseURL = 'https://github.com/repos/lhl-reporobot/patchwork/'
const stats = {}
// When a new, open Pull Request comes in via the webhook set on jlord/patchwork
// the request is queued and one by one sent here to verify the PR is a part of
// the Git-it challenges (and not a real, other one) and to verify the file
// contents of the PR and merge, making comments when needed.
// called by:
// mergePR(pullreq, function(err, message) { if (err) console.log(new Date(), message, err)
// callback(err)
// })
module.exports = function (pullreq, callback) {
if (pullreq.pull_request) pullreq = pullreq.pull_request
const prBranch = pullreq.head.ref.toLowerCase()
stats.user = pullreq.user.login
stats.prNum = pullreq.number
// if branch name doesn't include username, it may be
// a non git-it related normal PR
if (!prBranch.match(stats.user.toLowerCase())) {
return writeComment(messages.antipattern_branch, stats.prNum)
}
const options = {
url: baseURL + 'pulls/' + stats.prNum,
json: true,
headers: { 'User-Agent': 'request',
'Authorization': 'token ' + process.env['REPOROBOT_TOKEN']
}
}
function getTime (error, response, body) {
if (error) return callback(error, 'Error in request on PR via number')
// if a test pr is coming in from @RR
let info
if (!error && response.statusCode === 200 && pullreq.user.login === 'reporobot') {
info = body
stats.time = info.created_at.toLocaleString()
// RR is PRing on behalf of:
console.log(new Date(), 'PR ', stats.prNum, 'Reporobot Pull Request on behalf of ', stats.user)
return getFile(stats.prNum)
}
if (!error && response.statusCode === 200 && pullreq.user.login !== 'reporobot') {
info = body
stats.time = info.created_at
return getFile(stats.prNum)
}
callback(body)
}
request(options, getTime)
function getFile (prNum) {
const options = {
url: baseURL + 'pulls/' + prNum + '/files',
json: true,
headers: {
'User-Agent': 'request',
'Authorization': 'token ' + process.env['REPOROBOT_TOKEN']
}
}
request(options, function returnFiles (error, response, body) {
if (error || body.length === 0) return callback(error, 'Error finding file in PR')
if (!error && response.statusCode === 200) {
if (body.length > 1) {
console.log(new Date(), 'PR ', stats.prNum, 'MORE THAN ONE FILE ', stats.user)
return writeComment(messages.multi_files, stats.prNum)
}
const prInfo = body[0]
// TODO do empty files not have a patch property?
if (prInfo === undefined || !prInfo.patch) {
console.log(new Date(), 'PR ', stats.prNum, 'FILE IS EMPTY ', stats.user)
return writeComment(messages.empty_file, stats.prNum)
}
return verifyFilename(prInfo)
}
// huh? why sending this back to function(err, message)?
callback(body)
})
}
function verifyFilename (prInfo) {
const filename = prInfo.filename.toLowerCase()
if (filename.match('contributors/add-' + stats.user.toLowerCase())) {
console.log(new Date(), 'PR ', stats.prNum, 'Filename: MATCH ', stats.user)
return verifyContent(prInfo)
} else {
return writeComment(messages.bad_filename, stats.prNum)
}
}
function verifyContent (prInfo) {
// pull out the actual pr content
const patchArray = prInfo.patch.split('@@')
const patch = patchArray.pop()
// generate the expected content
asciify(stats.user, { font: 'isometric2' }, function (err, res) {
if (err) return callback(err, 'Error generating ascii art to test against')
if (patch !== stats.user) {
stats.userArt = res
console.log(new Date(), 'PR ', stats.prNum, 'Content: MATCH ', stats.user)
return setTimeout(() => mergePR(stats.prNum), 5000)
} else {
return writeComment(messages.bad_ascii, stats.prNum)
}
})
}
function writeComment (message, prNum) {
stats.user = stats.user || 'a skipped PR'
console.log(new Date(), 'PR ' + prNum + ' Uh oh, writing comment for ' + stats.user)
const options = {
url: baseURL + 'issues/' + prNum + '/comments',
headers: {
'User-Agent': 'request',
'Authorization': 'token ' + process.env['REPOROBOT_TOKEN']
},
json: {'body': message}
}
request.post(options, function doneWriteComment (error, response, body) {
if (error) return callback(error, 'Error writing comment on PR')
callback()
})
}
function mergePR (prNum) {
const tries = 0
const limit = 25
tryMerge()
function tryMerge () {
const message = 'Merging PR from @' + stats.user
const options = {
url: baseURL + 'pulls/' + prNum + '/merge',
headers: {
'User-Agent': 'request',
'Authorization': 'token ' + process.env['REPOROBOT_TOKEN']
},
json: {'commit_message': message}
}
request.put(options, function doneMerge (error, response, body) {
if (error) return callback(error, 'Error merging PR')
if (response.statusCode !== 200) {
console.log(new Date(), prNum, 'ERROR MERGING', response.statusCode, body.message)
console.log(new Date(), prNum, 'TRYING AGAIN')
if (tries <= limit) {
tries++
return setTimeout(tryMerge(prNum), 3000)
} else {
callback(null, new Date() + 'Could not merge after ' + limit + ' tries ' + prNum)
}
}
if (!error && response.statusCode === 200) {
console.log(new Date(), 'PR ', prNum, 'MERGED', stats.user)
// add contributor to file and then rebuild page
return addContributor(stats, callback)
} else {
callback(error, body)
}
})
}
}
}