-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpush.js
More file actions
288 lines (235 loc) · 9.59 KB
/
push.js
File metadata and controls
288 lines (235 loc) · 9.59 KB
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
// -*- tab-width: 4; indent-tabs-mode: nil; -*-
"use strict"
// Finds all users that have actually
// marked something as reviewed in the changed files.
function reviewers(review) {
var reviewers = {};
function collect_reviewers(commit) {
try {
review.getChangeset(commit).files.forEach(function(file) {
reviewers[file.reviewedBy.name] = true;
});
} catch(error) {
// Getting various exceptions when the changesets can't be found. So ignore...
}
};
review.branch.commits.forEach(collect_reviewers);
review.commits.forEach(collect_reviewers);
return Object.keys(reviewers).join(", ");
}
function cgit_range(repo, branch, sha1_update) {
if(!sha1_update) {
return null;
}
var ini = IO.File.read("integrate.ini");
var cgit_url = ini.decode().match(/cgit_url\s*=\s*(\S*)/);
if( !cgit_url ) {
return null;
}
var from = sha1_update[1];
var to = sha1_update[2];
return "[" + from + ".." + to + "|" + cgit_url[1] + repo +
"/log/?h=" + branch + "&qt=range&q=" + from + ".." + to + "]";
}
function review_link(id) {
var ini = IO.File.read("integrate.ini");
var critic_url = ini.decode().match(/critic_url\s*=\s*(\S*)/);
return "[r/" + id + "|" + critic_url[1] + "r/" + id + "]";
}
function add_jira_comment(review, branch, sha1_update) {
if(!critic.bts) {
return "no bts";
}
var issues = {};
review.branch.commits.forEach(function(commit) {
try {
var issue = null;
var match = commit.summary.match(/^([A-Z]{2,7}-\d+):.*/);
if( match ) {
if(!issues[match[1]]) {
issues[match[1]] = true;
var rlink = review_link(review.id);
var repo = review.repository.name;
var cgit = cgit_range(repo, branch, sha1_update);
issue = new critic.bts.Issue(match[1]);
if( cgit ) {
issue.addComment("Review " + rlink + " merged to " + branch + " in " + cgit + " in repository " + repo + ".");
} else {
issue.addComment("Review " + rlink + " merged to " + branch + " in repository " + repo + ".");
}
}
}
} catch(error) {
console.log(error);
}
});
return "jira updated";
}
function push() {
var data = JSON.parse(read());
writeln("200");
writeln("Content-Type: text/json");
writeln("");
try {
var review = new critic.Review(data.review_id);
var branch = data.branch;
// Should already have been checked - but verify
if( ! review.progress.accepted ) {
throw "Review is not accepted";
}
// Ready to try to push
// Assume remote is already setup in candidates()
var wc = review.branch.getWorkCopy();
var rnote = "Review-info: Reviewed in r/" + review.id + " by " + reviewers(review);
// Could have checked for existing notes here, but it seem that pushing notes
// to critic are rejected anyway.
wc.run("config", "user.email", critic.User.current.email);
wc.run("config", "user.name", critic.User.current.fullname);
// Add the note to each commit in the branch
review.branch.commits.forEach(function(commit) {
try {
wc.run("notes", "add", "-m", rnote, commit.sha1);
} catch (error) {
// This is fine - a note was already added / assumed by us
}
});
wc.run("push", "target", "refs/notes/*");
var out = wc.run("push", "target", "--porcelain", "HEAD:refs/heads/" + branch);
var sha1_update = null;
if( out.split('\n')[1][0] == '=' ) {
throw "Already integrated!";
} else {
sha1_update = out.match(/([a-f,0-9]{7,})\.\.([a-f,0-9]{7,})/);
}
var jira_info = add_jira_comment(review, branch, sha1_update);
var new_batch = review.startBatch();
new_batch.writeNote("[Integrate] Changes merged to " + branch);
new_batch.finish();
review.close();
writeln(JSON.stringify({ status: "ok", jira: String(jira_info) }));
} catch (error) {
if( error instanceof critic.CriticError ) {
throw error;
}
writeln(JSON.stringify({ status: "failure",
code: "willnotpush",
title: "Will not push to " + branch,
message: String(error) + "<br><br>" + error.stack}));
}
}
function rstore() {
// The values are not per user - but the api require a user
// so always store the data for user 1.
return new critic.Storage(new critic.User(1));
}
function get_target_remote(review) {
var refname = rstore().get('refbranch:' + review.repository.name);
if(refname == undefined) {
refname = "master";
}
var data = { branch: review.repository.getBranch(refname) };
return critic.TrackedBranch.find(data).remote;
}
function add_remote(review) {
var wc = review.branch.getWorkCopy();
var remotes = wc.run("remote");
if( remotes.split("\n").indexOf("target") != -1 ) {
wc.run("remote", "remove", "target");
}
var remote_base = get_target_remote(review);
wc.run("remote", "add", "target", remote_base);
wc.run("fetch", "target");
wc.run("fetch", "target", "refs/notes/*:refs/notes/*");
return wc
}
function can_push() {
var data = JSON.parse(read());
writeln("200");
writeln("Content-Type: text/json");
writeln("");
try {
var review = new critic.Review(data.review_id);
var branch = data.branch;
// Should already have been checked - but verify
if( ! review.progress.accepted ) {
throw "Review is not accepted";
}
// Add target and check if the review branch is based on it
var wc = review.branch.getWorkCopy();
var merge_base = wc.run("merge-base", "HEAD", "target/" + branch);
var target_branch = wc.run("rev-parse", "target/" + branch);
writeln(JSON.stringify({ status: "ok",
integratable: merge_base == target_branch,
branch: branch }));
} catch (error) {
if( error instanceof critic.CriticError ) {
throw error;
}
writeln(JSON.stringify({ status: "failure",
code: "willnotpush",
title: "Will not push to " + branch,
message: String(error) }));
}
}
// Try to guess what target branches might be possible
function candidates() {
var data = JSON.parse(read());
writeln("200");
writeln("Content-Type: text/json");
writeln("");
try {
var review = new critic.Review(data.review_id);
var commits = review.commits;
// Add the target remote and fetch
var wc = add_remote(review);
// There can be more tails if we have rebased. I did not find a way to
// reliably know what is the latest one - but we can find it with the
// json api and is passed here as data.rebase.
// We also try each tail to find all possibly candidates.
var sha1s = [];
if(data.rebase) {
sha1s.push(data.rebase);
}
commits.tails.forEach(function(tail) { sha1s.push(tail.sha1); });
var all_branches = {};
sha1s.forEach(function(sha1) {
// List all branch names at target that contain the tail of the review
// This indicates that the review might have branched off from there
// and that it then is a likely candidate for integrating back to.
// Note: --contains is only supported in git >= 2.7
var branches = "";
try {
var branches = wc.run("for-each-ref", "--format=%(refname:strip=3)",
"--contains", sha1, "refs/remotes/target").trim();
} catch (error) {
// FIXME: The above sometimes fails due to missing sha1. Unsure why.
// Until this is debugged or turns out to be a real issue just ignore it.
}
branches = branches.split('\n');
branches.forEach(function(b) {
if(b.length) {
all_branches[b] = true;
}
});
});
all_branches = Object.keys(all_branches);
// If master is in the list - we assume the most likely candidate is master
// At the master level there can be a huge number of irrelevant branches
// 5 is a arbitrarily chosen cut-off to limit the case with irrelevant branches.
if( all_branches.indexOf('master') != -1 && all_branches.length > 10) {
all_branches = ['master'];
}
if( all_branches.length == 0 || all_branches[0].length == 0) {
throw "Could not find a candidate branch - not even master!";
}
writeln(JSON.stringify({ status: "ok", branches: all_branches }));
} catch (error) {
if( error instanceof critic.CriticError ) {
throw error;
}
writeln(JSON.stringify({ status: "failure",
code: "willnotpush",
title: "Will not integrate",
message: String(error) }));
}
}