From 17e9e23015c7918df858ff591333d38443c62c5e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Oct 12 2017 08:52:54 +0000 Subject: Include the branches' head in the commit list This gives quickly a visual overview of where branches are at Fixes https://pagure.io/pagure/issue/2490 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/internal/__init__.py b/pagure/internal/__init__.py index d1180fc..c39dbb0 100644 --- a/pagure/internal/__init__.py +++ b/pagure/internal/__init__.py @@ -10,6 +10,7 @@ Internal endpoints. """ +import collections import os import flask @@ -493,3 +494,80 @@ def get_branches_of_commit(): 'branches': branches, } ) + + +@PV.route('/branches/heads/', methods=['POST']) +@localonly +def get_branches_head(): + """ Return the heads of each branch in the repo, using the following + structure: + { + code: 'OK', + branches: { + name : commit, + ... + }, + heads: { + commit : [branch, ...], + ... + } + } + """ + form = pagure.forms.ConfirmationForm() + if not form.validate_on_submit(): + response = flask.jsonify({ + 'code': 'ERROR', + 'message': 'Invalid input submitted', + }) + response.status_code = 400 + return response + + repo = pagure.get_authorized_project( + pagure.SESSION, + flask.request.form.get('repo', '').strip() or None, + namespace=flask.request.form.get('namespace', '').strip() or None, + user=flask.request.form.get('repouser', '').strip() or None) + + if not repo: + response = flask.jsonify({ + 'code': 'ERROR', + 'message': 'No repo found with the information provided', + }) + response.status_code = 404 + return response + + repopath = os.path.join(pagure.APP.config['GIT_FOLDER'], repo.path) + + if not os.path.exists(repopath): + response = flask.jsonify({ + 'code': 'ERROR', + 'message': 'No git repo found with the information provided', + }) + response.status_code = 404 + return response + + repo_obj = pygit2.Repository(repopath) + if repo.is_fork: + parentreponame = pagure.get_repo_path(repo.parent) + parent_repo_obj = pygit2.Repository(parentreponame) + else: + parent_repo_obj = repo_obj + + branches = {} + if not repo_obj.is_empty and repo_obj.listall_branches() > 1: + for branchname in repo_obj.listall_branches(): + branch = repo_obj.lookup_branch(branchname) + branches[branchname] = branch.get_object().hex + + # invert the dict + heads = collections.defaultdict(list) + for branch, commit in branches.items(): + heads[commit].append(branch) + + return flask.jsonify( + { + 'code': 'OK', + 'branches': branches, + 'heads': heads, + } + ) diff --git a/pagure/templates/commit.html b/pagure/templates/commit.html index b52b680..0534892 100644 --- a/pagure/templates/commit.html +++ b/pagure/templates/commit.html @@ -197,7 +197,7 @@ data: { repo: "{{ repo.name }}", repouser: "{{ repo.user.user if repo.is_fork else '' }}", - namespace: "{{ repo.namespace }}", + namespace: "{{ repo.namespace if repo.namespace else '' }}", commit_id: "{{ commitid }}", csrf_token: "{{ form.csrf_token.current_token }}", }, diff --git a/pagure/templates/commits.html b/pagure/templates/commits.html index 92a37dc..44779ef 100644 --- a/pagure/templates/commits.html +++ b/pagure/templates/commits.html @@ -156,8 +156,10 @@ namespace=repo.namespace, author=commit.author.email), cssclass="notblue")|safe}} -
- {{ commit.hex|short }} +
+ + {{ commit.hex|short }} +
{% endfor %} @@ -176,6 +178,32 @@ $('#diff_commits_link').click(function(){ $('#diff_commits').toggle(); }); + $.ajax({ + url: '{{ url_for("internal_ns.get_branches_head") }}' , + type: 'POST', + data: { + repo: "{{ repo.name }}", + repouser: "{{ repo.user.user if repo.is_fork else '' }}", + namespace: "{{ repo.namespace if repo.namespace else '' }}", + csrf_token: "{{ form.csrf_token.current_token }}", + }, + dataType: 'json', + success: function(res) { + for (var _c in res.heads) { + var _b = ''; + for (var i=0; i < res.heads[_c].length; i++){ + _b += res.heads[_c][i] + ', '; + } + _b = _b.slice(0, -2); + var html = '' + + ' ' + + _b + ''; + var el = $('#c_' + _c); + el.parent().before(html); + } + }, + }); }); {% endblock %} diff --git a/tests/test_pagure_flask_internal.py b/tests/test_pagure_flask_internal.py index 9b4eba9..fffb04e 100644 --- a/tests/test_pagure_flask_internal.py +++ b/tests/test_pagure_flask_internal.py @@ -41,6 +41,7 @@ class PagureFlaskInternaltests(tests.Modeltests): pagure.APP.config['IP_ALLOWED_INTERNAL'] + [None])) pagure.SESSION = self.session pagure.internal.SESSION = self.session + pagure.ui.app.SESSION = self.session pagure.ui.repo.SESSION = self.session pagure.ui.filters.SESSION = self.session @@ -1181,6 +1182,177 @@ class PagureFlaskInternaltests(tests.Modeltests): } ) + def test_get_branches_head(self): + ''' Test the get_branches_head from the internal API. ''' + tests.create_projects(self.session) + tests.create_projects_git(os.path.join(self.path, 'repos')) + + user = tests.FakeUser() + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + csrf_token = self.get_csrf() + + # No CSRF token + data = { + 'repo': 'fakerepo', + } + output = self.app.post('/pv/branches/heads/', data=data) + self.assertEqual(output.status_code, 400) + js_data = json.loads(output.data.decode('utf-8')) + self.assertDictEqual( + js_data, + {u'code': u'ERROR', u'message': u'Invalid input submitted'} + ) + + # Invalid repo + data = { + 'repo': 'fakerepo', + 'commit_id': 'foo', + 'csrf_token': csrf_token, + } + output = self.app.post('/pv/branches/heads/', data=data) + self.assertEqual(output.status_code, 404) + js_data = json.loads(output.data.decode('utf-8')) + self.assertDictEqual( + js_data, + { + u'code': u'ERROR', + u'message': u'No repo found with the information provided' + } + ) + + # Rigth repo, no commit + data = { + 'repo': 'test', + 'csrf_token': csrf_token, + } + + output = self.app.post('/pv/branches/heads/', data=data) + self.assertEqual(output.status_code, 200) + js_data = json.loads(output.data.decode('utf-8')) + self.assertDictEqual( + js_data, + {u"branches": {}, u"code": u"OK", u"heads": {}} + ) + + # Request is fine, but git repo doesn't exist + item = pagure.lib.model.Project( + user_id=1, # pingou + name='test20', + description='test project #20', + hook_token='aaabbbhhh', + ) + self.session.add(item) + self.session.commit() + + data = { + 'repo': 'test20', + 'csrf_token': csrf_token, + } + output = self.app.post('/pv/branches/heads/', data=data) + self.assertEqual(output.status_code, 404) + js_data = json.loads(output.data.decode('utf-8')) + self.assertDictEqual( + js_data, + { + u'code': u'ERROR', + u'message': u'No git repo found with the information provided' + } + ) + + # Create a git repo to play with + gitrepo = os.path.join(self.path, 'repos', 'test.git') + self.assertTrue(os.path.exists(gitrepo)) + repo = pygit2.Repository(gitrepo) + + # Create a file in that git repo + with open(os.path.join(gitrepo, 'sources'), 'w') as stream: + stream.write('foo\n bar') + repo.index.add('sources') + repo.index.write() + + # Commits the files added + tree = repo.index.write_tree() + author = pygit2.Signature( + 'Alice Author', 'alice@authors.tld') + committer = pygit2.Signature( + 'Cecil Committer', 'cecil@committers.tld') + repo.create_commit( + 'refs/heads/master', # the name of the reference to update + author, + committer, + 'Add sources file for testing', + # binary string representing the tree object ID + tree, + # list of binary strings representing parents of the new commit + [] + ) + + first_commit = repo.revparse_single('HEAD') + + # Edit the sources file again + with open(os.path.join(gitrepo, 'sources'), 'w') as stream: + stream.write('foo\n bar\nbaz\n boose') + repo.index.add('sources') + repo.index.write() + + # Commits the files added + tree = repo.index.write_tree() + author = pygit2.Signature( + 'Alice Author', 'alice@authors.tld') + committer = pygit2.Signature( + 'Cecil Committer', 'cecil@committers.tld') + repo.create_commit( + 'refs/heads/feature', # the name of the reference to update + author, + committer, + 'Add baz and boose to the sources\n\n There are more objects to ' + 'consider', + # binary string representing the tree object ID + tree, + # list of binary strings representing parents of the new commit + [first_commit.oid.hex] + ) + + # Create another file in the master branch + with open(os.path.join(gitrepo, '.gitignore'), 'w') as stream: + stream.write('*~') + repo.index.add('.gitignore') + repo.index.write() + + # Commits the files added + tree = repo.index.write_tree() + author = pygit2.Signature( + 'Alice Author', 'alice@authors.tld') + committer = pygit2.Signature( + 'Cecil Committer', 'cecil@committers.tld') + commit_hash = repo.create_commit( + 'refs/heads/feature_branch', # the name of the reference to update + author, + committer, + 'Add .gitignore file for testing', + # binary string representing the tree object ID + tree, + # list of binary strings representing parents of the new commit + [first_commit.oid.hex] + ) + + # All good + data = { + 'repo': 'test', + 'csrf_token': csrf_token, + } + output = self.app.post('/pv/branches/heads/', data=data) + self.assertEqual(output.status_code, 200) + js_data = json.loads(output.data.decode('utf-8')) + # We can't test the content since the commit hash will change all + # the time, so let's just check the structure + self.assertEqual( + sorted(js_data.keys()), ['branches', 'code', 'heads']) + self.assertEqual(js_data['code'], 'OK') + self.assertEqual(len(js_data['heads']), 3) + self.assertEqual(len(js_data['branches']), 3) + if __name__ == '__main__': unittest.main(verbosity=2)