diff --git a/restfulgit/__init__.py b/restfulgit/__init__.py index c607869..b6edf6b 100644 --- a/restfulgit/__init__.py +++ b/restfulgit/__init__.py @@ -16,6 +16,7 @@ import json import os import functools +import re # Optionally use better libmagic-based MIME-type guessing try: @@ -142,6 +143,23 @@ def _get_commit_for_refspec(repo, branch_or_tag_or_sha): raise NotFound("no such branch, tag, or commit SHA") +def _get_patches(repo, commit): + if commit.parents: + diff = repo.diff(commit.parents[0], commit) + diff.find_similar() + else: + diff = commit.tree.diff_to_tree(swap=True) + return diff, [patch for patch in diff] + + +SPLIT_PATCH_TXT_RE = re.compile(r'^\+\+\+\ b\/(.*?)\n(@@.*?)(?=\n^diff|\n\Z)', re.M | re.S) + + +def _get_patches_txt(diff): + matches = re.findall(SPLIT_PATCH_TXT_RE, diff.patch) + return dict(m for m in matches) + + DEFAULT_GIT_DESCRIPTION = "Unnamed repository; edit this file 'description' to name the repository.\n" @@ -203,6 +221,57 @@ def _convert_commit(repo_key, commit): } +GIT_STATUS_TO_NAME = { + 'M': 'modified', + 'A': 'added', + 'R': 'renamed', + 'D': 'removed', +} + + +def _convert_patch(repo_key, commit, patch, patches_txt): + deleted = patch.status == 'D' + result = { + "sha": patch.new_oid if not deleted else patch.old_oid, + "status": GIT_STATUS_TO_NAME[patch.status], + "filename": patch.new_file_path, + "additions": patch.additions, + "deletions": patch.deletions, + "changes": patch.additions + patch.deletions, + "raw_url": url_for('.get_raw', + _external=True, + repo_key=repo_key, + branch_or_tag_or_sha=commit.hex if not deleted else commit.parents[0].hex, + file_path=patch.new_file_path), + } + if patch.new_file_path in patches_txt: + result['patch'] = patches_txt[patch.new_file_path] + return result + + +def _repos_convert_commit(repo_key, repo, commit): + diff, patches = _get_patches(repo, commit) + patches_txt = _get_patches_txt(diff) + patches_additions = sum(patch.additions for patch in patches) + patches_deletions = sum(patch.deletions for patch in patches) + return { + "commit" : _convert_commit(repo_key, commit), + "url": url_for('.get_repos_commit', _external=True, + repo_key=repo_key, sha=commit.hex), + "parents": [{ + "sha": c.hex, + "url": url_for('.get_repos_commit', _external=True, + repo_key=repo_key, sha=c.hex) + } for c in commit.parents], + "stats": { + "additions": patches_additions, + "deletions": patches_deletions, + "total": patches_additions + patches_deletions, + }, + "files": [_convert_patch(repo_key, commit, patch, patches_txt) for patch in patches], + } + + def _tree_entries(repo_key, repo, tree, recursive=False, path=''): entry_list = [] for entry in tree: @@ -459,6 +528,15 @@ def get_commit(repo_key, sha): return _convert_commit(repo_key, commit) +@restfulgit.route('/repos//commits//') +@corsify +@jsonify +def get_repos_commit(repo_key, sha): + repo = _get_repo(repo_key) + commit = _get_commit(repo, sha) + return _repos_convert_commit(repo_key, repo, commit) + + @restfulgit.route('/repos//git/trees//') @corsify @jsonify