aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancois Kritzinger <francois@codesynthesis.com>2024-06-04 11:50:20 +0200
committerFrancois Kritzinger <francois@codesynthesis.com>2024-10-15 09:05:28 +0200
commit621ea867391ebd66d02dc57c6fd7c6ebf617bac0 (patch)
treef8765a9a78314253c351edf1e7b431fdaef54b64
parent8aee1c959b53ff300a9548615c44b51c960e1392 (diff)
Fetch repository pull requests with the specified base branch
-rw-r--r--mod/mod-ci-github-gq.cxx202
-rw-r--r--mod/mod-ci-github-gq.hxx12
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