Skip to content

Commit

Permalink
sort out Repo API for individual commits & their diffs
Browse files Browse the repository at this point in the history
X-Ref: #56
  • Loading branch information
cvrebert committed Dec 17, 2013
1 parent 33b3eca commit 765f8ac
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 23 deletions.
35 changes: 14 additions & 21 deletions restfulgit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _get_diff(repo, commit):
if commit.parents:
diff = repo.diff(commit.parents[0], commit)
diff.find_similar()
else:
else: # NOTE: RestfulGit extension; GitHub gives a 404 in this case
diff = commit.tree.diff_to_tree(swap=True)
return diff

Expand Down Expand Up @@ -226,6 +226,14 @@ def _convert_commit(repo_key, commit, porcelain=False):
message = commit.message
if porcelain:
message = message.rstrip('\n')
def commit_url_for(sha):
return url_for('.get_repos_commit', _external=True,
repo_key=repo_key, branch_or_tag_or_sha=sha)
else:
def commit_url_for(sha):
return url_for('.get_commit', _external=True,
repo_key=repo_key, sha=sha)

return {
"url": url_for('.get_commit', _external=True,
repo_key=repo_key, sha=commit.hex),
Expand All @@ -240,8 +248,7 @@ def _convert_commit(repo_key, commit, porcelain=False):
},
"parents": [{
"sha": c.hex,
"url": url_for(('.get_repos_commit' if porcelain else '.get_commit'), _external=True,
repo_key=repo_key, sha=c.hex)
"url": commit_url_for(c.hex)
} for c in commit.parents]
}

Expand Down Expand Up @@ -677,34 +684,20 @@ def get_repo_list():


@restfulgit.route('/repos/<repo_key>/commits/<branch_or_tag_or_sha>/')
@restfulgit.route('/repos/<repo_key>/commits/<sha:sha>/')
@corsify
@jsonify
def get_repos_commit(repo_key, sha=None, branch_or_tag_or_sha=None):
def get_repos_commit(repo_key, branch_or_tag_or_sha=None):
context.restfulgit_force_utc = True
repo = _get_repo(repo_key)
if sha:
try:
commit = _get_commit(repo, sha)
except NotFound:
branch_or_tag_or_sha = sha
if branch_or_tag_or_sha:
commit = _get_commit_for_refspec(repo, branch_or_tag_or_sha)
commit = _get_commit_for_refspec(repo, branch_or_tag_or_sha)
return _repos_convert_commit(repo_key, repo, commit, include_diff=True)


@restfulgit.route('/repos/<repo_key>/commits/<branch_or_tag_or_sha>.diff')
@restfulgit.route('/repos/<repo_key>/commits/<sha:sha>.diff')
@restfulgit.route('/repos/<repo_key>/commit/<branch_or_tag_or_sha>.diff')
@corsify
def get_repos_diff(repo_key, sha=None, branch_or_tag_or_sha=None):
repo = _get_repo(repo_key)
if sha:
try:
commit = _get_commit(repo, sha)
except NotFound:
branch_or_tag_or_sha = sha
if branch_or_tag_or_sha:
commit = _get_commit_for_refspec(repo, branch_or_tag_or_sha)
commit = _get_commit_for_refspec(repo, branch_or_tag_or_sha)
diff = _get_diff(repo, commit)
return Response(diff.patch, mimetype="text/x-diff")

Expand Down
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
10 changes: 8 additions & 2 deletions tests/test_restfulgit.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,13 +510,19 @@ def test_get_repo_commit_with_nonexistent_sha(self):
self.assertJson404(resp)

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)

def test_get_repos_tag_works(self):
Expand Down

0 comments on commit 765f8ac

Please sign in to comment.