Подтвердить что ты не робот

API Github: получить все фиксации для всех веток для репо

В соответствии с документацией V2 вы можете перечислить все фиксации для ветки с помощью:

commits/list/:user_id/:repository/:branch

Я не вижу такой же функциональности в документации V3.

Я хотел бы собрать все ветки, используя что-то вроде:

https://api.github.com/repos/:user/:repo/branches

И затем повторите их, потянув за все фиксации. В качестве альтернативы, если есть способ полностью вывести все фиксации для всех веток для репо, это будет работать так же хорошо, если не лучше. Есть идеи?

UPDATE: я пробовал передать ветку: sha как параметр следующим образом:

params = {:page => 1, :per_page => 100, :sha => b}

Проблема заключается в том, что, когда я это делаю, он не правильно отображает результаты. Я чувствую, что мы приближаемся к этому неправильно. Любые мысли?

4b9b3361

Ответ 1

Я столкнулся с той же проблемой. Мне удалось получить все коммиты для всех веток в репозитории (возможно, это не так эффективно из-за API).

Подход для получения всех коммитов для всех веток в репозитории

Как вы упомянули, сначала вы соберете все ветки:

# https://api.github.com/repos/:user/:repo/branches
https://api.github.com/repos/twitter/bootstrap/branches

Ключ, который вам не хватает, заключается в том, что APIv3 для получения коммитов работает с использованием контрольной фиксации (параметр для вызова API для списка фиксируется в хранилище sha). Поэтому вам нужно убедиться, что когда вы соберете ветки, которые вы также заберете в их последней шаге:

Обрезанный результат API-интерфейса ветки для twitter/bootstrap

[
  {
    "commit": {
      "url": "https://api.github.com/repos/twitter/bootstrap/commits/8b19016c3bec59acb74d95a50efce70af2117382",
      "sha": "8b19016c3bec59acb74d95a50efce70af2117382"
    },
    "name": "gh-pages"
  },
  {
    "commit": {
      "url": "https://api.github.com/repos/twitter/bootstrap/commits/d335adf644b213a5ebc9cee3f37f781ad55194ef",
      "sha": "d335adf644b213a5ebc9cee3f37f781ad55194ef"
    },
    "name": "master"
  }
]

Работа с последней командой фиксации

Итак, как мы видим, у двух ветвей есть разные шаны, это последняя фиксация sha на этих ветвях. Теперь вы можете перебирать каждую ветку из последней версии:

# With sha parameter of the branch lastest sha
# https://api.github.com/repos/:user/:repo/commits
https://api.github.com/repos/twitter/bootstrap/commits?per_page=100&sha=d335adf644b213a5ebc9cee3f37f781ad55194ef

Таким образом, вышеупомянутый вызов API отобразит последние 100 коммитов главной ветки twitter/bootstrap. Работая с API, вы должны указать следующую команду фиксации, чтобы получить следующие 100 коммитов. Мы можем использовать последний commit sha (который является 7a8d6b19767a92b1c4ea45d88d4eedc2b29bf1fa с использованием текущего примера) в качестве ввода для следующего вызова API:

# Next API call for commits (use the last commit sha)
# https://api.github.com/repos/:user/:repo/commits
https://api.github.com/repos/twitter/bootstrap/commits?per_page=100&sha=7a8d6b19767a92b1c4ea45d88d4eedc2b29bf1fa

Этот процесс повторяется до тех пор, пока последний commit sha не будет таким же, как параметр sha API-запроса.

Следующая ветка

Это для одной ветки. Теперь вы применяете тот же подход для другой ветки (работа с последней ша).


Существует большая проблема с этим подходом... Поскольку ветки разделяют некоторые идентичные коммиты, вы увидите, что те же самые перехватывают снова и снова при переходе в другую ветку.

Я могу представить, что есть намного более эффективный способ выполнить это, но это сработало для меня.

Ответ 2

Я задал этот же вопрос для поддержки GitHub, и они ответили мне на это:

GETing/repos/: owner/: repo/commits должен сделать трюк. Вы можете передать имя ветки в параметре sha. Например, чтобы получить первую страницу коммитов из ветки "3.0.0-wip" репозитория twitter/bootstrap, вы должны использовать следующий запрос на завивание:

curl https://api.github.com/repos/twitter/bootstrap/commits?sha=3.0.0-wip

Документы также описывают, как использовать разбиение на страницы, чтобы получить оставшиеся фиксации для этой ветки.

Пока вы выполняете аутентифицированные запросы, вы можете сделать до 5000 запросов в час.

Я использовал рельсы github-api в своем приложении следующим образом (используя https://github.com/peter-murach/github gem):

github_connection = Github.new :client_id => 'your_id', :client_secret => 'your_secret', :oauth_token => 'your_oath_token'
branches_info = {}
all_branches = git_connection.repos.list_branches owner,repo_name
all_branches.body.each do |branch|
    branches_info["#{branch.name}".to_s] = "#{branch.commit.url}"
end
branches_info.keys.each do |branch|
    commits_list.push (git_connection.repos.commits.list owner,repo_name, start_date,      end_date, :sha => "branch_name")
end

Ответ 3

Использование API-интерфейса GraphQL 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 ветвей max

Существует некоторое ограничение количества запросов на узлы, что мешает нам делать слишком много запросов на node. Здесь некоторые результаты тестирования показали, что мы не можем переходить за 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 для имени репо.
  • A fragment во избежание дублирования определения поля истории фиксации.

Вы можете видеть, что pageInfo.hasNextpage и pageInfo.endCursor будут использоваться для разбивки на страницы для каждой ветки. Разбиение страницы происходит в history(first: 100) с указанием последнего найденного курсора. Например, следующий запрос будет иметь history(first: 100, after: "6e2fcdcaf252c54a151ce6a4441280e4c54153ae 99"). Для каждой ветки мы должны обновить запрос с последним значением endCursor, чтобы запросить следующую следующую фиксацию.

Когда 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 вы можете выполнять меньшее количество запросов, это не обязательно улучшит ваш лимит ставок, поскольку предел ставки основан на точках, а не на ограниченном числе запросов: проверьте Ограничение скорости API GraphQL