diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py index ab00138cd..cdace31d3 100644 --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -1,412 +1,460 @@ # Copyright (C) 2017-2018 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import hashlib import json +from django.http import HttpResponse from django.shortcuts import render from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe from swh.web.common import service from swh.web.common.utils import reverse, format_utc_iso_date, gen_path_info from swh.web.common.exc import handle_view_exception from swh.web.browse.browseurls import browse_route from swh.web.browse.utils import ( gen_link, gen_person_link, gen_revision_link, prepare_revision_log_for_display, get_origin_context, gen_origin_directory_link, get_revision_log_url, get_directory_entries, gen_directory_link, request_content, prepare_content_for_display, content_display_max_size ) def _gen_content_url(revision, query_string, path, origin_context): if origin_context: url_args = origin_context['url_args'] url_args['path'] = path query_params = origin_context['query_params'] query_params['revision'] = revision['id'] content_url = reverse('browse-origin-content', kwargs=url_args, query_params=query_params) else: content_path = '%s/%s' % (revision['directory'], path) content_url = reverse('browse-content', kwargs={'query_string': query_string}, query_params={'path': content_path}) return content_url +def _gen_diff_link(idx, diff_anchor, link_text): + if idx < _max_displayed_file_diffs: + return gen_link(diff_anchor, link_text) + else: + return link_text + + +# TODO: put in conf +_max_displayed_file_diffs = 1000 + + def _gen_revision_changes_list(revision, changes, origin_context): """ Returns a HTML string describing the file changes introduced in a revision. As this string will be displayed in the browse revision view, links to adequate file diffs are also generated. Args: revision (str): hexadecimal representation of a revision identifier changes (list): list of file changes in the revision origin_context (dict): optional origin context used to reverse the content urls Returns: A string to insert in a revision HTML view. """ changes_msg = [] - for change in changes: + for i, change in enumerate(changes): hasher = hashlib.sha1() from_query_string = '' to_query_string = '' diff_id = 'diff-' if change['from']: from_query_string = 'sha1_git:' + change['from']['target'] diff_id += change['from']['target'] + '-' + change['from_path'] diff_id += '-' if change['to']: to_query_string = 'sha1_git:' + change['to']['target'] diff_id += change['to']['target'] + change['to_path'] change['path'] = change['to_path'] or change['from_path'] url_args = {'from_query_string': from_query_string, 'to_query_string': to_query_string} query_params = {'path': change['path']} change['diff_url'] = reverse('diff-contents', kwargs=url_args, query_params=query_params) hasher.update(diff_id.encode('utf-8')) diff_id = hasher.hexdigest() change['id'] = diff_id panel_diff_link = '#panel_' + diff_id if change['type'] == 'modify': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], origin_context) - changes_msg.append('modified: %s' % gen_link(panel_diff_link, - change['to_path'])) + changes_msg.append('modified: %s' % + _gen_diff_link(i, panel_diff_link, + change['to_path'])) elif change['type'] == 'insert': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], origin_context) - changes_msg.append('new file: %s' % gen_link(panel_diff_link, - change['to_path'])) + changes_msg.append('new file: %s' % + _gen_diff_link(i, panel_diff_link, + change['to_path'])) elif change['type'] == 'delete': parent = service.lookup_revision(revision['parents'][0]) change['content_url'] = \ _gen_content_url(parent, from_query_string, change['from_path'], origin_context) - changes_msg.append('deleted: %s' % gen_link(panel_diff_link, - change['from_path'])) + changes_msg.append('deleted: %s' % + _gen_diff_link(i, panel_diff_link, + change['from_path'])) elif change['type'] == 'rename': change['content_url'] = \ _gen_content_url(revision, to_query_string, change['to_path'], origin_context) link_text = change['from_path'] + ' → ' + change['to_path'] - changes_msg.append('renamed: %s' % gen_link(panel_diff_link, - link_text)) + changes_msg.append('renamed: %s' % + _gen_diff_link(i, panel_diff_link, link_text)) if not changes: changes_msg.append('No changes') return mark_safe('\n'.join(changes_msg)) +@browse_route(r'revision/(?P[0-9a-f]+)/diff/', + view_name='diff-revision') +def _revision_diff(request, sha1_git): + """ + Browse internal endpoint to compute revision diff + """ + try: + revision = service.lookup_revision(sha1_git) + origin_context = None + origin_type = request.GET.get('origin_type', None) + origin_url = request.GET.get('origin_url', None) + timestamp = request.GET.get('timestamp', None) + visit_id = request.GET.get('visit_id', None) + if origin_type and origin_url: + origin_context = get_origin_context(origin_type, origin_url, + timestamp, visit_id) + except Exception as exc: + return handle_view_exception(request, exc) + + changes = service.diff_revision(sha1_git) + changes_msg = _gen_revision_changes_list(revision, changes, origin_context) + + diff_data = { + 'total_nb_changes': len(changes), + 'changes': changes[:_max_displayed_file_diffs], + 'changes_msg': changes_msg + } + diff_data_json = json.dumps(diff_data, separators=(',', ': ')) + return HttpResponse(diff_data_json, content_type='application/json') + + @browse_route(r'revision/(?P[0-9a-f]+)/', view_name='browse-revision') def revision_browse(request, sha1_git): """ Django view that produces an HTML display of a SWH revision identified by its id. The url that points to it is :http:get:`/browse/revision/(sha1_git)/`. Args: request: input django http request sha1_git: a SWH revision id Returns: The HMTL rendering for the metadata of the provided revision. """ try: revision = service.lookup_revision(sha1_git) origin_info = None origin_context = None origin_type = request.GET.get('origin_type', None) origin_url = request.GET.get('origin_url', None) timestamp = request.GET.get('timestamp', None) visit_id = request.GET.get('visit_id', None) path = request.GET.get('path', None) dir_id = None dirs, files = None, None content_data = None if origin_type and origin_url: origin_context = get_origin_context(origin_type, origin_url, timestamp, visit_id) origin_info = origin_context['origin_info'] if path: path_info = \ service.lookup_directory_with_path(revision['directory'], path) if path_info['type'] == 'dir': dir_id = path_info['target'] else: query_string = 'sha1_git:' + path_info['target'] content_data = request_content(query_string) else: dir_id = revision['directory'] if dir_id: path = '' if path is None else (path + '/') dirs, files = get_directory_entries(dir_id) except Exception as exc: return handle_view_exception(request, exc) revision_data = {} revision_data['author'] = gen_person_link( revision['author']['id'], revision['author']['name']) revision_data['committer'] = gen_person_link( revision['committer']['id'], revision['committer']['name']) revision_data['committer date'] = format_utc_iso_date( revision['committer_date']) revision_data['date'] = format_utc_iso_date(revision['date']) if origin_context: revision_data['directory'] = \ gen_origin_directory_link(origin_context, sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-md btn-swh', # noqa 'role': 'button'}) else: revision_data['directory'] = \ gen_directory_link(revision['directory'], link_text='Browse', link_attrs={'class': 'btn btn-md btn-swh', 'role': 'button'}) revision_data['id'] = sha1_git revision_data['merge'] = revision['merge'] revision_data['metadata'] = json.dumps(revision['metadata'], sort_keys=True, indent=4, separators=(',', ': ')) if origin_info: revision_data['context-independent revision'] = \ gen_revision_link(sha1_git, link_text='Browse', link_attrs={'class': 'btn btn-md btn-swh', 'role': 'button'}) revision_data['origin id'] = origin_info['id'] revision_data['origin type'] = origin_info['type'] revision_data['origin url'] = gen_link(origin_info['url'], origin_info['url']) parents = '' for p in revision['parents']: parent_link = gen_revision_link(p, origin_context=origin_context) parents += parent_link + '
' revision_data['parents'] = mark_safe(parents) revision_data['synthetic'] = revision['synthetic'] revision_data['type'] = revision['type'] message_lines = revision['message'].split('\n') parents_links = '%s parent%s ' % \ (len(revision['parents']), '' if len(revision['parents']) == 1 else 's') parents_links += ' ' for p in revision['parents']: parent_link = gen_revision_link(p, shorten_id=True, origin_context=origin_context) parents_links += parent_link if p != revision['parents'][-1]: parents_links += ' + ' path_info = gen_path_info(path) query_params = {'origin_type': origin_type, 'origin_url': origin_url, 'timestamp': timestamp, 'visit_id': visit_id} breadcrumbs = [] breadcrumbs.append({'name': revision['directory'][:7], 'url': reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params)}) for pi in path_info: query_params['path'] = pi['path'] breadcrumbs.append({'name': pi['name'], 'url': reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params)}) vault_cooking = { 'directory_context': False, 'directory_id': None, 'revision_context': True, 'revision_id': sha1_git } content = None content_size = None mimetype = None language = None readme_name = None readme_url = None if content_data: breadcrumbs[-1]['url'] = None content_size = content_data['length'] mimetype = content_data['mimetype'] if content_data['raw_data']: content_display_data = prepare_content_for_display( content_data['raw_data'], content_data['mimetype'], path) content = content_display_data['content_data'] language = content_display_data['language'] query_params = {} if path: query_params['filename'] = path_info[-1]['name'] top_right_link = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params=query_params) top_right_link_text = mark_safe( 'Raw File') else: for d in dirs: query_params['path'] = path + d['name'] d['url'] = reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params) for f in files: query_params['path'] = path + f['name'] f['url'] = reverse('browse-revision', kwargs={'sha1_git': sha1_git}, query_params=query_params) f['length'] = filesizeformat(f['length']) if f['name'].lower().startswith('readme'): readme_name = f['name'] readme_sha1 = f['checksums']['sha1'] readme_url = reverse('browse-content-raw', kwargs={'query_string': readme_sha1}) top_right_link = get_revision_log_url(sha1_git, origin_context) top_right_link_text = mark_safe( '' 'History') vault_cooking['directory_context'] = True vault_cooking['directory_id'] = dir_id - changes = service.diff_revision(sha1_git) - changes_msg = _gen_revision_changes_list(revision, changes, origin_context) + diff_revision_url = reverse('diff-revision', kwargs={'sha1_git': sha1_git}, + query_params={'origin_type': origin_type, + 'origin_url': origin_url, + 'timestamp': timestamp, + 'visit_id': visit_id}) return render(request, 'revision.html', {'empty_browse': False, 'heading': 'Revision information', 'top_panel_visible': True, 'top_panel_collapsible': True, 'top_panel_text': 'SWH object: Revision', 'swh_object_metadata': revision_data, 'message_header': message_lines[0], 'message_body': '\n'.join(message_lines[1:]), 'parents_links': mark_safe(parents_links), 'main_panel_visible': True, 'origin_context': origin_context, 'dirs': dirs, 'files': files, 'content': content, 'content_size': content_size, 'max_content_size': content_display_max_size, 'mimetype': mimetype, 'language': language, 'readme_name': readme_name, 'readme_url': readme_url, 'breadcrumbs': breadcrumbs, 'top_right_link': top_right_link, 'top_right_link_text': top_right_link_text, 'vault_cooking': vault_cooking, - 'changes': changes, - 'changes_msg': changes_msg}) + 'diff_revision_url': diff_revision_url}) NB_LOG_ENTRIES = 20 @browse_route(r'revision/(?P[0-9a-f]+)/log/', view_name='browse-revision-log') def revision_log_browse(request, sha1_git): """ Django view that produces an HTML display of the history log for a SWH revision identified by its id. The url that points to it is :http:get:`/browse/revision/(sha1_git)/log/`. Args: request: input django http request sha1_git: a SWH revision id Returns: The HMTL rendering of the revision history log. """ # noqa try: per_page = int(request.GET.get('per_page', NB_LOG_ENTRIES)) revision_log = service.lookup_revision_log(sha1_git, limit=per_page+1) revision_log = list(revision_log) except Exception as exc: return handle_view_exception(request, exc) revs_breadcrumb = request.GET.get('revs_breadcrumb', None) revision_log_display_data = prepare_revision_log_for_display( revision_log, per_page, revs_breadcrumb) prev_rev = revision_log_display_data['prev_rev'] prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb'] prev_log_url = None if prev_rev: prev_log_url = \ reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': prev_revs_breadcrumb, 'per_page': per_page}) next_rev = revision_log_display_data['next_rev'] next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb'] next_log_url = None if next_rev: next_log_url = \ reverse('browse-revision-log', kwargs={'sha1_git': next_rev}, query_params={'revs_breadcrumb': next_revs_breadcrumb, 'per_page': per_page}) revision_log_data = revision_log_display_data['revision_log_data'] for log in revision_log_data: log['directory'] = gen_directory_link( log['directory'], link_text='Browse files', link_attrs={'class': 'btn btn-md btn-swh', 'role': 'button'}) return render(request, 'revision-log.html', {'empty_browse': False, 'heading': 'Revision history information', 'top_panel_visible': False, 'top_panel_collapsible': False, 'top_panel_text': 'SWH object: Revision history', 'swh_object_metadata': None, 'main_panel_visible': True, 'revision_log': revision_log_data, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, 'top_right_link': None, 'top_right_link_text': None, 'origin_context': None, 'vault_cooking': None}) diff --git a/swh/web/templates/revision.html b/swh/web/templates/revision.html index 61afeaae1..260779cd7 100644 --- a/swh/web/templates/revision.html +++ b/swh/web/templates/revision.html @@ -1,559 +1,593 @@ {% extends "browse.html" %} {% load static %} {% block header %} {% include "includes/content-scripts.html" %} {% endblock %} {% block swh-browse-before-panels %} {% if origin_context %} {% include "includes/origin-visit-snapshot.html" %} {% endif %} {% endblock %} {% block swh-browse-main-panel-content %}
Revision {{ swh_object_metadata.id }} authored by {{ swh_object_metadata.author }} on {{ swh_object_metadata.date }}
{{ message_body }}
{{ parents_links }}
{% include "includes/top-navigation.html" %} {% if content_size %} {% include "includes/content-display.html" %} {% else %} {% include "includes/directory-display.html" %} {% endif %}
-
+ + +
- - {% for change in changes %} -
- - -
- - - - {% endfor %} -
{% endblock %} {% block swh-browse-after-panels %} {% include "includes/readme-display.html" %} {% endblock %}