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 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) (limited to 'mod/mod-ci-github-gq.cxx') 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: -- cgit v1.1