diff --git a/lib/pygithub/__init__.py b/lib/pygithub/__init__.py deleted file mode 100644 index ad56e08736281760d4c5d20c45411ed16e552588..0000000000000000000000000000000000000000 --- a/lib/pygithub/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# <http://www.opensource.org/licenses/mit-license.php> -""" -github module. -""" -__all__ = ['github','ghsearch','githubsync'] diff --git a/lib/pygithub/ghsearch.py b/lib/pygithub/ghsearch.py deleted file mode 100644 index 586e502b18297fd16026e579ec70fe611e297284..0000000000000000000000000000000000000000 --- a/lib/pygithub/ghsearch.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# <http://www.opensource.org/licenses/mit-license.php> -""" -Search script. -""" - -import sys - -import github - -def usage(): - """display the usage and exit""" - print "Usage: %s keyword [keyword...]" % (sys.argv[0]) - sys.exit(1) - -def mk_url(repo): - return "http://github.com/%s/%s" % (repo.username, repo.name) - -if __name__ == '__main__': - g = github.GitHub() - if len(sys.argv) < 2: - usage() - res = g.repos.search(' '.join(sys.argv[1:])) - - for repo in res: - try: - print "Found %s at %s" % (repo.name, mk_url(repo)) - except AttributeError: - print "Bug: Couldn't format %s" % repo.__dict__ diff --git a/lib/pygithub/github.py b/lib/pygithub/github.py deleted file mode 100644 index c0bfad7780f8bf8a0a5b6cb75397c2dcc9efb59b..0000000000000000000000000000000000000000 --- a/lib/pygithub/github.py +++ /dev/null @@ -1,520 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# <http://www.opensource.org/licenses/mit-license.php> -""" -Interface to github's API (v2). - -Basic usage: - -g = GitHub() - -for r in g.user.search('dustin'): - print r.name - -See the GitHub docs or README.markdown for more usage. - -Copyright (c) 2007 Dustin Sallings <dustin@spy.net> -""" - -# GAE friendly URL detection (theoretically) -try: - import urllib2 - default_fetcher = urllib2.urlopen -except LoadError: - pass - -import urllib -import xml -import xml.dom.minidom - -def _string_parser(x): - """Extract the data from the first child of the input.""" - return x.firstChild.data - -_types = { - 'string': _string_parser, - 'integer': lambda x: int(_string_parser(x)), - 'float': lambda x: float(_string_parser(x)), - 'datetime': _string_parser, - 'boolean': lambda x: _string_parser(x) == 'true' -} - -def _parse(el): - """Generic response parser.""" - - type = 'string' - if el.attributes and 'type' in el.attributes.keys(): - type = el.attributes['type'].value - elif el.localName in _types: - type = el.localName - elif len(el.childNodes) > 1: - # This is a container, find the child type - type = None - ch = el.firstChild - while ch and not type: - if ch.localName == 'type': - type = ch.firstChild.data - ch = ch.nextSibling - - if not type: - raise Exception("Can't parse %s, known: %s" - % (el.toxml(), repr(_types.keys()))) - - return _types[type](el) - -def parses(t): - """Parser for a specific type in the github response.""" - def f(orig): - orig.parses = t - return orig - return f - -def with_temporary_mappings(m): - """Allow temporary localized altering of type mappings.""" - def f(orig): - def every(self, *args): - global _types - o = _types.copy() - for k,v in m.items(): - if v: - _types[k] = v - else: - del _types[k] - try: - return orig(self, *args) - finally: - _types = o - return every - return f - -@parses('array') -def _parseArray(el): - rv = [] - ch = el.firstChild - while ch: - if ch.nodeType != xml.dom.Node.TEXT_NODE and ch.firstChild: - rv.append(_parse(ch)) - ch=ch.nextSibling - return rv - -class BaseResponse(object): - """Base class for XML Response Handling.""" - - def __init__(self, el): - ch = el.firstChild - while ch: - if ch.nodeType != xml.dom.Node.TEXT_NODE and ch.firstChild: - ln = ch.localName.replace('-', '_') - self.__dict__[ln] = _parse(ch) - ch=ch.nextSibling - - def __repr__(self): - return "<<%s>>" % str(self.__class__) - -class User(BaseResponse): - """A github user.""" - - parses = 'user' - - def __repr__(self): - return "<<User %s>>" % self.name - -class Plan(BaseResponse): - """A github plan.""" - - parses = 'plan' - - def __repr__(self): - return "<<Plan %s>>" % self.name - -class Repository(BaseResponse): - """A repository.""" - - parses = 'repository' - - @property - def owner_name(self): - if hasattr(self, 'owner'): - return self.owner - else: - return self.username - - def __repr__(self): - return "<<Repository %s/%s>>" % (self.owner_name, self.name) - -class PublicKey(BaseResponse): - """A public key.""" - - parses = 'public-key' - title = 'untitled' - - def __repr__(self): - return "<<Public key %s>>" % self.title - -class Commit(BaseResponse): - """A commit.""" - - parses = 'commit' - - def __repr__(self): - return "<<Commit: %s>>" % self.id - -class Parent(Commit): - """A commit parent.""" - - parses = 'parent' - -class Author(User): - """A commit author.""" - - parses = 'author' - -class Committer(User): - """A commit committer.""" - - parses = 'committer' - -class Issue(BaseResponse): - """An issue within the issue tracker.""" - - parses = 'issue' - - def __repr__(self): - return "<<Issue #%d>>" % self.number - -class Label(BaseResponse): - """A Label within the issue tracker.""" - parses = 'label' - - def __repr__(self): - return "<<Label $%d>>" % self.number - -class Tree(BaseResponse): - """A Tree object.""" - - # Parsing is scoped to objects... - def __repr__(self): - return "<<Tree: %s>>" % self.name - -class Blob(BaseResponse): - """A Blob object.""" - - # Parsing is scoped to objects... - def __repr__(self): - return "<<Blob: %s>>" % self.name - -class Modification(BaseResponse): - """A modification object.""" - - # Parsing is scoped to usage - def __repr__(self): - return "<<Modification of %s>>" % self.filename - -class Network(BaseResponse): - """A network entry.""" - - parses = 'network' - - def __repr__(self): - return "<<Network of %s/%s>>" % (self.owner, self.name) - -# Load the known types. -for __t in (t for t in globals().values() if hasattr(t, 'parses')): - _types[__t.parses] = __t - -class BaseEndpoint(object): - - BASE_URL = 'https://github.com/api/v2/xml/' - - def __init__(self, user, token, fetcher): - self.user = user - self.token = token - self.fetcher = fetcher - - def _raw_fetch(self, path): - p = self.BASE_URL + path - args = '' - if self.user and self.token: - params = '&'.join(['login=' + urllib.quote(self.user), - 'token=' + urllib.quote(self.token)]) - if '?' in path: - p += params - else: - p += '?' + params - return self.fetcher(p).read() - - def _fetch(self, path): - return xml.dom.minidom.parseString(self._raw_fetch(path)) - - def _post(self, path, **kwargs): - p = {'login': self.user, 'token': self.token} - p.update(kwargs) - return self.fetcher(self.BASE_URL + path, urllib.urlencode(p)).read() - - def _parsed(self, path): - doc = self._fetch(path) - return _parse(doc.documentElement) - - def _posted(self,path,**kwargs): - stuff = self._post(path,**kwargs) - doc = xml.dom.minidom.parseString(stuff) - return _parse(doc.documentElement) - -class UserEndpoint(BaseEndpoint): - - def search(self, query): - """Search for a user.""" - return self._parsed('user/search/' + query) - - def show(self, username): - """Get the info for a user.""" - return self._parsed('user/show/' + username) - - def keys(self): - """Get the public keys for a user.""" - return self._parsed('user/keys') - - def removeKey(self, keyId): - """Remove the key with the given ID (as retrieved from keys)""" - self._post('user/key/remove', id=keyId) - - def addKey(self, name, key): - """Add an ssh key.""" - self._post('user/key/add', name=name, key=key) - -class RepositoryEndpoint(BaseEndpoint): - - def forUser(self, username): - """Get the repositories for the given user.""" - return self._parsed('repos/show/' + username) - - def branches(self, user, repo): - """List the branches for a repo.""" - doc = self._fetch("repos/show/" + user + "/" + repo + "/branches") - rv = {} - for c in doc.documentElement.childNodes: - if c.nodeType != xml.dom.Node.TEXT_NODE: - rv[c.localName] = str(c.firstChild.data) - return rv - - def search(self, term): - """Search for repositories.""" - return self._parsed('repos/search/' + urllib.quote_plus(term)) - - def show(self, user, repo): - """Lookup an individual repository.""" - return self._parsed('/'.join(['repos', 'show', user, repo])) - - def watch(self, user, repo): - """Watch a repository.""" - self._post('repos/watch/' + user + '/' + repo) - - def unwatch(self, user, repo): - """Stop watching a repository.""" - self._post('repos/unwatch/' + user + '/' + repo) - - def watched(self, user): - """Get watched repositories of a user.""" - return self._parsed('repos/watched/' + user) - - def network(self, user, repo): - """Get the network for a given repo.""" - return self._parsed('repos/show/' + user + '/' + repo + '/network') - - def setVisible(self, repo, public=True): - """Set the visibility of the given repository (owned by the current user).""" - if public: - path = 'repos/set/public/' + repo - else: - path = 'repos/set/private/' + repo - self._post(path) - - def create(self, name, description='', homepage='', public=1): - """Create a new repository.""" - self._post('repos/create', name=name, description=description, - homepage=homepage, public=str(public)) - - def delete(self, repo): - """Delete a repository.""" - self._post('repos/delete/' + repo) - - def fork(self, user, repo): - """Fork a user's repo.""" - self._post('repos/fork/' + user + '/' + repo) - - def collaborators(self, user, repo): - """Find all of the collaborators of one of your repositories.""" - return self._parsed('repos/show/%s/%s/collaborators' % (user, repo)) - - def addCollaborator(self, repo, username): - """Add a collaborator to one of your repositories.""" - self._post('repos/collaborators/' + repo + '/add/' + username) - - def removeCollaborator(self, repo, username): - """Remove a collaborator from one of your repositories.""" - self._post('repos/collaborators/' + repo + '/remove/' + username) - - def collaborators_all(self): - """Find all of the collaborators of every of your repositories. - - Returns a dictionary with reponame as key and a list of collaborators as value.""" - ret = {} - for reponame in (rp.name for rp in self.forUser(self.user)): - ret[reponame] = self.collaborators(self.user, reponame) - return ret - - def addCollaborator_all(self, username): - """Add a collaborator to all of your repositories.""" - for reponame in (rp.name for rp in self.forUser(self.user)): - self.addCollaborator(reponame, username) - - def removeCollaborator_all(self, username): - """Remove a collaborator from all of your repositories.""" - for reponame in (rp.name for rp in self.forUser(self.user)): - self.removeCollaborator(reponame, username) - - def deployKeys(self, repo): - """List the deploy keys for the given repository. - - The repository must be owned by the current user.""" - return self._parsed('repos/keys/' + repo) - - def addDeployKey(self, repo, title, key): - """Add a deploy key to a repository.""" - self._post('repos/key/' + repo + '/add', title=title, key=key) - - def removeDeployKey(self, repo, keyId): - """Remove a deploy key.""" - self._post('repos/key/' + repo + '/remove', id=keyId) - -class CommitEndpoint(BaseEndpoint): - - def forBranch(self, user, repo, branch='master'): - """Get the commits for the given branch.""" - return self._parsed('/'.join(['commits', 'list', user, repo, branch])) - - def forFile(self, user, repo, path, branch='master'): - """Get the commits for the given file within the given branch.""" - return self._parsed('/'.join(['commits', 'list', user, repo, branch, path])) - - @with_temporary_mappings({'removed': _parseArray, - 'added': _parseArray, - 'modified': Modification, - 'diff': _string_parser, - 'filename': _string_parser}) - def show(self, user, repo, sha): - """Get an individual commit.""" - c = self._parsed('/'.join(['commits', 'show', user, repo, sha])) - # Some fixup due to weird XML structure - if hasattr(c, 'removed'): - c.removed = [i[0] for i in c.removed] - if hasattr(c, 'added'): - c.added = [i[0] for i in c.added] - return c - -class IssuesEndpoint(BaseEndpoint): - - @with_temporary_mappings({'user': None}) - def list(self, user, repo, state='open'): - """Get the list of issues for the given repo in the given state.""" - return self._parsed('/'.join(['issues', 'list', user, repo, state])) - - @with_temporary_mappings({'user': None}) - def show(self, user, repo, issue_id): - """Show an individual issue.""" - return self._parsed('/'.join(['issues', 'show', user, repo, str(issue_id)])) - - def add_label(self, user, repo, issue_id, label): - """Add a label to an issue.""" - self._post('issues/label/add/' + user + '/' - + repo + '/' + label + '/' + str(issue_id)) - - def remove_label(self, user, repo, issue_id, label): - """Remove a label from an issue.""" - self._post('issues/label/remove/' + user + '/' - + repo + '/' + label + '/' + str(issue_id)) - - def close(self, user, repo, issue_id): - """Close an issue.""" - self._post('/'.join(['issues', 'close', user, repo, str(issue_id)])) - - def reopen(self, user, repo, issue_id): - """Reopen an issue.""" - self._post('/'.join(['issues', 'reopen', user, repo, str(issue_id)])) - - def new(self, user, repo, title, body=''): - """Create a new issue.""" - return self._posted('/'.join(['issues', 'open', user, repo]), - title=title, body=body) - - def edit(self, user, repo, issue_id, title, body): - """Create a new issue.""" - self._post('/'.join(['issues', 'edit', user, repo, str(issue_id)]), - title=title, body=body) - -class ObjectsEndpoint(BaseEndpoint): - - @with_temporary_mappings({'tree': Tree, 'type': _string_parser}) - def tree(self, user, repo, t): - """Get the given tree from the given repo.""" - tl = self._parsed('/'.join(['tree', 'show', user, repo, t])) - return dict([(t.name, t) for t in tl]) - - @with_temporary_mappings({'blob': Blob}) - def blob(self, user, repo, t, fn): - return self._parsed('/'.join(['blob', 'show', user, repo, t, fn])) - - def raw_blob(self, user, repo, sha): - """Get a raw blob from a repo.""" - path = 'blob/show/%s/%s/%s' % (user, repo, sha) - return self._raw_fetch(path) - -class GitHub(object): - """Interface to github.""" - - def __init__(self, user=None, token=None, fetcher=default_fetcher): - self.user = user - self.token = token - self.fetcher = fetcher - - @property - def users(self): - """Get access to the user API.""" - return UserEndpoint(self.user, self.token, self.fetcher) - - @property - def repos(self): - """Get access to the user API.""" - return RepositoryEndpoint(self.user, self.token, self.fetcher) - - @property - def commits(self): - return CommitEndpoint(self.user, self.token, self.fetcher) - - @property - def issues(self): - return IssuesEndpoint(self.user, self.token, self.fetcher) - - @property - def objects(self): - return ObjectsEndpoint(self.user, self.token, self.fetcher) diff --git a/lib/pygithub/githubsync.py b/lib/pygithub/githubsync.py deleted file mode 100644 index 50d70a076ad566970637f9f812dd733ee07f9b26..0000000000000000000000000000000000000000 --- a/lib/pygithub/githubsync.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# <http://www.opensource.org/licenses/mit-license.php> -""" -Grab all of a user's projects from github. -""" - -import os -import sys -import subprocess - -import github - -def check_for_old_format(path, url): - p = subprocess.Popen(['git', '--git-dir=' + path, 'config', - 'remote.origin.fetch'], stdout = subprocess.PIPE) - stdout, stderr = p.communicate() - if stdout.strip() != '+refs/*:refs/*': - print "Not properly configured for mirroring, repairing." - subprocess.call(['git', '--git-dir=' + path, 'remote', 'rm', 'origin']) - add_mirror(path, url) - -def add_mirror(path, url): - subprocess.call(['git', '--git-dir=' + path, 'remote', 'add', '--mirror', - 'origin', url]) - -def sync(path, url, repo_name): - p = os.path.join(path, repo_name) + ".git" - print "Syncing %s -> %s" % (repo_name, p) - if not os.path.exists(p): - subprocess.call(['git', 'clone', '--bare', url, p]) - add_mirror(p, url) - check_for_old_format(p, url) - subprocess.call(['git', '--git-dir=' + p, 'fetch', '-f']) - -def sync_user_repo(path, repo): - sync(path, "git://github.com/%s/%s" % (repo.owner, repo.name), repo.name) - -def usage(): - sys.stderr.write("Usage: %s username destination_url\n" % sys.argv[0]) - sys.stderr.write( - """Ensures you've got the latest stuff for the given user. - -Also, if the file $HOME/.github-private exists, it will be read for -additional projects. - -Each line must be a simple project name (e.g. py-github), a tab character, -and a git URL. -""") - -if __name__ == '__main__': - try: - user, path = sys.argv[1:] - except ValueError: - usage() - exit(1) - - privfile = os.path.join(os.getenv("HOME"), ".github-private") - if os.path.exists(privfile): - f = open(privfile) - for line in f: - name, url = line.strip().split("\t") - sync(path, url, name) - - gh = github.GitHub() - - for repo in gh.repos.forUser(user): - sync_user_repo(path, repo) diff --git a/lib/pygithub/githubtest.py b/lib/pygithub/githubtest.py deleted file mode 100644 index bfb73cd892db1262689bad17bc74c6617fbd83f9..0000000000000000000000000000000000000000 --- a/lib/pygithub/githubtest.py +++ /dev/null @@ -1,493 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# <http://www.opensource.org/licenses/mit-license.php> -""" -Defines and runs unittests. -""" - -import urllib -import hashlib -import unittest - -import github - -class BaseCase(unittest.TestCase): - - def _gh(self, expUrl, filename): - - def opener(url): - self.assertEquals(expUrl, url) - return open(filename) - return github.GitHub(fetcher=opener) - - def _agh(self, expUrl, u, t, filename): - - def opener(url): - self.assertEquals(expUrl, url + '?login=' + u + '&token=' + t) - return open(filename) - return github.GitHub(fetcher=opener) - - def _ghp(self, expUrl, u, t, **kv): - - def opener(url, data): - h = {'login': u, 'token': t} - h.update(kv) - self.assertEquals(github.BaseEndpoint.BASE_URL + expUrl, url) - self.assertEquals(sorted(data.split('&')), - sorted(urllib.urlencode(h).split('&'))) - return github.GitHub(u, t, fetcher=opener) - -class UserTest(BaseCase): - - def __loadUserSearch(self): - return self._gh('http://github.com/api/v2/xml/user/search/dustin', - 'data/user.search.xml').users.search('dustin') - - def __loadUser(self, which, u=None, p=None): - if u: - return self._agh('http://github.com/api/v2/xml/user/show/dustin' - + '?login=' + u + '&token=' + p, - u, p, 'data/' + which).users.show('dustin') - - else: - return self._gh('http://github.com/api/v2/xml/user/show/dustin', - 'data/' + which).users.show('dustin') - - def testUserSearch(self): - """Test the base properties of the user object.""" - u = self.__loadUserSearch()[0] - self.assertEquals("Dustin Sallings", u.fullname) - self.assertEquals("dustin", u.name) - self.assertEquals("dustin@spy.net", u.email) - self.assertEquals("Santa Clara, CA", u.location) - self.assertEquals("Ruby", u.language) - self.assertEquals(35, u.actions) - self.assertEquals(77, u.repos) - self.assertEquals(78, u.followers) - self.assertEquals('user-1779', u.id) - self.assertAlmostEquals(12.231684, u.score) - self.assertEquals('user', u.type) - self.assertEquals('2008-02-29T17:59:09Z', u.created) - self.assertEquals('2009-03-19T09:15:24.663Z', u.pushed) - self.assertEquals("<<User dustin>>", repr(u)) - - def testUserPublic(self): - """Test the user show API with no authentication.""" - u = self.__loadUser('user.public.xml') - self.assertEquals("Dustin Sallings", u.name) - # self.assertEquals(None, u.company) - self.assertEquals(10, u.following_count) - self.assertEquals(21, u.public_gist_count) - self.assertEquals(81, u.public_repo_count) - self.assertEquals('http://bleu.west.spy.net/~dustin/', u.blog) - self.assertEquals(1779, u.id) - self.assertEquals(82, u.followers_count) - self.assertEquals('dustin', u.login) - self.assertEquals('Santa Clara, CA', u.location) - self.assertEquals('dustin@spy.net', u.email) - self.assertEquals('2008-02-29T09:59:09-08:00', u.created_at) - - def testUserPrivate(self): - """Test the user show API with extra info from auth.""" - u = self.__loadUser('user.private.xml', 'dustin', 'blahblah') - self.assertEquals("Dustin Sallings", u.name) - # self.assertEquals(None, u.company) - self.assertEquals(10, u.following_count) - self.assertEquals(21, u.public_gist_count) - self.assertEquals(81, u.public_repo_count) - self.assertEquals('http://bleu.west.spy.net/~dustin/', u.blog) - self.assertEquals(1779, u.id) - self.assertEquals(82, u.followers_count) - self.assertEquals('dustin', u.login) - self.assertEquals('Santa Clara, CA', u.location) - self.assertEquals('dustin@spy.net', u.email) - self.assertEquals('2008-02-29T09:59:09-08:00', u.created_at) - - # Begin private data - - self.assertEquals("micro", u.plan.name) - self.assertEquals(1, u.plan.collaborators) - self.assertEquals(614400, u.plan.space) - self.assertEquals(5, u.plan.private_repos) - self.assertEquals(155191, u.disk_usage) - self.assertEquals(6, u.collaborators) - self.assertEquals(4, u.owned_private_repo_count) - self.assertEquals(5, u.total_private_repo_count) - self.assertEquals(0, u.private_gist_count) - - def testKeysList(self): - """Test key listing.""" - kl = self._agh('http://github.com/api/v2/xml/user/keys?login=dustin&token=blahblah', - 'dustin', 'blahblah', 'data/keys.xml').users.keys() - self.assertEquals(7, len(kl)) - k = kl[0] - - self.assertEquals('some key', k.title) - self.assertEquals(2181, k.id) - self.assertEquals(549, k.key.find('cdEXwCSjAIFp8iRqh3GOkxGyFSc25qv/MuOBg==')) - - def testRemoveKey(self): - """Remove a key.""" - self._ghp('user/key/remove', - 'dustin', 'p', id=828).users.removeKey(828) - - def testAddKey(self): - """Add a key.""" - self._ghp('user/key/add', - 'dustin', 'p', name='my key', key='some key').users.addKey( - 'my key', 'some key') - -class RepoTest(BaseCase): - - def __loadUserRepos(self): - return self._gh('http://github.com/api/v2/xml/repos/show/verbal', - 'data/repos.xml').repos.forUser('verbal') - - def testUserRepoList(self): - """Get a list of repos for a user.""" - rs = self.__loadUserRepos() - self.assertEquals(10, len(rs)) - r = rs[0] - self.assertEquals('A beanstalk client for the twisted network framework.', - r.description) - self.assertEquals(2, r.watchers) - self.assertEquals(0, r.forks) - self.assertEquals('beanstalk-client-twisted', r.name) - self.assertEquals(False, r.private) - self.assertEquals('http://github.com/verbal/beanstalk-client-twisted', - r.url) - self.assertEquals(True, r.fork) - self.assertEquals('verbal', r.owner) - # XXX: Can't parse empty elements. :( - # self.assertEquals('', r.homepage) - - def testRepoSearch(self): - """Test searching a repository.""" - rl = self._gh('http://github.com/api/v2/xml/repos/search/ruby+testing', - 'data/repos.search.xml').repos.search('ruby testing') - self.assertEquals(12, len(rl)) - - r = rl[0] - self.assertEquals('synthesis', r.name) - self.assertAlmostEquals(0.3234576, r.score, 4) - self.assertEquals(4656, r.actions) - self.assertEquals(2048, r.size) - self.assertEquals('Ruby', r.language) - self.assertEquals(26, r.followers) - self.assertEquals('gmalamid', r.username) - self.assertEquals('repo', r.type) - self.assertEquals('repo-3555', r.id) - self.assertEquals(1, r.forks) - self.assertFalse(r.fork) - self.assertEquals('Ruby test code analysis tool employing a ' - '"Synthesized Testing" strategy, aimed to reduce ' - 'the volume of slower, coupled, complex wired tests.', - r.description) - self.assertEquals('2009-01-08T13:45:06Z', r.pushed) - self.assertEquals('2008-03-11T23:38:04Z', r.created) - - def testBranchList(self): - """Test branch listing for a repo.""" - bl = self._gh('http://github.com/api/v2/xml/repos/show/schacon/ruby-git/branches', - 'data/repos.branches.xml').repos.branches('schacon', 'ruby-git') - self.assertEquals(4, len(bl)) - self.assertEquals('ee90922f3da3f67ef19853a0759c1d09860fe3b3', bl['master']) - - def testGetOneRepo(self): - """Fetch an individual repository.""" - r = self._gh('http://github.com/api/v2/xml/repos/show/schacon/grit', - 'data/repo.xml').repos.show('schacon', 'grit') - - self.assertEquals('Grit is a Ruby library for extracting information from a ' - 'git repository in an object oriented manner - this fork ' - 'tries to intergrate as much pure-ruby functionality as possible', - r.description) - self.assertEquals(68, r.watchers) - self.assertEquals(4, r.forks) - self.assertEquals('grit', r.name) - self.assertFalse(r.private) - self.assertEquals('http://github.com/schacon/grit', r.url) - self.assertTrue(r.fork) - self.assertEquals('schacon', r.owner) - self.assertEquals('http://grit.rubyforge.org/', r.homepage) - - def testGetRepoNetwork(self): - """Test network fetching.""" - nl = self._gh('http://github.com/api/v2/xml/repos/show/dustin/py-github/network', - 'data/network.xml').repos.network('dustin', 'py-github') - self.assertEquals(5, len(nl)) - - n = nl[0] - self.assertEquals('Python interface for talking to the github API', - n.description) - self.assertEquals('py-github', n.name) - self.assertFalse(n.private) - self.assertEquals('http://github.com/dustin/py-github', n.url) - self.assertEquals(30, n.watchers) - self.assertEquals(4, n.forks) - self.assertFalse(n.fork) - self.assertEquals('dustin', n.owner) - self.assertEquals('http://dustin.github.com/2008/12/29/github-sync.html', - n.homepage) - - def testSetPublic(self): - """Test setting a repo visible.""" - self._ghp('repos/set/public/py-github', 'dustin', 'p').repos.setVisible( - 'py-github') - - def testSetPrivate(self): - """Test setting a repo to private.""" - self._ghp('repos/set/private/py-github', 'dustin', 'p').repos.setVisible( - 'py-github', False) - - def testCreateRepository(self): - """Test creating a repository.""" - self._ghp('repos/create', 'dustin', 'p', - name='testrepo', - description='woo', - homepage='', - public='1').repos.create( - 'testrepo', description='woo') - - def testDeleteRepo(self): - """Test setting a repo to private.""" - self._ghp('repos/delete/mytest', 'dustin', 'p').repos.delete('mytest') - - def testFork(self): - """Test forking'""" - self._ghp('repos/fork/someuser/somerepo', 'dustin', 'p').repos.fork( - 'someuser', 'somerepo') - - def testAddCollaborator(self): - """Adding a collaborator.""" - self._ghp('repos/collaborators/memcached/add/trondn', - 'dustin', 'p').repos.addCollaborator('memcached', 'trondn') - - def testRemoveCollaborator(self): - """Removing a collaborator.""" - self._ghp('repos/collaborators/memcached/remove/trondn', - 'dustin', 'p').repos.removeCollaborator('memcached', 'trondn') - - def testAddDeployKey(self): - """Add a deploy key.""" - self._ghp('repos/key/blah/add', 'dustin', 'p', - title='title', key='key').repos.addDeployKey('blah', 'title', 'key') - - def testRemoveDeployKey(self): - """Remove a deploy key.""" - self._ghp('repos/key/blah/remove', 'dustin', 'p', - id=5).repos.removeDeployKey('blah', 5) - -class CommitTest(BaseCase): - - def testCommitList(self): - """Test commit list.""" - cl = self._gh('http://github.com/api/v2/xml/commits/list/mojombo/grit/master', - 'data/commits.xml').commits.forBranch('mojombo', 'grit') - self.assertEquals(30, len(cl)) - - c = cl[0] - self.assertEquals("Regenerated gemspec for version 1.1.1", c.message) - self.assertEquals('4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', c.id) - self.assertEquals('94490563ebaf733cbb3de4ad659eb58178c2e574', c.tree) - self.assertEquals('2009-03-31T09:54:51-07:00', c.committed_date) - self.assertEquals('2009-03-31T09:54:51-07:00', c.authored_date) - self.assertEquals('http://github.com/mojombo/grit/commit/4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', - c.url) - self.assertEquals(1, len(c.parents)) - self.assertEquals('5071bf9fbfb81778c456d62e111440fdc776f76c', c.parents[0].id) - self.assertEquals('Tom Preston-Werner', c.author.name) - self.assertEquals('tom@mojombo.com', c.author.email) - self.assertEquals('Tom Preston-Werner', c.committer.name) - self.assertEquals('tom@mojombo.com', c.committer.email) - - def testCommitListForFile(self): - """Test commit list for a file.""" - cl = self._gh('http://github.com/api/v2/xml/commits/list/mojombo/grit/master/grit.gemspec', - 'data/commits.xml').commits.forFile('mojombo', 'grit', 'grit.gemspec') - self.assertEquals(30, len(cl)) - - c = cl[0] - self.assertEquals("Regenerated gemspec for version 1.1.1", c.message) - self.assertEquals('4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', c.id) - self.assertEquals('94490563ebaf733cbb3de4ad659eb58178c2e574', c.tree) - self.assertEquals('2009-03-31T09:54:51-07:00', c.committed_date) - self.assertEquals('2009-03-31T09:54:51-07:00', c.authored_date) - self.assertEquals('http://github.com/mojombo/grit/commit/4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', - c.url) - self.assertEquals(1, len(c.parents)) - self.assertEquals('5071bf9fbfb81778c456d62e111440fdc776f76c', c.parents[0].id) - self.assertEquals('Tom Preston-Werner', c.author.name) - self.assertEquals('tom@mojombo.com', c.author.email) - self.assertEquals('Tom Preston-Werner', c.committer.name) - self.assertEquals('tom@mojombo.com', c.committer.email) - - def testIndividualCommit(self): - """Grab a single commit.""" - h = '4c86fa592fcc7cb685c6e9d8b6aebe8dcbac6b3e' - c = self._gh('http://github.com/api/v2/xml/commits/show/dustin/memcached/' + h, - 'data/commit.xml').commits.show('dustin', 'memcached', h) - self.assertEquals(['internal_tests.c'], c.removed) - self.assertEquals(set(['cache.c', 'cache.h', 'testapp.c']), set(c.added)) - self.assertEquals('Create a generic cache for objects of same size\n\n' - 'The suffix pool could be thread-local and use the generic cache', - c.message) - - self.assertEquals(6, len(c.modified)) - self.assertEquals('.gitignore', c.modified[0].filename) - self.assertEquals(140, len(c.modified[0].diff)) - - self.assertEquals(['ee0c3d5ae74d0862b4d9990e2ad13bc79f8c34df'], - [p.id for p in c.parents]) - self.assertEquals('http://github.com/dustin/memcached/commit/' + h, c.url) - self.assertEquals('Trond Norbye', c.author.name) - self.assertEquals('Trond.Norbye@sun.com', c.author.email) - self.assertEquals(h, c.id) - self.assertEquals('2009-04-17T16:15:52-07:00', c.committed_date) - self.assertEquals('2009-03-27T10:30:16-07:00', c.authored_date) - self.assertEquals('94b644163f6381a9930e2d7c583fae023895b903', c.tree) - self.assertEquals('Dustin Sallings', c.committer.name) - self.assertEquals('dustin@spy.net', c.committer.email) - - def testWatchRepo(self): - """Test watching a repo.""" - self._ghp('repos/watch/dustin/py-github', 'dustin', 'p').repos.watch( - 'dustin', 'py-github') - - def testWatchRepo(self): - """Test watching a repo.""" - self._ghp('repos/unwatch/dustin/py-github', 'dustin', 'p').repos.unwatch( - 'dustin', 'py-github') - -class IssueTest(BaseCase): - - def testListIssues(self): - """Test listing issues.""" - il = self._gh('http://github.com/api/v2/xml/issues/list/schacon/simplegit/open', - 'data/issues.list.xml').issues.list('schacon', 'simplegit') - self.assertEquals(1, len(il)) - i = il[0] - - self.assertEquals('schacon', i.user) - self.assertEquals('2009-04-17T16:19:02-07:00', i.updated_at) - self.assertEquals('something', i.body) - self.assertEquals('new', i.title) - self.assertEquals(2, i.number) - self.assertEquals(0, i.votes) - self.assertEquals(1.0, i.position) - self.assertEquals('2009-04-17T16:18:50-07:00', i.created_at) - self.assertEquals('open', i.state) - - def testShowIssue(self): - """Show an individual issue.""" - i = self._gh('http://github.com/api/v2/xml/issues/show/dustin/py-github/1', - 'data/issues.show.xml').issues.show('dustin', 'py-github', 1) - - self.assertEquals('dustin', i.user) - self.assertEquals('2009-04-17T18:37:04-07:00', i.updated_at) - self.assertEquals('http://develop.github.com/p/general.html', i.body) - self.assertEquals('Add auth tokens', i.title) - self.assertEquals(1, i.number) - self.assertEquals(0, i.votes) - self.assertEquals(1.0, i.position) - self.assertEquals('2009-04-17T17:00:58-07:00', i.created_at) - self.assertEquals('closed', i.state) - - def testAddLabel(self): - """Adding a label to an issue.""" - self._ghp('issues/label/add/dustin/py-github/todo/33', 'd', 'pw').issues.add_label( - 'dustin', 'py-github', 33, 'todo') - - def testRemoveLabel(self): - """Removing a label from an issue.""" - self._ghp('issues/label/remove/dustin/py-github/todo/33', - 'd', 'pw').issues.remove_label( - 'dustin', 'py-github', 33, 'todo') - - def testCloseIssue(self): - """Closing an issue.""" - self._ghp('issues/close/dustin/py-github/1', 'd', 'pw').issues.close( - 'dustin', 'py-github', 1) - - def testReopenIssue(self): - """Reopening an issue.""" - self._ghp('issues/reopen/dustin/py-github/1', 'd', 'pw').issues.reopen( - 'dustin', 'py-github', 1) - - def testCreateIssue(self): - """Creating an issue.""" - self._ghp('issues/open/dustin/py-github', 'd', 'pw', - title='test title', body='').issues.new( - 'dustin', 'py-github', title='test title') - - def testEditIssue(self): - """Editing an existing issue.""" - self._ghp('issues/edit/dustin/py-github/1', 'd', 'pw', - title='new title', body='new body').issues.edit( - 'dustin', 'py-github', 1, 'new title', 'new body') - -class ObjectTest(BaseCase): - - def testTree(self): - """Test tree fetching.""" - h = '1ddd3f99f0b96019042239375b3ad4d45796ffba' - tl = self._gh('http://github.com/api/v2/xml/tree/show/dustin/py-github/' + h, - 'data/tree.xml').objects.tree('dustin', 'py-github', h) - self.assertEquals(8, len(tl)) - self.assertEquals('setup.py', tl['setup.py'].name) - self.assertEquals('6e290379ec58fa00ac9d1c2a78f0819a21397445', - tl['setup.py'].sha) - self.assertEquals('100755', tl['setup.py'].mode) - self.assertEquals('blob', tl['setup.py'].type) - - self.assertEquals('src', tl['src'].name) - self.assertEquals('5fb9175803334c82b3fd66f1b69502691b91cf4f', - tl['src'].sha) - self.assertEquals('040000', tl['src'].mode) - self.assertEquals('tree', tl['src'].type) - - def testBlob(self): - """Test blob fetching.""" - h = '1ddd3f99f0b96019042239375b3ad4d45796ffba' - blob = self._gh('http://github.com/api/v2/xml/blob/show/dustin/py-github/' - + h + '/setup.py', - 'data/blob.xml').objects.blob('dustin', 'py-github', h, 'setup.py') - self.assertEquals('setup.py', blob.name) - self.assertEquals(1842, blob.size) - self.assertEquals('6e290379ec58fa00ac9d1c2a78f0819a21397445', blob.sha) - self.assertEquals('100755', blob.mode) - self.assertEquals('text/plain', blob.mime_type) - self.assertEquals(1842, len(blob.data)) - self.assertEquals(1641, blob.data.index('Production/Stable')) - - def testRawBlob(self): - """Test raw blob fetching.""" - h = '6e290379ec58fa00ac9d1c2a78f0819a21397445' - blob = self._gh('http://github.com/api/v2/xml/blob/show/dustin/py-github/' + h, - 'data/setup.py').objects.raw_blob('dustin', 'py-github', h) - self.assertEquals('e2dc8aea9ae8961f4f5923f9febfdd0a', - hashlib.md5(blob).hexdigest()) - - -if __name__ == '__main__': - unittest.main() diff --git a/sickbeard/gh_api.py b/sickbeard/gh_api.py new file mode 100644 index 0000000000000000000000000000000000000000..32bb9cec8608a348db94403904afe2842c381529 --- /dev/null +++ b/sickbeard/gh_api.py @@ -0,0 +1,59 @@ +# Author: Nic Wolfe <nic@wolfeden.ca> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +try: + import json +except ImportError: + from lib import simplejson as json + +import urllib + +class GitHub(object): + """ + Simple api wrapper for the Github API v3. Currently only supports the small thing that SB + needs it for - list of cimmots. + """ + + def _access_API(self, path, params=None): + """ + Access the API at the path given and with the optional params given. + + path: A list of the path elements to use (eg. ['repos', 'midgetspy', 'Sick-Beard', 'commits']) + params: Optional dict of name/value pairs for extra params to send. (eg. {'per_page': 10}) + + Returns a deserialized json object of the result. Doesn't do any error checking (hope it works). + """ + + url = 'https://api.github.com/' + '/'.join(path) + + if params and type(params) is dict: + url += '?' + '&'.join([str(x) + '=' + str(params[x]) for x in params.keys()]) + + return json.load(urllib.urlopen(url)) + + def commits(self, user, repo, branch='master'): + """ + Uses the API to get a list of the 100 most recent commits from the specified user/repo/branch, starting from HEAD. + + user: The github username of the person whose repo you're querying + repo: The repo name to query + branch: Optional, the branch name to show commits from + + Returns a deserialized json object containing the commit info. See http://developer.github.com/v3/repos/commits/ + """ + return self._access_API(['repos', user, repo, 'commits'], {'per_page': 100, 'branch': branch}) diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index f4f040f8041382ec7a517095bc9196ce71a4dbdd..a0ef58b1a5049e375dbe4bc132104fa7594403d4 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -28,7 +28,7 @@ import urllib, urllib2 import zipfile, tarfile from urllib2 import URLError -from lib.pygithub import github +import gh_api as github class CheckVersion(): """ @@ -297,13 +297,13 @@ class GitUpdateManager(UpdateManager): gh = github.GitHub() # find newest commit - for curCommit in gh.commits.forBranch('midgetspy', 'Sick-Beard', version.SICKBEARD_VERSION): + for curCommit in gh.commits('midgetspy', 'Sick-Beard', version.SICKBEARD_VERSION): if not self._newest_commit_hash: - self._newest_commit_hash = curCommit.id + self._newest_commit_hash = curCommit['sha'] if not self._cur_commit_hash: break - if curCommit.id == self._cur_commit_hash: + if curCommit['sha'] == self._cur_commit_hash: break self._num_commits_behind += 1