Использование GraphQL API v4
Вы можете использовать GraphQL API v4 для оптимизации загрузки коммитов для каждой ветви. В следующем методе мне удалось за один запрос загрузить 1900 коммитов (100 коммитов на ветку в 19 различных ветвях), что значительно уменьшает количество запросов (по сравнению с использованием REST api).
1 - Получить все филиалы
Вам нужно будет получить все ветки и пройти нумерацию страниц, если у вас более 100 веток:
Запрос:
query($owner:String!, $name:String!, $branchCursor: String!) {
repository(owner: $owner, name: $name) {
refs(first: 100, refPrefix: "refs/heads/",after: $branchCursor) {
totalCount
edges {
node {
name
target {
...on Commit {
history(first:0){
totalCount
}
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
переменные:
{
"owner": "google",
"name": "gson",
"branchCursor": ""
}
Попробуйте в проводнике
Обратите внимание, что переменная branchCursor
используется, когда у вас более 100 ветвей и в этом случае значение pageInfo.endCursor
указано в предыдущем запросе.
2 - разбить массив ветвей на массив из 19 ветвей макс.
Существует некоторое ограничение количества запросов на узлы, которое не позволяет нам делать слишком много запросов на узел. Здесь, некоторые тесты, которые я провел, показали, что мы не можем пройти более 19 * 100 коммитов в одном запросе.
Обратите внимание, что в случае репо с <19 филиалами вам не нужно об этом беспокоиться </p>
3 - запрос фиксируется фрагментом 100 для каждой ветви
Затем вы можете динамически создать запрос для получения 100 следующих коммитов во всех ветвях. Пример с 2 ветками:
query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
branch0: ref(qualifiedName: "JsonArrayImplementsList") {
target {
... on Commit {
history(first: 100) {
...CommitFragment
}
}
}
}
branch1: ref(qualifiedName: "master") {
target {
... on Commit {
history(first: 100) {
...CommitFragment
}
}
}
}
}
}
fragment CommitFragment on CommitHistoryConnection {
totalCount
nodes {
oid
message
committedDate
author {
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}
Попробуйте в проводнике
- Используемые переменные:
owner
для владельца репо и name
для имени репо.
- A фрагмент во избежание дублирования определения поля истории коммитов.
Вы можете видеть, что pageInfo.hasNextpage
& pageInfo.endCursor
будет использоваться для разбивки на страницы для каждой ветви. Пагинация выполняется в history(first: 100)
с указанием последнего найденного курсора. Например, следующий запрос будет иметь history(first: 100, after: "6e2fcdcaf252c54a151ce6a4441280e4c54153ae 99")
. Для каждой ветви мы должны обновить запрос с последним значением endCursor
, чтобы запросить 100 следующих коммитов.
Когда pageInfo.hasNextPage
равно false
, для этой ветви больше нет страницы, поэтому мы не будем включать ее в следующий запрос.
Когда у последней ветви от pageInfo.hasNextPage
до false
, мы получили все коммиты
Пример реализации
Вот пример реализации в NodeJS с использованием github-graphql-client . Тот же метод может быть реализован на любом другом языке. Следующее также будет хранить коммиты в файле commitsX.json
:
var client = require('github-graphql-client');
var fs = require("fs");
const owner = "google";
const repo = "gson";
const accessToken = "YOUR_ACCESS_TOKEN";
const branchQuery = `
query($owner:String!, $name:String!, $branchCursor: String!) {
repository(owner: $owner, name: $name) {
refs(first: 100, refPrefix: "refs/heads/",after: $branchCursor) {
totalCount
edges {
node {
name
target {
...on Commit {
history(first:0){
totalCount
}
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}`;
function buildCommitQuery(branches){
var query = `
query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {`;
for (var key in branches) {
if (branches.hasOwnProperty(key) && branches[key].hasNextPage) {
query+=`
${key}: ref(qualifiedName: "${branches[key].name}") {
target {
... on Commit {
history(first: 100, after: ${branches[key].cursor ? '"' + branches[key].cursor + '"': null}) {
...CommitFragment
}
}
}
}`;
}
}
query+=`
}
}`;
query+= commitFragment;
return query;
}
const commitFragment = `
fragment CommitFragment on CommitHistoryConnection {
totalCount
nodes {
oid
message
committedDate
author {
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}`;
function doRequest(query, variables) {
return new Promise(function (resolve, reject) {
client({
token: accessToken,
query: query,
variables: variables
}, function (err, res) {
if (!err) {
resolve(res);
} else {
console.log(JSON.stringify(err, null, 2));
reject(err);
}
});
});
}
function buildBranchObject(branch){
var refs = {};
for (var i = 0; i < branch.length; i++) {
console.log("branch " + branch[i].node.name);
refs["branch" + i] = {
name: branch[i].node.name,
totalCount: branch[i].node.target.history.totalCount,
cursor: null,
hasNextPage : true,
commits: []
};
}
return refs;
}
async function requestGraphql() {
var iterateBranch = true;
var branches = [];
var cursor = "";
// get all branches
while (iterateBranch) {
let res = await doRequest(branchQuery,{
"owner": owner,
"name": repo,
"branchCursor": cursor
});
iterateBranch = res.data.repository.refs.pageInfo.hasNextPage;
cursor = res.data.repository.refs.pageInfo.endCursor;
branches = branches.concat(res.data.repository.refs.edges);
}
//split the branch array into smaller array of 19 items
var refChunk = [], size = 19;
while (branches.length > 0){
refChunk.push(branches.splice(0, size));
}
for (var j = 0; j < refChunk.length; j++) {
//1) store branches in a format that makes it easy to concat commit when receiving the query result
var refs = buildBranchObject(refChunk[j]);
//2) query commits while there are some pages existing. Note that branches that don't have pages are not
//added in subsequent request. When there are no more page, the loop exit
var hasNextPage = true;
var count = 0;
while (hasNextPage) {
var commitQuery = buildCommitQuery(refs);
console.log("request : " + count);
let commitResult = await doRequest(commitQuery, {
"owner": owner,
"name": repo
});
hasNextPage = false;
for (var key in refs) {
if (refs.hasOwnProperty(key) && commitResult.data.repository[key]) {
isEmpty = false;
let history = commitResult.data.repository[key].target.history;
refs[key].commits = refs[key].commits.concat(history.nodes);
refs[key].cursor = (history.pageInfo.hasNextPage) ? history.pageInfo.endCursor : '';
refs[key].hasNextPage = history.pageInfo.hasNextPage;
console.log(key + " : " + refs[key].commits.length + "/" + refs[key].totalCount + " : " + refs[key].hasNextPage + " : " + refs[key].cursor + " : " + refs[key].name);
if (refs[key].hasNextPage){
hasNextPage = true;
}
}
}
count++;
console.log("------------------------------------");
}
for (var key in refs) {
if (refs.hasOwnProperty(key)) {
console.log(refs[key].totalCount + " : " + refs[key].commits.length + " : " + refs[key].name);
}
}
//3) write commits chunk (up to 19 branches) in a single json file
fs.writeFile("commits" + j + ".json", JSON.stringify(refs, null, 4), "utf8", function(err){
if (err){
console.log(err);
}
console.log("done");
});
}
}
requestGraphql();
Это также работает с репо с большим количеством веток, например , это , у которого более 700 веток
Ограничение скорости
Обратите внимание, что, хотя это правда, что с GraphQL вы можете выполнять уменьшенное количество запросов, это не обязательно улучшит ваш предел скорости, так как ограничение скорости основано на баллах, а не ограниченном количестве запросов: проверьте Ограничение скорости GraphQL API