forked from BranchMetrics/xcode-github
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathXGCommand.m
More file actions
297 lines (260 loc) · 9.54 KB
/
XGCommand.m
File metadata and controls
297 lines (260 loc) · 9.54 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
289
290
291
292
293
294
295
296
297
/**
@file XGCommand.m
@package xcode-github
@brief Main body of the xcode-github app.
@author Edward Smith
@date April 24, 2018
@copyright Copyright © 2018 Branch. All rights reserved.
*/
#import "XGCommand.h"
#import "XGXcodeBot.h"
#import "XGGitHubPullRequest.h"
#import "XGSettings.h"
#import "BNCLog.h"
#import "BNCNetworkService.h"
#include <sysexits.h>
#pragma mark Bot Functions
NSError*_Nullable XGCreateBotWithOptions(
XGCommandOptions*_Nonnull options,
XGGitHubPullRequest*_Nonnull pr,
XGXcodeBot*_Nonnull templateBot,
NSString*_Nonnull newBotName
) {
if (options.dryRun) {
BNCLog(@"Would create bot '%@'.", newBotName);
return nil;
}
NSError *error = nil;
BNCLogDebug(@"Creating bot '%@'...", newBotName);
[pr setStatus:XGPullRequestStatusPending
message:@"Creating Xcode bot..."
statusURL:nil];
[templateBot duplicateBotWithNewName:newBotName
branchName:pr.branch
gitHubPullRequestNumber:pr.number
gitHubPullRequestTitle:pr.title
error:&error];
if (error) {
if (error.code == -999) {
BNCLogError(@"Can't create Xcode bot, permission error: %@.", error);
} else {
BNCLogError(@"Can't create Xcode bot: %@.", error);
}
}
return error;
}
NSError*_Nullable XGDeleteBotWithOptions(
XGCommandOptions*_Nonnull options,
XGXcodeBot*_Nonnull bot
) {
NSError*error = nil;
if (options.dryRun) {
BNCLog(@"Would delete bot '%@'.", bot.name);
return error;
}
BNCLogDebug(@"Deleting old bot '%@'...", bot.name);
error = [bot deleteBot];
if (error) {
BNCLogError(
@"Can't remove old bot named '%@' from server: %@.", bot.name, error
);
return error;
}
[[XGSettings sharedSettings]
deleteGitHubStatusForRepoOwner:bot.repoOwner
repoName:bot.repoName
branch:bot.branch];
return error;
}
NSError*_Nullable XGUpdatePRStatusOnGitHub(
XGCommandOptions*_Nonnull options,
XGGitHubPullRequest*_Nonnull pr,
XGXcodeBotStatus*_Nonnull botStatus
) {
NSError*error = nil;
XGPullRequestStatus status = XGPullRequestStatusError;
/*
XGXcodeBotStatus:
XGPullRequestStatusError,
XGPullRequestStatusFailure,
XGPullRequestStatusPending,
XGPullRequestStatusSuccess,
*/
NSSet<NSString*>*failureResults = [NSSet setWithArray:@[
@"build-errors",
@"test-failures",
@"build-failed",
@"canceled",
]];
NSSet<NSString*>*successResults = [NSSet setWithArray:@[
@"succeeded",
@"warnings",
@"analyzer-warnings",
]];
if ([botStatus.currentStep isEqualToString:@"completed"]) {
if (botStatus.result == nil) {
} else
if ([successResults containsObject:botStatus.result]) {
status = XGPullRequestStatusSuccess;
} else
if ([failureResults containsObject:botStatus.result]) {
status = XGPullRequestStatusFailure;
} else {
status = XGPullRequestStatusError;
}
} else {
status = XGPullRequestStatusPending;
}
NSString*message = botStatus.summaryString;
NSString*statusHash = [NSString stringWithFormat:@"%@:%@",
NSStringFromXGPullRequestStatus(status), message];
NSString*lastStatusHash =
[[XGSettings sharedSettings]
gitHubStatusForRepoOwner:pr.repoOwner
repoName:pr.repoName
branch:pr.branch];
if (lastStatusHash == nil) {
// Get the most recent status from GitHub:
XGGitHubPullRequestStatus *status = [[pr statusesWithError:nil] firstObject];
if (status) {
lastStatusHash = [NSString stringWithFormat:@"%@:%@",
NSStringFromXGPullRequestStatus(status.status), status.message];
[[XGSettings sharedSettings]
setGitHubStatus:lastStatusHash
forRepoOwner:pr.repoOwner
repoName:pr.repoName
branch:pr.branch];
}
}
if ([lastStatusHash isEqualToString:statusHash])
return nil;
if (options.dryRun) {
BNCLog(@"Would update PR#%@ with status %@: %@.",
pr.number, NSStringFromXGPullRequestStatus(status), message);
return nil;
}
error = [pr setStatus:status
message:message
statusURL:nil];
if (error) return error;
// Add a completion message to the PR:
if ([botStatus.currentStep isEqualToString:@"completed"]) {
error = [pr addComment:[[botStatus formatDetailString:options.successMessage :options.failureMessage :options.perfectMessage] renderMarkDown]];
if (error) return error;
}
[[XGSettings sharedSettings]
setGitHubStatus:statusHash
forRepoOwner:pr.repoOwner
repoName:pr.repoName
branch:pr.branch];
return nil;
}
NSError* XGShowXcodeBotStatus(XGCommandOptions* options) {
// Update the bots and display the results:
XGServer*xcodeServer = [[XGServer alloc] init];
xcodeServer.server = options.xcodeServerName;
xcodeServer.user = options.xcodeServerUser;
xcodeServer.password = options.xcodeServerPassword;
// Allow self-signed certs from the xcode server:
[BNCNetworkService shared].allowAnySSLCert = YES;
BNCLogDebug(@"Refreshing Xcode bot status...");
NSError *error = nil;
NSDictionary<NSString*, XGXcodeBot*> *bots = [XGXcodeBot botsForServer:xcodeServer error:&error];
if (error) {
BNCLogError(@"Can't retrieve Xcode bot information from '%@': %@.",
xcodeServer.server, error);
return error;
}
if (bots.count == 0) {
BNCLog(@"Xcode bot status: No Xcode bots.");
} else {
BNCLog(@"Xcode bot status:");
for (XGXcodeBot *bot in bots.objectEnumerator) {
XGXcodeBotStatus *status = [bot status];
BNCLog(@"%@", status);
}
}
return nil;
}
#pragma mark - Main Function
NSError*_Nullable XGUpdateXcodeBotsWithGitHub(XGCommandOptions*_Nonnull options) {
NSError *error = nil;
int returnCode = EXIT_FAILURE;
{
XGServer*xcodeServer = [[XGServer alloc] init];
xcodeServer.server = options.xcodeServerName;
xcodeServer.user = options.xcodeServerUser;
xcodeServer.password = options.xcodeServerPassword;
// Allow self-signed certs from the xcode server:
[BNCNetworkService shared].allowAnySSLCert = YES;
BNCLogDebug(@"Getting Xcode bots on '%@'...", options.xcodeServerName);
NSDictionary<NSString*, XGXcodeBot*> *bots =
[XGXcodeBot botsForServer:xcodeServer error:&error];
if (error) {
BNCLogError(@"Can't retrieve Xcode bot information from %@: %@.",
options.xcodeServerName, error);
returnCode = EX_NOHOST;
goto exit;
}
if (options.showStatusOnly) {
error = XGShowXcodeBotStatus(options);
if (!error) returnCode = EXIT_SUCCESS;
goto exit;
}
// Check that the template bot exists:
XGXcodeBot *templateBot = bots[options.templateBotName];
if (!templateBot) {
BNCLogError(@"Can't find Xcode template bot named '%@'.", options.templateBotName);
returnCode = EX_CONFIG;
goto exit;
}
BNCLogDebug(@"Getting pull requests for '%@'...", templateBot.sourceControlRepository);
NSDictionary<NSString*, XGGitHubPullRequest*> *pullRequests =
[XGGitHubPullRequest pullsRequestsForRepository:templateBot.sourceControlRepository
authToken:options.githubAuthToken
error:&error];
if (error) {
BNCLogError(@"Can't retrieve pull requests from '%@': %@.",
templateBot.sourceControlRepository, error);
returnCode = EX_NOHOST;
goto exit;
}
// Check for open pull requests with state 'open':
for (XGGitHubPullRequest *pr in pullRequests.objectEnumerator) {
NSString *newBotName = [XGXcodeBot botNameFromPRNumber:pr.number title:pr.title];
XGXcodeBot *bot = bots[newBotName];
if ([pr.state isEqualToString:@"open"]) {
if (bot) {
error = XGUpdatePRStatusOnGitHub(options, pr, bot.status);
} else {
error = XGCreateBotWithOptions(options, pr, templateBot, newBotName);
}
if (error) {
returnCode = EX_NOPERM;
goto exit;
}
}
}
// Check for bots with no PR and delete it:
for (XGXcodeBot *bot in bots.objectEnumerator) {
NSString *number = bot.pullRequestNumber;
if (number && !pullRequests[number]) {
error = XGDeleteBotWithOptions(options, bot);
if (error) { returnCode = EX_NOPERM; goto exit; }
}
}
error = nil;
returnCode = EXIT_SUCCESS;
}
exit:
if (returnCode != EXIT_SUCCESS) {
if (!error) error = [NSError errorWithDomain:NSMachErrorDomain code:KERN_FAILURE userInfo:nil];
NSMutableDictionary *userInfo =
([error.userInfo isKindOfClass:NSDictionary.class])
? [error.userInfo mutableCopy]
: [NSMutableDictionary new];
userInfo[@"return_code"] = @(returnCode);
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
}
return error;
}