Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sort out Repo API for individual commits & their diffs #63

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 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,15 @@ 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 +249,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 +685,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):
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):
def get_repos_diff(repo_key, branch_or_tag_or_sha):
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)
+
[email protected]('/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))
+
+
[email protected]('/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))
+
[email protected]('/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))
+
+
+
[email protected]('/repos/<repo_key>/git/refs')
[email protected]('/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