Skip to content

Commit 378757c

Browse files
authored
Merge pull request #158 from wilzbach/improve-merge
Avoid any action on 'mergeable: null'
2 parents d85dafe + a64744f commit 378757c

File tree

6 files changed

+121
-7
lines changed

6 files changed

+121
-7
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ matrix:
2424
after_success:
2525
- bash <(curl -s https://codecov.io/bash)
2626
allow_failures:
27+
- d: dmd
2728
- d: dmd-nightly
2829
- d: dmd-beta

source/dlangbot/app.d

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,8 @@ void githubHook(HTTPServerRequest req, HTTPServerResponse res)
139139

140140
//==============================================================================
141141

142-
void cronDaily(string[] repositories, bool simulate)
142+
void cronDaily(string[] repositories, CronConfig config)
143143
{
144-
CronConfig config = {simulate: simulate};
145144
auto actions = [
146145
&detectStalledPR,
147146
&detectInactiveStablePR,
@@ -285,7 +284,10 @@ else void main(string[] args)
285284
}
286285

287286
if (cronRepositories)
288-
return cronDaily(cronRepositories, runDailyCronSimulation);
287+
{
288+
CronConfig config = {simulate: runDailyCronSimulation};
289+
return cronDaily(cronRepositories, config);
290+
}
289291

290292
startServer(settings);
291293
lowerPrivileges();

source/dlangbot/cron.d

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct CronConfig
2020
Duration stalledPRs = 90.days; // PRs with no activity within X days will get flagged
2121
Duration oldStablePRs = 3.days; // PRs targeting stable which should trigger a warning
2222
bool simulate;
23+
Duration waitAfterMergeNullState = 750.msecs; // Time to wait before refetching a PR with mergeable: null
2324
}
2425

2526
alias PRTuple = Tuple!(PullRequest, "pr", GHComment[], "comments", GHComment[], "reviewComments", CronConfig, "config");
@@ -73,8 +74,51 @@ auto detectInactiveStablePR(PRTuple t)
7374

7475
auto detectPRWithMergeConflicts(PRTuple t)
7576
{
77+
if (t.pr.mergeable.isNull)
78+
{
79+
logInfo("[cron-daily/%s/%d]: detectMerge - mergeable is null.", t.pr.repoSlug, t.pr.number);
80+
// repeat request to receive computed mergeable information
81+
foreach (i; 0 .. 4)
82+
{
83+
import vibe.core.core : sleep;
84+
t.config.waitAfterMergeNullState.sleep;
85+
logInfo("[cron-daily/%s/%d]: detectMerge - repeating request", t.pr.repoSlug, t.pr.number);
86+
t.pr = t.pr.refresh;
87+
if (!t.pr.mergeable.isNull)
88+
goto mergable;
89+
}
90+
return LabelResponse(LabelAction.none, "");
91+
}
92+
mergable:
93+
94+
bool isMergeable;
95+
if (!t.pr.mergeableState.isNull)
96+
{
97+
logInfo("[cron-daily/%s/%d]: mergableState = %s", t.pr.repoSlug, t.pr.number, t.pr.mergeableState.get);
98+
with (PullRequest.MergeableState)
99+
final switch(t.pr.mergeableState)
100+
{
101+
case unknown, checking:
102+
// should only be set if mergeable is null
103+
return LabelResponse(LabelAction.none, "");
104+
case clean, unstable:
105+
isMergeable = true;
106+
break;
107+
case dirty:
108+
isMergeable = false;
109+
break;
110+
// a reviewer has blocked the merge (not observed yet)
111+
case blocked:
112+
return LabelResponse(LabelAction.none, "");
113+
}
114+
}
115+
else
116+
{
117+
logInfo("[cron-daily/%s/%d]: mergable = %s", t.pr.repoSlug, t.pr.number, t.pr.mergeable.get);
118+
isMergeable = t.pr.mergeable.get;
119+
}
120+
76121
// "needs rebase" gets automatically removed on a new push
77-
bool isMergeable = !t.pr.mergeable.isNull && t.pr.mergeable.get;
78122
with(LabelAction)
79123
return LabelResponse(!isMergeable ? add : remove, "needs rebase");
80124
}

source/dlangbot/github_api.d

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,13 @@ struct PullRequest
136136
Nullable!Repo repo;
137137
}
138138
Branch base, head;
139-
enum MergeableState { clean, dirty, unstable, blocked, unknown }
139+
enum MergeableState { checking, clean, dirty, unstable, blocked, unknown }
140140
@byName GHState state;
141141
uint number;
142+
string url;
142143
string title;
143144
@optional Nullable!bool mergeable;
145+
// https://platform.github.community/t/documentation-about-mergeable-state/4259
144146
@optional @byName @name("mergeable_state") Nullable!MergeableState mergeableState;
145147
@name("created_at") SysTime createdAt;
146148
@name("updated_at") SysTime updatedAt;
@@ -223,6 +225,12 @@ struct PullRequest
223225
req.writeJsonBody(merge);
224226
}, mergeURL);
225227
}
228+
229+
typeof(this) refresh() {
230+
return ghGetRequest(url)
231+
.readJson
232+
.deserializeJson!(typeof(this));
233+
}
226234
}
227235

228236
static struct GHUser

test/cronjob.d

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,58 @@ unittest
8484

8585
testCronDaily(repositories);
8686
}
87+
88+
// test that no label updates are sent if no activity was found
89+
unittest
90+
{
91+
import std.datetime : Clock, days;
92+
setAPIExpectations(
93+
"/github/repos/dlang/phobos/issues?state=open&sort=updated&direction=asc", (ref Json j) {
94+
// only test one pull request
95+
j = Json([j[0]]);
96+
},
97+
"/github/repos/dlang/phobos/pulls/2526", (ref Json j) {
98+
j["mergeable"] = false;
99+
},
100+
"/github/repos/dlang/phobos/status/a04acd6a2813fb344d3e47369cf7fd64523ece44",
101+
"/github/repos/dlang/phobos/issues/2526/comments", (ref Json j) {
102+
j[$ - 1]["created_at"] = (Clock.currTime - 2.days).toISOExtString;
103+
j[$ - 1]["updated_at"] = (Clock.currTime - 2.days).toISOExtString;
104+
},
105+
"/github/repos/dlang/phobos/pulls/2526/comments",
106+
);
107+
108+
testCronDaily(repositories);
109+
}
110+
111+
// test that the merge state gets refreshed
112+
unittest
113+
{
114+
import std.datetime : Clock, days;
115+
setAPIExpectations(
116+
"/github/repos/dlang/phobos/issues?state=open&sort=updated&direction=asc", (ref Json j) {
117+
// only test one pull request
118+
j = Json([j[0]]);
119+
},
120+
"/github/repos/dlang/phobos/pulls/2526", (ref Json j) {
121+
j["mergeable"] = null;
122+
},
123+
"/github/repos/dlang/phobos/pulls/2526", (ref Json j) {
124+
j["mergeable"] = false;
125+
j["mergeable_state"] = "dirty";
126+
},
127+
"/github/repos/dlang/phobos/status/a04acd6a2813fb344d3e47369cf7fd64523ece44",
128+
"/github/repos/dlang/phobos/issues/2526/comments", (ref Json j) {
129+
j[$ - 1]["created_at"] = (Clock.currTime - 2.days).toISOExtString;
130+
j[$ - 1]["updated_at"] = (Clock.currTime - 2.days).toISOExtString;
131+
},
132+
"/github/repos/dlang/phobos/pulls/2526/comments",
133+
"/github/repos/dlang/phobos/issues/2526/labels",
134+
(scope HTTPServerRequest req, scope HTTPServerResponse res){
135+
assert(req.method == HTTPMethod.PUT);
136+
assert(req.json[].map!(e => e.get!string).equal(["blocked", "needs rebase"]));
137+
},
138+
);
139+
140+
testCronDaily(repositories);
141+
}

test/utils.d

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,14 @@ void openUrl(string url, string expectedResponse,
321321
void testCronDaily(string[] repositories, int line = __LINE__, string file = __FILE__)
322322
{
323323
import dlangbot.app : cronDaily;
324-
bool simulate = false;
324+
import dlangbot.cron : CronConfig;
325325

326326
logInfo("Starting cron test in %s:%d", file, line);
327327

328-
cronDaily(repositories, simulate);
328+
CronConfig config = {
329+
simulate: false,
330+
waitAfterMergeNullState: 1.msecs,
331+
};
332+
cronDaily(repositories, config);
329333
checkAPIExpectations;
330334
}

0 commit comments

Comments
 (0)