From 02a1142400a2620f53c700bab2b51e8176d33750 Mon Sep 17 00:00:00 2001 From: Francois Kritzinger Date: Fri, 24 May 2024 09:38:04 +0200 Subject: Fetch pull request mergeability --- mod/mod-ci-github-gq.cxx | 142 +++++++++++++++++++++++++++++++++++++++++++++++ mod/mod-ci-github-gq.hxx | 22 ++++++++ 2 files changed, 164 insertions(+) (limited to 'mod') diff --git a/mod/mod-ci-github-gq.cxx b/mod/mod-ci-github-gq.cxx index 954f5a8..54822ae 100644 --- a/mod/mod-ci-github-gq.cxx +++ b/mod/mod-ci-github-gq.cxx @@ -557,6 +557,148 @@ namespace brep return r; } + // Serialize a GraphQL query that fetches a pull request from GitHub. + // + static string + gq_query_pr_mergeability (const string& nid) + { + ostringstream os; + + os << "query {" << '\n' + << " node(id:" << gq_str (nid) << ") {" << '\n' + << " ... on PullRequest {" << '\n' + << " mergeable potentialMergeCommit { oid }" << '\n' + << " }" << '\n' + << " }" << '\n' + << "}" << '\n'; + + return os.str (); + } + + pair, bool> + gq_pull_request_mergeable (const basic_mark& error, + const string& iat, + const string& nid) + { + string rq (gq_serialize_request (gq_query_pr_mergeability (nid))); + + try + { + // Response parser. + // + struct resp + { + // True if the pull request was found (i.e., the node ID was valid). + // + bool found = false; + + // The response value. Absent if the merge commit is still being + // generated. + // + optional value; + + resp (json::parser& p) + { + using event = json::event; + + gq_parse_response (p, [this] (json::parser& p) + { + p.next_expect (event::begin_object); + + if (p.next_expect_member_object_null ("node")) + { + found = true; + + string ma (p.next_expect_member_string ("mergeable")); + + if (ma == "MERGEABLE") + { + p.next_expect_member_object ("potentialMergeCommit"); + string oid (p.next_expect_member_string ("oid")); + p.next_expect (event::end_object); + + value = {true, move (oid)}; + } + else if (ma == "CONFLICTING") + { + value = {false, ""}; + } + else if (ma == "UNKNOWN") + { + // Still being generated; leave value absent. + } + + p.next_expect (event::end_object); // node + } + + p.next_expect (event::end_object); + }); + } + + resp () = default; + } rs; + + uint16_t sc (github_post (rs, + "graphql", // API Endpoint. + strings {"Authorization: Bearer " + iat}, + move (rq))); + + if (sc == 200) + { + if (!rs.found) + error << "pull request '" << nid << "' not found"; + + return {move (rs.value), rs.found}; + } + else + error << "failed to fetch pull request: error HTTP response status " + << sc; + } + catch (const json::invalid_json_input& e) + { + // Note: e.name is the GitHub API endpoint. + // + error << "malformed JSON in response from " << e.name << ", line: " + << e.line << ", column: " << e.column << ", byte offset: " + << e.position << ", error: " << e; + } + catch (const invalid_argument& e) + { + error << "malformed header(s) in response: " << e; + } + catch (const system_error& e) + { + error << "unable to fetch pull request (errno=" << e.code () << "): " + << e.what (); + } + catch (const runtime_error& e) // From response type's parsing constructor. + { + // GitHub response contained error(s) (could be ours or theirs at this + // point). + // + error << "unable to fetch pull request: " << e; + } + + return {nullopt, false}; + } + + // bool + // gq_fetch_branch_open_pull_requests () + // { + // // query { + // // repository(owner:"francoisk" name:"libb2") + // // { + // // pullRequests (last:100 states:OPEN baseRefName:"master") { + // // edges { + // // node { + // // id + // // } + // // } + // // } + // // } + // // } + // } + // GraphQL serialization functions. // // The GraphQL spec: diff --git a/mod/mod-ci-github-gq.hxx b/mod/mod-ci-github-gq.hxx index 8f7a9ca..7a15b5e 100644 --- a/mod/mod-ci-github-gq.hxx +++ b/mod/mod-ci-github-gq.hxx @@ -78,6 +78,28 @@ namespace brep const string& details_url, build_state, optional = nullopt); + + // Fetch a pull request's mergeability from GitHub and return it in first, + // or absent if the merge commit is still being generated. + // + // Return false in second and issue diagnostics if the request failed. + // + struct gq_pr_mergeability + { + // True if the pull request is auto-mergeable; false if it would create + // conflicts. + // + bool mergeable; + + // The ID of the test merge commit. Empty if mergeable is false. + // + string merge_commit_id; + }; + + pair, bool> + gq_pull_request_mergeable (const basic_mark& error, + const string& installation_access_token, + const string& node_id); } #endif // MOD_MOD_CI_GITHUB_GQ_HXX -- cgit v1.1