web-dev-qa-db-ja.com

GitHub V3 APIを使用してリポジトリのコミット数を取得する方法

私はAPIを使用して多くの大きなgithubreposのコミットをカウントしようとしているので、コミットのリスト全体を取得することは避けたいです(このように例:api.github.com/repos/jasonrudolph/keyboard/commits)とカウントします。

最初の(初期)コミットのハッシュがある場合、 このテクニックを使用して、最初のコミットを最新のコミットと比較します で、その間のtotal_commitsを喜んで報告します(したがって、1つ追加する必要があります) )そのように。残念ながら、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)そして最も古いもの(常に最後にリストされますか?)または空の親を持つもの(多分)フォークされたプロジェクトに最初の親コミットがあるかどうかは不明です)。

リポジトリの最初のコミットハッシュを取得するより良い方法はありますか?

さらに良いことに、この全体は単純な統計では入り組んでいるように見え、何か不足しているのではないかと思います。 APIを使用してリポジトリのコミット数を取得するためのより良いアイデアはありますか?

編集:これは やや類似した質問 が特定のファイル( "およびその中から特定のファイルに。")でフィルタリングしようとしているため、別の答えになります。

19
SteveCoffman

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フラグメント であり、各リポジトリのクエリフィールドの重複を回避するのに役立ちます

デフォルトのブランチでコミット数のみが必要な場合は、より簡単です:

{
  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
        }
      }
    }
  }
}

エクスプローラーで試してください

8
Bertrand Martel

デフォルトのブランチでコミットの総数を探している場合は、別の方法を検討することもできます。

Repo Contributors APIを使用して、すべての寄稿者のリストを取得します。

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

リストの各項目には、デフォルトのブランチでユーザーが作成したコミットの数を示すcontributionsフィールドが含まれます。すべてのコントリビュータにわたってこれらのフィールドを合計すると、デフォルトのブランチのコミットの総数が得られます。

多くの場合、貢献者のリストはコミットのリストよりもはるかに短いため、デフォルトのブランチのコミットの合計数を計算するために要求するリクエストが少なくなるはずです。

10
Ivan Zuzak

簡単な解決策:ページ番号を見てください。 Githubがページ付けを行います。したがって、Linkヘッダーから最後のページ番号を取得し、1を引いて(最後のページを手動で追加する必要があります)、ページサイズを掛けて、結果の最後のページを取得することで、コミット数を簡単に計算できます。その配列のサイズを取得し、2つの数値を加算します。最大2つのAPI呼び出しです!

Rubyでoctokit gemを使用して組織全体のコミットの総数を取得する私の実装は次のとおりです。

@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

そして、はい、コードが汚れていることを知っています。これは数分で一緒に投げられただけです。

4
snowe

新しいプロジェクトを開始する場合は、おそらくGraphQL API v4を使用することでこれを処理できますが、まだREST API v3を使用している場合は、ページ分割の問題を回避できますリクエストをページあたり1つの結果に制限することにより、その制限を設定することにより、最後のリンクで返される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
4
buckley

これを行うための小さなスクリプトを作成しました。 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')
3
fnkr

pythonを使用して、コントリビューターのリストを返し、合計コミットカウントを合計し、それが有効かどうかを確認するジェネレーターを作成しました。それが有効である場合はTrueを返し、および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's 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's base api url
    baseurl = "https://api.github.com/"

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

if __name__ == "__main__":
    main()
1
Arcsector