Как использовать API GitHub V3 для получения суммы фиксации для репо?

Я пытаюсь подсчитать коммиты для многих больших github repos с помощью API, поэтому я хотел бы избежать получения всего списка коммитов (следующим образом: api.github.com/repos/jasonrudolph/keyboard/commits) и подсчет их.

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

Базовый URL-адрес репо дает мне created_at (этот URL-адрес является примером: api.github.com/repos/jasonrudolph/keyboard), поэтому я могу получить сокращенный набор фиксаций, ограничивая фиксации до даты создания (этот URL-адрес является примером: api.github.com/repos/jasonrudolph/keyboard/commits?until=2013-03-30T16:01:43Z) и с использованием самого раннего (всегда указанного в списке?) или, возможно, пустой родительский (не уверен, что если forked проекты имеют начальную запись).

Любой лучший способ получить первый хэш-код фиксации для репо?

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

Изменить: этот несколько схожий вопрос пытается фильтровать определенные файлы ( "и внутри них для определенных файлов." ), поэтому имеет другой ответ.

Ответ 1

Вы можете использовать GraphQL API v4 для выполнения подсчета фиксации для нескольких репозиториев в одно и то же время с помощью aliases. Ниже перечисляется сумма фиксации для всех ветвей из 3 разных хранилищ (до 100 веток на репо):

{
  gson: repository(owner: "google", name: "gson") {
    ...RepoFragment
  }
  martian: repository(owner: "google", name: "martian") {
    ...RepoFragment
  }
  keyboard: repository(owner: "jasonrudolph", name: "keyboard") {
    ...RepoFragment
  }
}

fragment RepoFragment on Repository {
  name
  refs(first: 100, refPrefix: "refs/heads/") {
    edges {
      node {
        name
        target {
          ... on Commit {
            id
            history(first: 0) {
              totalCount
            }
          }
        }
      }
    }
  }
}

Попробуйте в проводнике

RepoFragment является fragment, который помогает избежать дублирования полей запроса для каждого из этих репо

Если вам нужна только сумма фиксации в ветке по умолчанию, это более просто:

{
  gson: repository(owner: "google", name: "gson") {
    ...RepoFragment
  }
  martian: repository(owner: "google", name: "martian") {
    ...RepoFragment
  }
  keyboard: repository(owner: "jasonrudolph", name: "keyboard") {
    ...RepoFragment
  }
}

fragment RepoFragment on Repository {
  name
  defaultBranchRef {
    name
    target {
      ... on Commit {
        id
        history(first: 0) {
          totalCount
        }
      }
    }
  }
}

Попробуйте в проводнике

Ответ 2

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

Используйте API Repo Contributors, чтобы получить список всех участников:

https://developer.github.com/v3/repos/#list-contributors

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

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

Ответ 3

Я сделал немного script, чтобы сделать это. Он может не работать с большими репозиториями, поскольку он не обрабатывает ограничения скорости GitHub. Также требуется пакет Python requests.

#!/bin/env python3.4
import requests

GITHUB_API_BRANCHES = 'https://%(token)[email protected]/repos/%(namespace)s/%(repository)s/branches'
GUTHUB_API_COMMITS = 'https://%(token)[email protected]/repos/%(namespace)s/%(repository)s/commits?sha=%(sha)s&page=%(page)i'


def github_commit_counter(namespace, repository, access_token=''):
    commit_store = list()

    branches = requests.get(GITHUB_API_BRANCHES % {
        'token': access_token,
        'namespace': namespace,
        'repository': repository,
    }).json()

    print('Branch'.ljust(47), 'Commits')
    print('-' * 55)

    for branch in branches:
        page = 1
        branch_commits = 0

        while True:
            commits = requests.get(GUTHUB_API_COMMITS % {
                'token': access_token,
                'namespace': namespace,
                'repository': repository,
                'sha': branch['name'],
                'page': page
            }).json()

            page_commits = len(commits)

            for commit in commits:
                commit_store.append(commit['sha'])

            branch_commits += page_commits

            if page_commits == 0:
                break

            page += 1

        print(branch['name'].ljust(45), str(branch_commits).rjust(9))

    commit_store = set(commit_store)
    print('-' * 55)
    print('Total'.ljust(42), str(len(commit_store)).rjust(12))

# for private repositories, get your own token from
# https://github.com/settings/tokens
# github_commit_counter('github', 'gitignore', access_token='fnkr:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
github_commit_counter('github', 'gitignore')

Ответ 4

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

Вот моя реализация захвата общего количества коммитов для всей организации с использованием драгоценного камня octokit в ruby:

@github = Octokit::Client.new access_token: key, auto_traversal: true, per_page: 100

Octokit.auto_paginate = true
repos = @github.org_repos('my_company', per_page: 100)

# * take the pagination number
# * get the last page
# * see how many items are on it
# * multiply the number of pages - 1 by the page size
# * and add the two together. Boom. Commit count in 2 api calls
def calc_total_commits(repos)
    total_sum_commits = 0

    repos.each do |e| 
        repo = Octokit::Repository.from_url(e.url)
        number_of_commits_in_first_page = @github.commits(repo).size
        repo_sum = 0
        if number_of_commits_in_first_page >= 100
            links = @github.last_response.rels

            unless links.empty?
                last_page_url = links[:last].href

                /.*page=(?<page_num>\d+)/ =~ last_page_url
                repo_sum += (page_num.to_i - 1) * 100 # we add the last page manually
                repo_sum += links[:last].get.data.size
            end
        else
            repo_sum += number_of_commits_in_first_page
        end
        puts "Commits for #{e.name} : #{repo_sum}"
        total_sum_commits += repo_sum
    end
    puts "TOTAL COMMITS #{total_sum_commits}"
end

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

Ответ 5

Я использовал python для создания генератора, который возвращает список участников, суммирует общее количество коммитов, а затем проверяет, является ли он действительным. Возвращает True если оно имеет меньше, и False если False то же или большее. Единственное, что вам нужно заполнить, это сеанс запросов, который использует ваши учетные данные. Вот что я написал для вас:

from requests import session
def login()
    sess = session()

    # login here and return session with valid creds
    return sess

def generateList(link):
    # you need to login before you do anything
    sess = login()

    # because of the way that requests works, you must start out by creating an object to
    # imitate the response object. This will help you to cleanly while-loop through
    # github pagination
    class response_immitator:
        links = {'next': {'url':link}}
    response = response_immitator() 
    while 'next' in response.links:
        response = sess.get(response.links['next']['url'])
        for repo in response.json():
            yield repo

def check_commit_count(baseurl, user_name, repo_name, max_commit_count=None):
    # login first
    sess = login()
    if max_commit_count != None:
        totalcommits = 0

        # construct url to paginate
        url = baseurl+"repos/" + user_name + '/' + repo_name + "/stats/contributors"
        for stats in generateList(url):
            totalcommits+=stats['total']

        if totalcommits >= max_commit_count:
            return False
        else:
            return True

def main():
    # what user do you want to check for commits
    user_name = "arcsector"

    # what repo do you want to check for commits
    repo_name = "EyeWitness"

    # github base api url
    baseurl = "https://api.github.com/"

    # call function
    check_commit_count(baseurl, user_name, repo_name, 30)

if __name__ == "__main__":
    main()

Ответ 6

Использование GraphQL API v4, вероятно, является способом справиться с этим, если вы только начинаете работу в новом проекте, но если вы все еще используете REST API v3, вы можете обойти проблему разбивки на страницы, ограничив запрос только одним результатом на стр. При установке этого ограничения количество pages возвращаемых по последней ссылке, будет равно общему количеству.

Например, используя python3 и библиотеку запросов

def commit_count(project, sha='master', token=None):
    """
    Return the number of commits to a project
    """
    token = token or os.environ.get('GITHUB_API_TOKEN')
    url = f'https://api.github.com/repos/{project}/commits'
    headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': f'token {token}',
    }
    params = {
        'sha': sha,
        'per_page': 1,
    }
    resp = requests.request('GET', url, params=params, headers=headers)
    if (resp.status_code // 100) != 2:
        raise Exception(f'invalid github response: {resp.content}')
    # check the resp count, just in case there are 0 commits
    commit_count = len(resp.json())
    last_page = resp.links.get('last')
    # if there are no more pages, the count must be 0 or 1
    if last_page:
        # extract the query string from the last page url
        qs = urllib.parse.urlparse(last_page['url']).query
        # extract the page number from the query string
        commit_count = int(dict(urllib.parse.parse_qsl(qs))['page'])
    return commit_count