11module dlangbot.github ;
22
3- string githubAPIURL = " https://api.github.com" ;
4- string githubAuth, hookSecret;
5-
63import dlangbot.bugzilla : bugzillaURL, Issue, IssueRef;
74import dlangbot.warnings : printMessages, UserMessage;
85
@@ -13,9 +10,8 @@ import std.typecons : Tuple;
1310
1411import vibe.core.log ;
1512import vibe.data.json;
16- import vibe.http.client : HTTPClientRequest, requestHTTP;
17- import vibe.http.common : HTTPMethod;
18- import vibe.stream.operations : readAllUTF8;
13+
14+ public import dlangbot.github_api;
1915
2016// ==============================================================================
2117// Github comments
@@ -108,50 +104,6 @@ GHComment getBotComment(in ref PullRequest pr)
108104 return GHComment ();
109105}
110106
111- auto ghGetRequest (string url)
112- {
113- return requestHTTP (url, (scope req) {
114- req.headers[" Authorization" ] = githubAuth;
115- });
116- }
117-
118- auto ghGetRequest (scope void delegate (scope HTTPClientRequest req) userReq, string url)
119- {
120- return requestHTTP (url, (scope req) {
121- req.headers[" Authorization" ] = githubAuth;
122- userReq(req);
123- });
124- }
125-
126- auto ghSendRequest (scope void delegate (scope HTTPClientRequest req) userReq, string url)
127- {
128- HTTPMethod method;
129- requestHTTP(url, (scope req) {
130- req.headers[" Authorization" ] = githubAuth;
131- userReq(req);
132- method = req.method;
133- }, (scope res) {
134- if (res.statusCode / 100 == 2 )
135- {
136- logInfo(" %s %s, %s\n " , method, url, res.statusPhrase);
137- res.bodyReader.readAllUTF8;
138- }
139- else
140- logWarn(" %s %s failed; %s %s.\n %s" , method, url,
141- res.statusPhrase, res.statusCode, res.bodyReader.readAllUTF8);
142- });
143- }
144-
145- auto ghSendRequest (T... )(HTTPMethod method, string url, T arg)
146- if (T.length <= 1 )
147- {
148- return ghSendRequest ((scope req) {
149- req.method = method;
150- static if (T.length)
151- req.writeJsonBody(arg);
152- }, url);
153- }
154-
155107void updateGithubComment (in ref PullRequest pr, in ref GHComment comment,
156108 string action, IssueRef[] refs, Issue[] descs, UserMessage[] msgs)
157109{
@@ -177,7 +129,6 @@ void updateGithubComment(in ref PullRequest pr, in ref GHComment comment,
177129// ==============================================================================
178130
179131alias LabelsAndCommits = Tuple ! (Json[], " labels" , Json[], " commits" );
180- enum MergeMethod { none = 0 , merge, squash, rebase }
181132
182133string labelName (MergeMethod method)
183134{
@@ -357,35 +308,6 @@ void checkTitleForLabels(in ref PullRequest pr)
357308// Search for inactive PRs
358309// ==============================================================================
359310
360- // range-based page loader for the GH API
361- private struct AllPages
362- {
363- private string url;
364- private string link = " next" ;
365-
366- // does not cache
367- Json front () {
368- scope req = ghGetRequest(url);
369- link = req.headers.get (" Link" );
370- return req.readJson;
371- }
372- void popFront ()
373- {
374- import std.utf : byCodeUnit;
375- if (link)
376- url = link[1 .. $].byCodeUnit.until(" >" ).array;
377- }
378- bool empty ()
379- {
380- return ! link.canFind(" next" );
381- }
382- }
383-
384- auto ghGetAllPages (string url)
385- {
386- return AllPages (url);
387- }
388-
389311void searchForInactivePrs (string repoSlug, Duration dur)
390312{
391313 auto now = Clock .currTime;
@@ -467,202 +389,3 @@ void searchForInactivePrs(string repoSlug, Duration dur)
467389 }
468390 logInfo(" ended cron.daily for repo: %s (pages: %d)" , repoSlug, loadedPages);
469391}
470-
471- // ==============================================================================
472- // Github API objects
473- // ==============================================================================
474-
475- struct PullRequest
476- {
477- import std.typecons : Nullable;
478-
479- static struct Repo
480- {
481- @name(" full_name" ) string fullName;
482- GHUser owner;
483- }
484- static struct Branch
485- {
486- string sha;
487- string ref_;
488- Repo repo;
489- }
490- Branch base, head;
491- enum State { open, closed }
492- enum MergeableState { clean, dirty, unstable, blocked, unknown }
493- @byName State state;
494- uint number;
495- string title;
496- @optional Nullable! bool mergeable;
497- @optional @byName Nullable! MergeableState mergeable_state;
498- @name(" created_at" ) SysTime createdAt;
499- @name(" updated_at" ) SysTime updatedAt;
500- bool locked;
501-
502- GHUser user;
503- Nullable! GHUser assignee;
504- GHUser[] assignees;
505-
506- string baseRepoSlug () const { return base.repo.fullName; }
507- string headRepoSlug () const { return head.repo.fullName; }
508- alias repoSlug = baseRepoSlug;
509- bool isOpen () const { return state == State.open; }
510-
511- string htmlURL () const { return " https://github.com/%s/pull/%d" .format(repoSlug, number); }
512- string commentsURL () const { return " %s/repos/%s/issues/%d/comments" .format(githubAPIURL, repoSlug, number); }
513- string commitsURL () const { return " %s/repos/%s/pulls/%d/commits" .format(githubAPIURL, repoSlug, number); }
514- string eventsURL () const { return " %s/repos/%s/issues/%d/events" .format(githubAPIURL, repoSlug, number); }
515- string labelsURL () const { return " %s/repos/%s/issues/%d/labels" .format(githubAPIURL, repoSlug, number); }
516- string reviewsURL () const { return " %s/repos/%s/pulls/%d/reviews" .format(githubAPIURL, repoSlug, number); }
517- string mergeURL () const { return " %s/repos/%s/pulls/%d/merge" .format(githubAPIURL, repoSlug, number); }
518- string statusURL () const { return " %s/repos/%s/status/%s" .format(githubAPIURL, repoSlug, head.sha); }
519- string membersURL () const { return " %s/orgs/%s/public_members" .format(githubAPIURL, base.repo.owner.login); }
520-
521- string pid () const
522- {
523- import std.conv : text;
524- return text (repoSlug, " /" , number);
525- }
526-
527- GHComment[] comments () const {
528- return ghGetRequest (commentsURL)
529- .readJson
530- .deserializeJson! (GHComment[]);
531- }
532- GHCommit[] commits () const {
533- return ghGetRequest (commitsURL)
534- .readJson
535- .deserializeJson! (GHCommit[]);
536- }
537- GHReview[] reviews () const {
538- return ghGetRequest ((scope req) {
539- // custom media type is required during preview period:
540- // preview review api: https://developer.github.com/changes/2016-12-14-reviews-api
541- req.headers[" Accept" ] = " application/vnd.github.black-cat-preview+json" ;
542- }, reviewsURL)
543- .readJson
544- .deserializeJson! (GHReview[]);
545- }
546- GHCiStatus[] status () const {
547- return ghGetRequest (statusURL)
548- .readJson[" statuses" ]
549- .deserializeJson! (GHCiStatus[]);
550- }
551-
552- void postMerge (in ref GHMerge merge) const
553- {
554- ghSendRequest((scope req){
555- req.method = HTTPMethod.PUT ;
556- // custom media type is required during preview period:
557- // https://developer.github.com/changes/2016-09-26-pull-request-merge-api-update/
558- req.headers[" Accept" ] = " application/vnd.github.polaris-preview+json" ;
559- req.writeJsonBody(merge);
560- }, mergeURL);
561- }
562- }
563-
564- static struct GHUser
565- {
566- string login;
567- ulong id;
568- @name(" avatar_url" ) string avatarURL;
569- @name(" gravatar_id" ) string gravatarId;
570- string type;
571- @name(" site_admin" ) bool siteAdmin;
572- }
573-
574- struct GHComment
575- {
576- @name(" created_at" ) SysTime createdAt;
577- @name(" updated_at" ) SysTime updatedAt;
578- GHUser user;
579- string body_;
580- string url;
581-
582- static void post (in ref PullRequest pr, string msg)
583- {
584- ghSendRequest(HTTPMethod.POST , pr.commentsURL, [" body" : msg]);
585- }
586-
587- void update (string msg) const
588- {
589- ghSendRequest(HTTPMethod.PATCH , url, [" body" : msg]);
590- }
591-
592- void remove () const
593- {
594- if (url.length) // delete any existing comment
595- ghSendRequest(HTTPMethod.DELETE , url);
596- }
597- }
598-
599- struct GHReview
600- {
601- GHUser user;
602- @name(" commit_id" ) string commitId;
603- string body_;
604- enum State { APPROVED , CHANGES_REQUESTED , COMMENTED }
605- @byName State state;
606- }
607-
608- struct GHCommit
609- {
610- string sha;
611- static struct CommitAuthor
612- {
613- string name;
614- string email;
615- SysTime date;
616- }
617- static struct Commit
618- {
619- CommitAuthor author;
620- CommitAuthor committer;
621- string message;
622- }
623- Commit commit;
624- GHUser author;
625- GHUser committer;
626- }
627-
628- struct GHCiStatus
629- {
630- enum State { success, error, failure, pending }
631- @byName State state;
632- string description;
633- @name(" target_url" ) string targetUrl;
634- string context; // "CyberShadow/DAutoTest", "Project Tester",
635- // "ci/circleci", "auto-tester", "codecov/project",
636- // "codecov/patch", "continuous-integration/travis-ci/pr"
637- }
638-
639- struct GHMerge
640- {
641- @name(" commit_message" ) string commitMessage;
642- string sha;
643- @name(" merge_method" ) @byName MergeMethod mergeMethod;
644- }
645-
646- // ==============================================================================
647- // Github hook signature
648- // ==============================================================================
649-
650- auto getSignature (string data)
651- {
652- import std.digest.digest , std.digest.hmac , std.digest.sha ;
653- import std.string : representation;
654-
655- auto hmac = HMAC ! SHA1 (hookSecret.representation);
656- hmac.put(data.representation);
657- return hmac.finish.toHexString! (LetterCase.lower);
658- }
659-
660- Json verifyRequest (string signature, string data)
661- {
662- import std.exception : enforce;
663- import std.string : chompPrefix;
664-
665- enforce(getSignature(data) == signature.chompPrefix(" sha1=" ),
666- " Hook signature mismatch" );
667- return parseJsonString (data);
668- }
0 commit comments