Skip to content

Commit ba0dc07

Browse files
committed
Add support for searching commits
Adds the `search_commits` method to the `GitHub` class in order to be able to search for commits that match a given query. Closes sigmavirus24#748
1 parent 37b3cf9 commit ba0dc07

File tree

6 files changed

+186
-2
lines changed

6 files changed

+186
-2
lines changed

src/github3/github.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,87 @@ def search_code(self, query, sort=None, order=None, per_page=None,
16691669
number, url, search.CodeSearchResult, self, params, etag, headers
16701670
)
16711671

1672+
def search_commits(self, query, sort=None, order=None, per_page=None,
1673+
text_match=False, number=-1, etag=None):
1674+
"""Find commits via the commits search API.
1675+
1676+
The query can contain any combination of the following supported
1677+
qualifiers:
1678+
1679+
- ``author`` Matches commits authored by the given username.
1680+
Example: ``author:defunkt``.
1681+
- ``committer`` Matches commits committed by the given username.
1682+
Example: ``committer:defunkt``.
1683+
- ``author-name`` Matches commits authored by a user with the given
1684+
name. Example: ``author-name:wanstrath``.
1685+
- ``committer-name`` Matches commits committed by a user with the given
1686+
name. Example: ``committer-name:wanstrath``.
1687+
- ``author-email`` Matches commits authored by a user with the given
1688+
email. Example: ``author-email:chris@github.com``.
1689+
- ``committer-email`` Matches commits committed by a user with the
1690+
given email. Example: ``committer-email:chris@github.com``.
1691+
- ``author-date`` Matches commits authored within the specified date
1692+
range. Example: ``author-date:<2016-01-01``.
1693+
- ``committer-date`` Matches commits committed within the specified
1694+
date range. Example: ``committer-date:>2016-01-01``.
1695+
- ``merge`` Matches merge commits when set to to ``true``, excludes
1696+
them when set to ``false``.
1697+
- ``hash`` Matches commits with the specified hash. Example:
1698+
``hash:124a9a0ee1d8f1e15e833aff432fbb3b02632105``.
1699+
- ``parent`` Matches commits whose parent has the specified hash.
1700+
Example: ``parent:124a9a0ee1d8f1e15e833aff432fbb3b02632105``.
1701+
- ``tree`` Matches commits with the specified tree hash. Example:
1702+
``tree:99ca967``.
1703+
- ``is`` Matches public repositories when set to ``public``, private
1704+
repositories when set to ``private``.
1705+
- ``user`` or ``org`` or ``repo`` Limits the search to a specific user,
1706+
organization, or repository.
1707+
1708+
For more information about these qualifiers, see: https://git.io/vb7XQ
1709+
1710+
:param str query:
1711+
(required), a valid query as described above, e.g.,
1712+
``css repo:octocat/Spoon-Knife``
1713+
:param str sort:
1714+
(optional), how the results should be sorted;
1715+
options: ``author-date``, ``committer-date``;
1716+
default: best match
1717+
:param str order:
1718+
(optional), the direction of the sorted results,
1719+
options: ``asc``, ``desc``; default: ``desc``
1720+
:param int per_page:
1721+
(optional)
1722+
:param int number:
1723+
(optional), number of commits to return.
1724+
Default: -1, returns all available commits
1725+
:param str etag:
1726+
(optional), previous ETag header value
1727+
:return:
1728+
generator of commit search results
1729+
:rtype:
1730+
:class:`~github3.search.commits.CommitSearchResult`
1731+
"""
1732+
params = {'q': query}
1733+
headers = {'Accept': 'application/vnd.github.cloak-preview'}
1734+
1735+
if sort in ('author-date', 'committer-date'):
1736+
params['sort'] = sort
1737+
1738+
if sort and order in ('asc', 'desc'):
1739+
params['order'] = order
1740+
1741+
if text_match:
1742+
headers['Accept'] = ', '.join([
1743+
headers['Accept'],
1744+
'application/vnd.github.v3.full.text-match+json'
1745+
])
1746+
1747+
url = self._build_url('search', 'commits')
1748+
return structs.SearchIterator(
1749+
number, url, search.CommitSearchResult,
1750+
self, params, etag, headers
1751+
)
1752+
16721753
def search_issues(self, query, sort=None, order=None, per_page=None,
16731754
text_match=False, number=-1, etag=None):
16741755
"""Find issues by state and keyword.

src/github3/search/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
from .code import CodeSearchResult
2+
from .commit import CommitSearchResult
23
from .issue import IssueSearchResult
34
from .repository import RepositorySearchResult
45
from .user import UserSearchResult
56

67

7-
__all__ = [CodeSearchResult, IssueSearchResult, RepositorySearchResult,
8-
UserSearchResult]
8+
__all__ = (
9+
'CodeSearchResult',
10+
'CommitSearchResult',
11+
'IssueSearchResult',
12+
'RepositorySearchResult',
13+
'UserSearchResult',
14+
)

src/github3/search/commit.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*-
2+
"""Commit search results implementation."""
3+
from __future__ import unicode_literals
4+
5+
from .. import git
6+
from .. import models
7+
from .. import repos
8+
from .. import users
9+
10+
11+
class CommitSearchResult(models.GitHubCore):
12+
"""A representation of a commit search result from the API.
13+
14+
This object has the following attributes:
15+
16+
.. attribute:: author
17+
18+
A :class:`~github3.users.ShortUser` representing the user who
19+
authored the found commit.
20+
21+
.. attribute:: comments_url
22+
23+
The URL to retrieve the comments on the found commit from the API.
24+
25+
.. attribute:: commit
26+
27+
A :class:`~github3.git.ShortCommit` representing the found commit.
28+
29+
.. attribute:: committer
30+
31+
A :class:`~github3.users.ShortUser` representing the user who
32+
committed the found commit.
33+
34+
.. attribute:: html_url
35+
36+
The URL to view the found commit in a browser.
37+
38+
.. attribute:: repository
39+
40+
A :class:`~github3.repos.repo.ShortRepository` representing the
41+
repository in which the commit was found.
42+
43+
.. attribute:: score
44+
45+
The confidence score assigned to the result.
46+
47+
.. attribute:: sha
48+
49+
The SHA1 of the found commit.
50+
51+
.. attribute:: text_matches
52+
53+
A list of the text matches in the commit that generated this result.
54+
55+
.. note::
56+
57+
To receive these, you must pass ``text_match=True`` to
58+
:meth:`~github3.github.GitHub.search_commit`.
59+
"""
60+
61+
def _update_attributes(self, data):
62+
self._api = data['url']
63+
self.author = users.ShortUser(data['author'], self)
64+
self.comments_url = data['comments_url']
65+
self.commit = git.ShortCommit(data['commit'], self)
66+
self.committer = users.ShortUser(data['committer'], self)
67+
self.html_url = data['html_url']
68+
self.repository = repos.ShortRepository(data['repository'], self)
69+
self.score = data['score']
70+
self.sha = data['sha']
71+
self.text_matches = data.get('text_matches', [])
72+
73+
def _repr(self):
74+
return '<CommitSearchResult [{0}]>'.format(self.sha[:7])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.cloak-preview"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "GET", "uri": "https://api.github.com/search/commits?q=css+repo%3Aoctocat%2FSpoon-Knife&per_page=100"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA+1ZXW/iRhT9K4jXJtjGhthI0XbVpKttRaKk7HaTaoXG4zEesD3WzDgUrPz33hkbA1EIYLbtS15CMD5n7sfM9bnXRVsyieIxZnkq2wPrrE1TzJIsJpKMORF5LEV7EKJYEPhJkgS+/VW0cx63B+1IykwMDANltDOhMsr9DmANTjImDIYlw0gaf2SMpee/pzQkBvyaUCkM33cwdgPb7xLL8W0U9oKw73nuRT8InDC0EQ5M078I22dtESFY6QhAygIypgGAhlcP/buuJ/0/Y3M4Gs6HV9e94TSjD9P76cMymg27nxePo2vzYXoTPU6/zh6nD73b0cS+Se5nN58ek2H31+nj1cf5wzRIwJJIJvF42/ENp3e7e4y3KkAkleLFOv9agHVG1ILgX5mc9qBxdiEaTTKMchkxrtYNkCSQt65pOedm99x0RpYzsN2B3T833YFpgpEpStQto4i0bssNBhdJgqjaj1UOfk7ZPCKcqM3Yfl45Jslra1jdkdUbWO6g1ztpjYQIgSbKtF84ATdaQi5iIjpYiBZKg1aeKeeC1v31x6vhNdgsOYG7T4q1ohAG6tse8fqhZ9ruhXcR+qhPTKfvhZ6DHN/E2Ol5Ieq79Vk6GFCFDrZHXR4suLbOV8wmNF3HHVZQ567n2l0bCsnmQbzrf/12E+PpF2e4/Lwc3l1ews3oCUnEX251fVHYVUHJBeGYpRKM0LUlN0r6D0+XDlBMeEWiDzxceLMwKbK6MMHNB51ouC9kcczmgH1p63bd26I3alBNQNPJ8QQAKgwmYT+PFf+zcpqKfRVi2xQNKAz1AZVRUQiIMSfBMeZUEDBmnoIdhS7zmiv3BeY0k5SlR0VoCwhEjE9QSpfoaCIAqgKmnzvHuKQBACRP+2vudkRLRGFknD4hvFBh4AQT+gQxPZ7tBRTI5CJTteQLZFxFGB68YxQk6rDpR/HLovZ+ELcEyPtBfD+I/9lBzBDXAuoHyGJkm9jyiB1a9kW/h2zfcd2ud+G6Praw7/oO8XqYEPXgO+jR9Yr2No5Yo5TeBwOev1clmErGF0rbqIeyZZum5XW35cD1/Db+LcafvCX6dv+E09nfw+XH+XA0W4BrlcTb6BvgYpjH8bj65RWpDXfox5Ja9b0YvhfDd1Xy/6gSrYdUE1dNDI4uU3CQA1LLSd3pUdFSQq0FnyHjrYAkoDMl1zKxleUcNBwRLZbGi44qFIzP6uXf7AZ2jikqlj1ScicclD+fKVk5I4vGHApbGPC3EuwYWhDkM/Ca7WtDdhu2RVJAr77mVApWEpQ0NliDgSRibNaYRIOBhAqRk4OE9G5nNYcwVko9zRO/bJ8O0ee7aUs02IiEoJMUOvC3O47dTDVBYaw6O5+jFEfNKVf4wij/01lFk8YmKixQ+DHzG3OoqZAmKAwQFGX/KsenWKUYFX6LkJPwJBMVvibUk5WmedXmKYKaDlpnCSluzLjCG0UVwRilkxzGTY0ZawLIrmrsJ2i5d7yxeyevGYBOTWs49fPTCtWaQ1lYThjg/DZ2eINiTahHFk3TvDm90G7r0XZTtgq+taVPpNwYx9a0P2K+rLqdwljX07JYV8xN/a+q9cq+Tf5q/HdCaPX4UBjFTxmSkapAsIxq25oaW8GNwkcwi+l0OkVEkJ6rJYSfcCpLNNAgjiOYJTW1r1jhQYkkSOpZXajMC6BJihkKGseyJgCyMmVNbSzRm3nOoMVrbJgGb7IlFMbvkqXNa+SaYZM3ZZKGFF5s7Z927i6YWyTFBwHv3MgZiuMz2JWSYgr7FOa+KmMg+kjzqJRoMB/empQzypjAlm0cZU5KfGGUY+SAZDFbnPTGaoNCvasRmHFoH+yO0+1Z8B7h+fvzP+6CgBWjHAAA", "encoding": "utf-8"}, "headers": {"Status": ["200 OK"], "X-RateLimit-Remaining": ["9"], "X-GitHub-Media-Type": ["github.cloak-preview"], "X-Runtime-rack": ["0.066329"], "Content-Security-Policy": ["default-src 'none'"], "X-Content-Type-Options": ["nosniff"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "X-GitHub-Request-Id": ["BFE0:5172:2C4F2:5A0E2:5B64C901"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "X-XSS-Protection": ["1; mode=block"], "Server": ["GitHub.com"], "X-RateLimit-Limit": ["10"], "Cache-Control": ["no-cache"], "Date": ["Fri, 03 Aug 2018 21:28:33 GMT"], "Access-Control-Allow-Origin": ["*"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Frame-Options": ["deny"], "Content-Encoding": ["gzip"], "X-RateLimit-Reset": ["1533331773"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/search/commits?q=css+repo%3Aoctocat%2FSpoon-Knife&per_page=100"}, "recorded_at": "2018-08-03T21:28:33"}], "recorded_with": "betamax/0.8.1"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.cloak-preview, application/vnd.github.v3.full.text-match+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "GET", "uri": "https://api.github.com/search/commits?q=css+repo%3Aoctocat%2FSpoon-Knife&per_page=100"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA+1ZbW/bNhD+K4a/LrEkS7YlA0VXNFnRDUrRzO2aDIVBUZRFWxIFkoprC/nvO1KybAd1Y8vd9iUIECeynof3wjveHcuuZBIlU8yKTHbH1kWXZpileUIkmXIiikSK7jhCiSDwlSQp/Pd32S140h13YylzMTYMlNPejMq4CHqANTjJmTAYlgwjafyZM5Zd/pHRiBjwbUqlMILAwdgN7aBPLCewUTQIo6HnuaNhGDpRZCMcmmYwiroXXREjWOkEQMZCMqUhgPyru+HHvieDvxLTn/hL/+p64M9zeje/nd+t44Xff7+6n1ybd/Ob+H7+eXE/vxt8mMzsm/R2cfPuPvX7v83vr94s7+ZhCpLEMk2m+4rvKH1Y3VO0VQYimRRP1vnXDKw9ohYE/SrndMetvQvWaONhVMiYcbVuiCQBv/VNy7k0+5emM7Gcse2O7eGl6Y5NE4TMUKpemcSk86HaYPCQpIiq/Vj74NeMLWPCidqM3ceNYpJ8bw2rP7EGY8sdDwZnrZESIdBMifaWE1CjI+QqIaKHheigLOwUuVIu7Nxev7nyr0FmyQm8fZatFYUw0ND2iDeMPNN2R94oCtCQmM7QizwHOYGJsTPwIjR0m1g6GlCbDrZHkx4seLb1V8JmNNvaHVZQcTdw7b4NiWQ3ED8OP3+5SfD8k+Ov36/9j69ewcvoAUnEn251/VDYdUIpBOGYZRKE0LmlMCr61w+vHKCY8ZpEBzw8+GFiUmRNYoKXj4poeC9iScKWgH0q637e26M3GlBDQLPZ6QQAKg0mYT9PFf+jUpqK5zLEvigaUBrqAzKjohBgY07CU8SpISDMMgM5Sp3mNVcRCMxpLinLTrLQHhCIGJ+hjK7RyUQAVAlMnzunqKQBACQPz+fcfYtWiNLIOX1AeKXMwAkm9AFsejrbEyiQyVWucskn8LiyMBy8UxSmKtj0Ufw0qb0E4l4B8hKIL4H4nwVijrguoH5CWYxsE1sesSPLHg0HyA4c1+17I9cNsIUDN3CIN8CEqIPvqKPrO7W3ccIaVel9NODxa52CqWR8pWobdShbtmlaXn+/HLhefkh+T/A7b42+3D7gbPHNX79Z+pPFClSrS7ydvgEeRkWSTOtvvlNqwxv6WFKrviTDl2T4UpX8P1WJrodUE1dPDE5OUxDIIWnKSd3pUdFRhVoHPiPGOyFJoc6UXJeJnbzgUMMR0WFZsuqpRMH4oln+h93AwTFFzfJMKXkQDpU/X6iyckFWrTkUtjTgd12wY2hBUMBAa/ZcG3JYsD2SEnr1LaeqYCVBaWuBNRhIYsYWrUk0GEioEAU5qpA+rKzmEMamUs+KNKjap2Pq88O0FRpkRELQWQYd+I87jsNMDUFpbDq7gKMMx+0pN/jSqP7SXkWz1iIqLFAECQtac6ipkCYoDSgoqv5VTs+RSjEq/B4hJ9FZIip8Q6gnK239qsVTBA0dtM4SXNyacYM3ytqCCcpmBYybWjM2BOBd1djP0PrZ8cbhnbxlADo1reE0KM5LVFsOJWE1YYD4ba3wDsWWUI8s2rp5d3qh1daj7bZsNXxvS59JuTOObWh/xnxZdTulsc2nVbKumdvqX2frjXy7/PX47wzT6vGhMMpfciRjlYFgGdW2tRW2hhtlgGAW0+v1ypggPVdLCT8jKis00CCOY5gltZWv3OChEkmR1LO6SIkXQpOUMBS2tmVDAGSVy9rKWKF3/ZxDi9daMA3eZUspjN8ly9rnyC3DLm/GJI0oXGw9P+08nDD3SMrXAu7cyAVKkgvYlZJiCvsU5r7KY1D0kfZWqdAgPtyaVDPKhMCWbW1lTip8aVRj5JDkCVuddWO1Q6HuagRmHNoHu+f0hwO3b6uq9Jucwi5WdZEesrBgTrBsu+/qpHrKpVy9YD2TfasTvV/f91x0c85ywiUMPCD6q0sg6EI4mimzHH8ftKuhUhmQcHekwiwLKdaqW86FNfr6WP38AyuRIHW5HQAA", "encoding": "utf-8"}, "headers": {"Status": ["200 OK"], "X-RateLimit-Remaining": ["8"], "X-GitHub-Media-Type": ["github.cloak-preview; param=full.text-match"], "X-Runtime-rack": ["0.043624"], "Content-Security-Policy": ["default-src 'none'"], "X-Content-Type-Options": ["nosniff"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "X-GitHub-Request-Id": ["B258:516C:12CE5:2A7F2:5B64C902"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "X-XSS-Protection": ["1; mode=block"], "Server": ["GitHub.com"], "X-RateLimit-Limit": ["10"], "Cache-Control": ["no-cache"], "Date": ["Fri, 03 Aug 2018 21:28:34 GMT"], "Access-Control-Allow-Origin": ["*"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Frame-Options": ["deny"], "Content-Encoding": ["gzip"], "X-RateLimit-Reset": ["1533331773"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/search/commits?q=css+repo%3Aoctocat%2FSpoon-Knife&per_page=100"}, "recorded_at": "2018-08-03T21:28:34"}], "recorded_with": "betamax/0.8.1"}

tests/integration/test_github.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,27 @@ def test_search_code_with_text_match(self):
523523
assert isinstance(code_result, github3.search.CodeSearchResult)
524524
assert len(code_result.text_matches) > 0
525525

526+
def test_search_commits(self):
527+
"""Test the ability to search for commits."""
528+
cassette_name = self.cassette_name('search_commits')
529+
with self.recorder.use_cassette(cassette_name):
530+
result_iterator = self.gh.search_commits(
531+
'css repo:octocat/Spoon-Knife')
532+
commit_result = next(result_iterator)
533+
534+
assert isinstance(commit_result, github3.search.CommitSearchResult)
535+
536+
def test_search_commits_with_text_match(self):
537+
"""Test the ability to search for commits with text matches."""
538+
cassette_name = self.cassette_name('search_commits_with_text_match')
539+
with self.recorder.use_cassette(cassette_name):
540+
result_iterator = self.gh.search_commits(
541+
'css repo:octocat/Spoon-Knife', text_match=True)
542+
commit_result = next(result_iterator)
543+
544+
assert isinstance(commit_result, github3.search.CommitSearchResult)
545+
assert len(commit_result.text_matches) > 0
546+
526547
def test_search_users(self):
527548
"""Test the ability to use the user search endpoint."""
528549
cassette_name = self.cassette_name('search_users')

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy