Skip to content

Commit

Permalink
add tests for .diff endpoint (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
cvrebert committed Dec 20, 2013
1 parent 12fa071 commit 7da1a61
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 0 deletions.
186 changes: 186 additions & 0 deletions tests/fixtures/07b9bf1540305153ceeb4519a50b588c35a35464.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
diff --git a/api.py b/api.py
new file mode 100644
index 0000000..ae9d907
--- /dev/null
+++ b/api.py
@@ -0,0 +1,179 @@
+from flask import Flask, url_for
+from werkzeug.exceptions import NotFound
+app = Flask(__name__)
+from pygit2 import Repository, GIT_OBJ_COMMIT, GIT_OBJ_TREE, GIT_OBJ_BLOB, GIT_REF_SYMBOLIC
+from datetime import datetime, tzinfo, timedelta
+import json
+from base64 import b64encode
+
+class FixedOffset(tzinfo):
+ ZERO = timedelta(0)
+ def __init__(self, offset):
+ self._offset = timedelta(minutes = offset)
+
+ def utcoffset(self, dt):
+ return self._offset
+
+ def dst(self, dt):
+ return self.ZERO
+
+REPO_BASE = '/Users/rajiv/Code/'
+
+def _get_repo(repo_key):
+ path = REPO_BASE + repo_key
+ try:
+ return Repository(path)
+ except KeyError:
+ raise NotFound("repository not found")
+
+def _convert_signature(sig):
+ return {
+ "name" : sig.name,
+ "email" : sig.email,
+ "date" : datetime.fromtimestamp(sig.time, FixedOffset(sig.offset))
+ }
+
+def _convert_commit(repo_key, commit):
+ return {
+ "url": url_for('get_commit', _external=True, repo_key=repo_key, sha=commit.hex),
+ "sha": commit.hex,
+ "author": _convert_signature(commit.author),
+ "committer": _convert_signature(commit.committer),
+ "message": commit.message,
+ "tree" : {
+ "sha": commit.tree.hex,
+ "url": url_for('get_tree', _external=True, repo_key=repo_key, sha=commit.tree.hex),
+ },
+ "parents" : [{
+ "sha": c.hex,
+ "url": url_for('get_commit', _external=True, repo_key=repo_key, sha=c.hex)
+ } for c in commit.parents]
+ }
+
+def _convert_tree(repo_key, tree):
+ entry_list = []
+ for entry in tree:
+ obj = entry.to_object()
+ entry_data = {
+ "path": entry.name,
+ "sha": entry.hex,
+ }
+ if obj.type == GIT_OBJ_BLOB:
+ entry_data['type'] = "blob"
+ entry_data['size'] = obj.size
+ entry_data['url'] = url_for('get_blob', _external=True, repo_key=repo_key, sha=entry.hex)
+ elif obj.type == GIT_OBJ_TREE:
+ entry_data['type'] = "tree"
+ entry_data['url'] = url_for('get_tree', _external=True, repo_key=repo_key, sha=entry.hex)
+ entry_list.append(entry_data)
+
+ return {
+ "url": url_for('get_tree', _external=True, repo_key=repo_key, sha=tree.hex),
+ "sha": tree.hex,
+ "tree": entry_list,
+ #"mode": todo,
+ }
+
+def _linkobj_for_gitobj(repo_key, obj, include_type=False):
+ data = {}
+ data['sha'] = obj.hex
+ obj_type = None
+ if obj.type == GIT_OBJ_COMMIT:
+ obj_type = 'commit'
+ elif obj.type == GIT_OBJ_TREE:
+ obj_type = 'tree'
+ elif obj.type == GIT_OBJ_BLOB:
+ obj_type = 'blob'
+ if obj_type is not None:
+ data['url'] = url_for('get_' + obj_type, _external=True, repo_key=repo_key, sha=obj.hex)
+ if include_type:
+ data['type'] = obj_type
+ return data
+
+def _encode_blob_data(data):
+ try:
+ return 'utf-8', data.decode('utf-8')
+ except UnicodeDecodeError:
+ return 'base64', b64encode(data)
+
+def _convert_blob(repo_key, blob):
+ encoding, data = _encode_blob_data(blob.data)
+ return {
+ "url": url_for('get_blob', _external=True, repo_key=repo_key, sha=blob.hex),
+ "sha": blob.hex,
+ "size": blob.size,
+ "encoding": encoding,
+ "data": data,
+ }
+
+def _convert_ref(repo_key, ref, obj):
+ return {
+ "url": url_for('get_ref_list', _external=True, repo_key=repo_key, ref_path=ref.name[5:]), #[5:] to cut off the redundant refs/
+ "ref": ref.name,
+ "object": _linkobj_for_gitobj(repo_key, obj, include_type=True),
+ }
+
+def dthandler(obj):
+ if hasattr(obj, 'isoformat'):
+ return obj.isoformat()
+
+def _json_dump(obj):
+ return json.dumps(obj, default=dthandler)
+
+@app.route('/repos/<repo_key>/git/commits/<sha>')
+def get_commit(repo_key, sha):
+ repo = _get_repo(repo_key)
+ try:
+ commit = repo[unicode(sha)]
+ except KeyError:
+ raise NotFound("commit not found")
+ if commit.type != GIT_OBJ_COMMIT:
+ raise NotFound("sha not a commit")
+ return _json_dump(_convert_commit(repo_key, commit))
+
+
+@app.route('/repos/<repo_key>/git/trees/<sha>')
+def get_tree(repo_key, sha):
+ repo = _get_repo(repo_key)
+ try:
+ tree = repo[unicode(sha)]
+ except KeyError:
+ raise NotFound("tree not found")
+ if tree.type != GIT_OBJ_TREE:
+ raise NotFound("sha not a tree")
+ return _json_dump(_convert_tree(repo_key, tree))
+
+@app.route('/repos/<repo_key>/git/blobs/<sha>')
+def get_blob(repo_key, sha):
+ repo = _get_repo(repo_key)
+ try:
+ blob = repo[unicode(sha)]
+ except KeyError:
+ raise NotFound("blob not found")
+ if blob.type != GIT_OBJ_BLOB:
+ raise NotFound("sha not a blob")
+ return _json_dump(_convert_blob(repo_key, blob))
+
+
+
+@app.route('/repos/<repo_key>/git/refs')
+@app.route('/repos/<repo_key>/git/refs/<path:ref_path>')
+def get_ref_list(repo_key, ref_path=None):
+ if ref_path is not None:
+ ref_path = "refs/" + ref_path
+ else:
+ ref_path = ""
+ repo = _get_repo(repo_key)
+ ref_data = [
+ _convert_ref(repo_key, reference, repo[reference.oid]) for reference in filter(lambda x: x.type != GIT_REF_SYMBOLIC, [repo.lookup_reference(r) for r in filter(lambda x: x.startswith(ref_path), repo.listall_references())])
+ ]
+ if len(ref_data) == 1:
+ ref_data = ref_data[0]
+ return _json_dump(ref_data)
+
+
+if __name__ == '__main__':
+ app.debug = True
+ app.run(host="0.0.0.0")
+
+application = app
\ No newline at end of file
27 changes: 27 additions & 0 deletions tests/fixtures/d408fc2428bc6444cabd7f7b46edbe70b6992b16.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/README.md b/README.md
index 1b51a0e..c65dc8c 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,11 @@ REST API for Git data
Provides a read-only restful interface for accessing data from Git repositories (local to the server).
Modeled off the GitHub Git DB API for compatibility (see http://developer.github.com/v3/git/).

+Requires: flask, pygit2 (>= 0.18.1), libgit2 (>= 0.18).
+Must modify: REPO_BASE (root path for repositories, note only repositories immediately under this path are currently supported).
+
+api.py is a valid WSGI application.
+
--

All of these routes return JSON unless otherwise specified.
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..da23f6c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+Flask==0.9
+Jinja2==2.6
+Werkzeug==0.8.3
+pygit2==0.18.1
+wsgiref==0.1.2
28 changes: 28 additions & 0 deletions tests/test_restfulgit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@


TEST_SUBDIR = os.path.join(RESTFULGIT_REPO, 'tests')
FIXTURES_DIR = os.path.join(TEST_SUBDIR, 'fixtures')
GIT_MIRROR_DESCRIPTION_FILEPATH = os.path.join(RESTFULGIT_REPO, 'description')
NORMAL_CLONE_DESCRIPTION_FILEPATH = os.path.join(RESTFULGIT_REPO, '.git', 'description')
FIRST_COMMIT = "07b9bf1540305153ceeb4519a50b588c35a35464"
Expand Down Expand Up @@ -63,6 +64,14 @@ def config_override(self, key, val):
finally:
self.app.config[key] = orig_val

def _get_fixture_text(self, filename):
filepath = os.path.join(FIXTURES_DIR, filename)
with open(filepath, 'r') as fixture_file:
return fixture_file.read()

def assertTextEqualsFixture(self, text, fixture):
self.assertEqual(text, self._get_fixture_text(fixture))


class RepoKeyTestCase(_RestfulGitTestCase):
def test_nonexistent_directory(self):
Expand Down Expand Up @@ -757,6 +766,25 @@ def test_get_repo_commit(self):
self.assert200(resp)
self.assertEqual(reference, resp.json)

def test_get_diff_works(self):
resp = self.client.get('/repos/restfulgit/commits/d408fc2428bc6444cabd7f7b46edbe70b6992b16.diff')
resp = self.client.get('/repos/restfulgit/commit/d408fc2428bc6444cabd7f7b46edbe70b6992b16.diff')
self.assert200(resp)
self.assertEqual(resp.headers.get_all('Content-Type'), [b'text/x-diff; charset=utf-8'])
self.assertTextEqualsFixture(resp.get_data(), 'd408fc2428bc6444cabd7f7b46edbe70b6992b16.diff')

def test_get_diff_with_parentless_commit(self): # NOTE: RestfulGit extension; GitHub gives a 404 in this case
resp = self.client.get('/repos/restfulgit/commit/07b9bf1540305153ceeb4519a50b588c35a35464.diff')
self.assert200(resp)
self.assertEqual(resp.headers.get_all('Content-Type'), [b'text/x-diff; charset=utf-8'])
self.assertTextEqualsFixture(resp.get_data(), '07b9bf1540305153ceeb4519a50b588c35a35464.diff')

def test_get_diff_with_nonexistent_sha(self):
resp = self.client.get('/repos/restfulgit/commits/{}.diff'.format(IMPROBABLE_SHA))
resp = self.client.get('/repos/restfulgit/commit/{}.diff'.format(IMPROBABLE_SHA))
self.assertJson404(resp)


class RefsTestCase(_RestfulGitTestCase):
def test_get_refs_works(self):
# From https://api.github.com/repos/hulu/restfulgit/git/refs with necessary adjustments
Expand Down

0 comments on commit 7da1a61

Please sign in to comment.