使用GraphQL API v4
您可以使用GraphQL API v4来优化每个分支的提交下载。在下面的方法中,我成功地通过单个请求下载了1900个提交(19个不同分支中每个分支100个提交),这极大地减少了请求数量(与使用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": ""
}
在浏览器中尝试一下
请注意,当您有超过100个分支和特性时,变量
branchCursor
会在该情况下使用前一个请求中的
pageInfo.endCursor
值。
2-将分支数组拆分为最多19个分支的数组
每个节点的请求次数存在一定限制,防止我们对每个节点进行过多的查询。在这里,我进行了一些测试,发现单个查询中不能超过19 * 100个提交。
请注意,在具有<19个分支的仓库的情况下,您不需要担心这个问题。
3-按照每个分支的100个一组查询提交
然后,您可以动态地创建查询,以获取所有分支的下100个提交。以下是两个分支的示例:
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
表示仓库名称。
- 为避免重复定义提交历史字段,使用片段。
可以看到,pageInfo.hasNextpage
和pageInfo.endCursor
将用于浏览每个分支的分页。分页在history(first: 100)
中进行,指定最后一个遇到的游标。例如,下一个请求将具有history(first: 100, after: "6e2fcdcaf252c54a151ce6a4441280e4c54153ae 99")
。对于每个分支,我们必须使用最后一个endCursor
值更新请求,以查询接下来的100个提交。
当pageInfo.hasNextPage
为false
时,此分支没有更多页面,因此我们不会在下一个请求中包含它。
当最后一个分支的pageInfo.hasNextPage
为false
时,我们已检索到所有提交。
示例实现
以下是使用github-graphql-client在NodeJS中的示例实现。相同的方法可以在任何其他语言中实现。以下还将提交存储在文件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 = "";
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);
}
var refChunk = [], size = 19;
while (branches.length > 0){
refChunk.push(branches.splice(0, size));
}
for (var j = 0; j < refChunk.length; j++) {
var refs = buildBranchObject(refChunk[j]);
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);
}
}
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速率限制。
git log --pretty="%h"
。 - George Pligoropoulos