Github API: получение всех коммитов для всех веток для репо - PullRequest
33 голосов
/ 07 февраля 2012

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

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

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

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

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

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

ОБНОВЛЕНИЕ: я попытался передать ветку: sha как параметр следующим образом:

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

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

Ответы [ 3 ]

35 голосов
/ 02 апреля 2012

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

Подход к извлечению всех коммитов для всех веток в хранилище

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

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

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

Урезанный результат вызова API ветви для твиттера / начальной загрузки

[
  {
    "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"
  }
]

Работа с последним коммитом ша

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

# With sha parameter of the branch's 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 коммитов master ветви twitter / bootstrap . Работая с API, вы должны указать Sha следующего коммита, чтобы получить следующие 100 коммитов. Мы можем использовать sha последнего коммита (который является 7a8d6b19767a92b1c4ea45d88d4eedc2b29bf1fa с использованием текущего примера) в качестве входных данных для следующего вызова API:

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

Этот процесс повторяется до тех пор, пока sha последнего коммита не станет таким же, как параметр sha вызова API.

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

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


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

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

20 голосов
/ 28 мая 2013

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

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

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

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

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

Я использовал rails 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
6 голосов
/ 28 ноября 2017

Использование 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...