diff options
author | Francois Kritzinger <francois@codesynthesis.com> | 2024-06-04 11:50:20 +0200 |
---|---|---|
committer | Francois Kritzinger <francois@codesynthesis.com> | 2024-10-15 09:05:28 +0200 |
commit | 621ea867391ebd66d02dc57c6fd7c6ebf617bac0 (patch) | |
tree | f8765a9a78314253c351edf1e7b431fdaef54b64 /mod | |
parent | 8aee1c959b53ff300a9548615c44b51c960e1392 (diff) |
Fetch repository pull requests with the specified base branch
Diffstat (limited to 'mod')
-rw-r--r-- | mod/mod-ci-github-gq.cxx | 202 | ||||
-rw-r--r-- | mod/mod-ci-github-gq.hxx | 12 |
2 files changed, 198 insertions, 16 deletions
diff --git a/mod/mod-ci-github-gq.cxx b/mod/mod-ci-github-gq.cxx index 5979511..26b4c1f 100644 --- a/mod/mod-ci-github-gq.cxx +++ b/mod/mod-ci-github-gq.cxx @@ -693,22 +693,192 @@ namespace brep return nullopt; } - // bool - // gq_fetch_branch_open_pull_requests () - // { - // // query { - // // repository(owner:"francoisk" name:"libb2") - // // { - // // pullRequests (last:100 states:OPEN baseRefName:"master") { - // // edges { - // // node { - // // id - // // } - // // } - // // } - // // } - // // } - // } + // Serialize a GraphQL query that fetches the last 100 (the maximum per + // page) open pull requests with the specified base branch from the + // repository with the specified node ID. + // + // @@ TMP Should we support more than 100? + // + // Example query: + // + // query { + // node(id:"R_kgDOLc8CoA") + // { + // ... on Repository { + // pullRequests (last:100 states:OPEN baseRefName:"master") { + // edges { + // node { + // id + // number + // headRefOid + // } + // } + // } + // } + // } + // } + // + static string + gq_query_fetch_open_pull_requests (const string& rid, const string& br) + { + ostringstream os; + + os << "query {" << '\n' + << " node(id:" << gq_str (rid) << ") {" << '\n' + << " ... on Repository {" << '\n' + << " pullRequests (last:100" << '\n' + << " states:" << gq_enum ("OPEN") << '\n' + << " baseRefName:" << gq_str (br) << '\n' + << " ) {" << '\n' + << " totalCount" << '\n' + << " edges { node { id number headRefOid } }" << '\n' + << " }" << '\n' + << " }" << '\n' + << " }" << '\n' + << "}" << '\n'; + + return os.str (); + } + + optional<vector<gh_pull_request>> + gq_fetch_open_pull_requests (const basic_mark& error, + const string& iat, + const string& nid, + const string& br) + { + string rq ( + gq_serialize_request (gq_query_fetch_open_pull_requests (nid, br))); + + try + { + // Response parser. + // + // Example response (only the part we need to parse here): + // + // { + // "node": { + // "pullRequests": { + // "totalCount": 2, + // "edges": [ + // { + // "node": { + // "id": "PR_kwDOLc8CoM5vRS0y", + // "number": 7, + // "headRefOid": "cf72888be9484d6946a1340264e7abf18d31cc92" + // } + // }, + // { + // "node": { + // "id": "PR_kwDOLc8CoM5vRzHs", + // "number": 8, + // "headRefOid": "626d25b318aad27bc0005277afefe3e8d6b2d434" + // } + // } + // ] + // } + // } + // } + // + struct resp + { + bool found = false; + + vector<gh_pull_request> pull_requests; + + 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; + + p.next_expect_member_object ("pullRequests"); + + uint16_t n (p.next_expect_member_number<uint16_t> ("totalCount")); + + p.next_expect_member_array ("edges"); + for (size_t i (0); i != n; ++i) + { + p.next_expect (event::begin_object); // edges[i] + + p.next_expect_member_object ("node"); + { + gh_pull_request pr; + pr.node_id = p.next_expect_member_string ("id"); + pr.number = p.next_expect_member_number<unsigned int> ("number"); + pr.head_sha = p.next_expect_member_string ("headRefOid"); + pull_requests.push_back (move (pr)); + } + p.next_expect (event::end_object); // node + + p.next_expect (event::end_object); // edges[i] + } + p.next_expect (event::end_array); // edges + + p.next_expect (event::end_object); // pullRequests + 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 << "repository '" << nid << "' not found"; + + return nullopt; + } + + return rs.pull_requests; + } + else + error << "failed to fetch repository pull requests: " + << "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 repository pull requests (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 repository pull requests: " << e; + } + + return nullopt; + } + // GraphQL serialization functions. // diff --git a/mod/mod-ci-github-gq.hxx b/mod/mod-ci-github-gq.hxx index 9721b6e..439f7b7 100644 --- a/mod/mod-ci-github-gq.hxx +++ b/mod/mod-ci-github-gq.hxx @@ -116,6 +116,18 @@ namespace brep gq_pull_request_mergeable (const basic_mark& error, const string& installation_access_token, const string& node_id); + + // Fetch the last 100 open pull requests with the specified base branch from + // the repository with the specified node ID. + // + // Issue diagnostics and return nullopt if the repository was not found or + // an error occurred. + // + optional<vector<gh_pull_request>> + gq_fetch_open_pull_requests (const basic_mark& error, + const string& installation_access_token, + const string& repository_node_id, + const string& base_branch); } #endif // MOD_MOD_CI_GITHUB_GQ_HXX |