diff --git a/debian/control b/debian/control --- a/debian/control +++ b/debian/control @@ -26,7 +26,7 @@ python3-yaml, python3-swh.core (>= 0.0.40~), python3-swh.model (>= 0.0.25~), - python3-swh.storage (>= 0.0.107~), + python3-swh.storage (>= 0.0.109~), python3-swh.indexer.storage (>= 0.0.52~), python3-swh.vault (>= 0.0.20~), python3-swh.scheduler (>= 0.0.31~) @@ -37,7 +37,7 @@ Architecture: all Depends: python3-swh.core (>= 0.0.40~), python3-swh.model (>= 0.0.25~), - python3-swh.storage (>= 0.0.107~), + python3-swh.storage (>= 0.0.109~), python3-swh.indexer.storage (>= 0.0.52~), python3-swh.vault (>= 0.0.20~), python3-swh.scheduler (>= 0.0.31~), diff --git a/docs/uri-scheme-browse-origin.rst b/docs/uri-scheme-browse-origin.rst --- a/docs/uri-scheme-browse-origin.rst +++ b/docs/uri-scheme-browse-origin.rst @@ -257,13 +257,16 @@ The following data are displayed for each log entry: + * link to browse the associated revision in the origin context * author of the revision - * link to the revision metadata - * message associated the revision * date of the revision - * link to browse the associated source tree in the origin context + * message associated the revision + * commit date of the revision - N log entries are displayed per page (default is 20). In order to navigate + By default, the revisions are ordered in reverse chronological order of + their commit date. + + N log entries are displayed per page (default is 100). In order to navigate in a large history, two buttons are present at the bottom of the view: * *Newer*: fetch and display if available the N more recent log entries @@ -279,13 +282,10 @@ :param string origin_type: the type of the SWH origin (*git*, *svn*, *deb* ...) :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) - :query string revs_breadcrumb: used internally to store - the navigation breadcrumbs (i.e. the list of descendant revisions - visited so far). It must be a string in the form - "(rev_1)[/(rev_2)/.../(rev_n)]" where rev_i corresponds to a - revision *sha1_git*. :query int per_page: the number of log entries to display per page - (default is 20, max is 50) + :query int offset: the number of revisions to skip before returning those to display + :query str revs_ordering: specify the revisions ordering, possible values are *committer_date*, + *dfs*, *dfs_post* and *bfs* :query string branch: specify the origin branch name from which to retrieve the commit log :query string release: specify the origin release name from which diff --git a/docs/uri-scheme-browse-revision.rst b/docs/uri-scheme-browse-revision.rst --- a/docs/uri-scheme-browse-revision.rst +++ b/docs/uri-scheme-browse-revision.rst @@ -47,13 +47,16 @@ a given one. In other words, it shows a commit log. The following data are displayed for each log entry: - * author of the revision * link to browse the revision - * message associated to the revision + * author of the revision * date of the revision - * link to browse the associated source tree + * message associated to the revision + * commit date of the revision + + By default, the revisions are ordered in reverse chronological order of + their commit date. - N log entries are displayed per page (default is 20). In order to navigate + N log entries are displayed per page (default is 100). In order to navigate in a large history, two buttons are present at the bottom of the view: * *Newer*: fetch and display if available the N more recent log entries @@ -63,13 +66,10 @@ :param string sha1_git: hexadecimal representation for the *sha1_git* identifier of a SWH revision - :query string revs_breadcrumb: used internally to store - the navigation breadcrumbs (i.e. the list of descendant revisions - visited so far). It must be a string in the form - "[//.../]" where rev_i corresponds to a - revision sha1_git. :query int per_page: the number of log entries to display per page - (default is 20, max is 50) + :query int offset: the number of revisions to skip before returning those to display + :query str revs_ordering: specify the revisions ordering, possible values are *committer_date*, + *dfs*, *dfs_post* and *bfs* :statuscode 200: no error :statuscode 404: requested revision can not be found in the SWH archive diff --git a/requirements-swh.txt b/requirements-swh.txt --- a/requirements-swh.txt +++ b/requirements-swh.txt @@ -1,6 +1,6 @@ swh.core >= 0.0.40 swh.model >= 0.0.25 -swh.storage >= 0.0.107 +swh.storage >= 0.0.109 swh.vault >= 0.0.20 swh.indexer >= 0.0.52 swh.scheduler >= 0.0.31 diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -18,9 +18,8 @@ pyyaml requests -#Doc dependencies +# Doc dependencies sphinx sphinxcontrib-httpdomain -# Test dependencies diff --git a/swh/web/assets/src/bundles/browse/browse.css b/swh/web/assets/src/bundles/browse/browse.css --- a/swh/web/assets/src/bundles/browse/browse.css +++ b/swh/web/assets/src/bundles/browse/browse.css @@ -73,9 +73,9 @@ } .swh-log-entry-message { - min-width: 460px; - max-width: 460px; - width: 460px; + min-width: 440px; + max-width: 440px; + width: 440px; } .swh-popover { diff --git a/swh/web/assets/src/bundles/revision/index.js b/swh/web/assets/src/bundles/revision/index.js --- a/swh/web/assets/src/bundles/revision/index.js +++ b/swh/web/assets/src/bundles/revision/index.js @@ -9,3 +9,4 @@ import './revision.css'; export * from './diff-utils'; +export * from './log-utils'; diff --git a/swh/web/assets/src/bundles/revision/log-utils.js b/swh/web/assets/src/bundles/revision/log-utils.js new file mode 100644 --- /dev/null +++ b/swh/web/assets/src/bundles/revision/log-utils.js @@ -0,0 +1,26 @@ +export function revsOrderingTypeClicked(event) { + let urlParams = new URLSearchParams(window.location.search); + let orderingType = $(event.target).val(); + if (orderingType) { + urlParams.set('revs_ordering', $(event.target).val()); + } else if (urlParams.has('revs_ordering')) { + urlParams.delete('revs_ordering'); + } + window.location.search = urlParams.toString(); +} + +export function initRevisionsLog() { + $(document).ready(() => { + let urlParams = new URLSearchParams(window.location.search); + let revsOrderingType = urlParams.get('revs_ordering'); + if (revsOrderingType) { + $(`:input[value="${revsOrderingType}"]`).prop('checked', true); + } + + $('tr.swh-revision-log-entry').on('click', function() { + window.location = $(this).data('href'); + return false; + }); + $('td > a').on('click', e => e.stopPropagation()); + }); +} diff --git a/swh/web/assets/src/bundles/revision/revision.css b/swh/web/assets/src/bundles/revision/revision.css --- a/swh/web/assets/src/bundles/revision/revision.css +++ b/swh/web/assets/src/bundles/revision/revision.css @@ -31,6 +31,18 @@ fill: currentColor; } +tr.swh-revision-log-entry { + cursor: pointer; +} + +tr.swh-revision-log-entry:hover td { + background: #fecd1b; +} + +tr.swh-revision-log-entry td a { + text-decoration: none; +} + .swh-revision-log-entry-id { min-width: 110px; max-width: 110px; @@ -44,7 +56,13 @@ } .swh-revision-log-entry-date { - min-width: 230px; - max-width: 230px; - width: 230px; + min-width: 200px; + max-width: 200px; + width: 200px; +} + +.swh-revision-log-entry-commit-date { + min-width: 200px; + max-width: 200px; + width: 200px; } diff --git a/swh/web/browse/utils.py b/swh/web/browse/utils.py --- a/swh/web/browse/utils.py +++ b/swh/web/browse/utils.py @@ -9,6 +9,7 @@ import math import pypandoc import stat +import textwrap from django.core.cache import cache from django.utils.safestring import mark_safe @@ -730,8 +731,7 @@ query_params = {'revision': revision_id} if snapshot_context['origin_info']: origin_info = snapshot_context['origin_info'] - url_args = {'origin_type': origin_info['type'], - 'origin_url': origin_info['url']} + url_args = {'origin_url': origin_info['url']} if 'timestamp' in snapshot_context['url_args']: url_args['timestamp'] = \ snapshot_context['url_args']['timestamp'] @@ -792,8 +792,7 @@ query_params = {'revision': revision_id} if snapshot_context and snapshot_context['origin_info']: origin_info = snapshot_context['origin_info'] - url_args = {'origin_type': origin_info['type'], - 'origin_url': origin_info['url']} + url_args = {'origin_url': origin_info['url']} if 'timestamp' in snapshot_context['url_args']: url_args['timestamp'] = \ snapshot_context['url_args']['timestamp'] @@ -842,28 +841,7 @@ return gen_link(revision_log_url, link_text, link_attrs) -def _format_log_entries(revision_log, per_page, snapshot_context=None): - revision_log_data = [] - for i, log in enumerate(revision_log): - if i == per_page: - break - author_name = 'None' - author_link = 'None' - if log['author']: - author_name = log['author']['name'] or log['author']['fullname'] - author_link = gen_person_link(log['author']['id'], author_name, - snapshot_context) - revision_log_data.append( - {'author': author_link, - 'revision': gen_revision_link(log['id'], True, snapshot_context), - 'message': log['message'], - 'date': format_utc_iso_date(log['date']), - 'directory': log['directory']}) - return revision_log_data - - -def prepare_revision_log_for_display(revision_log, per_page, revs_breadcrumb, - snapshot_context=None): +def format_log_entries(revision_log, per_page, snapshot_context=None): """ Utility functions that process raw revision log data for HTML display. Its purpose is to: @@ -872,48 +850,46 @@ * format date in human readable format * truncate the message log - It also computes the data needed to generate the links for navigating back - and forth in the history log. - Args: revision_log (list): raw revision log as returned by the SWH web api per_page (int): number of log entries per page - revs_breadcrumb (str): breadcrumbs of revisions navigated so far, - in the form 'rev1[/rev2/../revN]'. Each revision corresponds to - the first one displayed in the HTML view for history log. snapshot_context (dict): if provided, generate snapshot-dependent browsing link """ - current_rev = revision_log[0]['id'] - next_rev = None - prev_rev = None - next_revs_breadcrumb = None - prev_revs_breadcrumb = None - if len(revision_log) == per_page + 1: - prev_rev = revision_log[-1]['id'] - - prev_rev_bc = current_rev - if snapshot_context: - prev_rev_bc = prev_rev - - if revs_breadcrumb: - revs = revs_breadcrumb.split('/') - next_rev = revs[-1] - if len(revs) > 1: - next_revs_breadcrumb = '/'.join(revs[:-1]) - if len(revision_log) == per_page + 1: - prev_revs_breadcrumb = revs_breadcrumb + '/' + prev_rev_bc - else: - prev_revs_breadcrumb = prev_rev_bc - - return {'revision_log_data': _format_log_entries(revision_log, per_page, - snapshot_context), - 'prev_rev': prev_rev, - 'prev_revs_breadcrumb': prev_revs_breadcrumb, - 'next_rev': next_rev, - 'next_revs_breadcrumb': next_revs_breadcrumb} + revision_log_data = [] + for i, rev in enumerate(revision_log): + if i == per_page: + break + author_name = 'None' + author_fullname = 'None' + committer_fullname = 'None' + if rev['author']: + author_name = rev['author']['name'] or rev['author']['fullname'] + author_fullname = rev['author']['fullname'] + if rev['committer']: + committer_fullname = rev['committer']['fullname'] + author_date = format_utc_iso_date(rev['date']) + committer_date = format_utc_iso_date(rev['committer_date']) + + tooltip = 'revision %s\n' % rev['id'] + tooltip += 'author: %s\n' % author_fullname + tooltip += 'author date: %s\n' % author_date + tooltip += 'committer: %s\n' % committer_fullname + tooltip += 'committer date: %s\n\n' % committer_date + tooltip += textwrap.indent(rev['message'], ' '*4) + + revision_log_data.append({ + 'author': author_name, + 'id': rev['id'][:7], + 'message': rev['message'], + 'date': author_date, + 'commit_date': committer_date, + 'url': gen_revision_url(rev['id'], snapshot_context), + 'tooltip': tooltip + }) + return revision_log_data # list of origin types that can be found in the swh archive diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -21,12 +21,11 @@ from swh.web.browse.browseurls import browse_route from swh.web.browse.utils import ( gen_link, gen_person_link, gen_revision_link, gen_revision_url, - prepare_revision_log_for_display, get_snapshot_context, gen_snapshot_directory_link, get_revision_log_url, get_directory_entries, gen_directory_link, request_content, prepare_content_for_display, content_display_max_size, gen_snapshot_link, get_readme_to_display, - get_swh_persistent_ids + get_swh_persistent_ids, format_log_entries ) @@ -171,7 +170,7 @@ return HttpResponse(diff_data_json, content_type='application/json') -NB_LOG_ENTRIES = 20 +NB_LOG_ENTRIES = 100 @browse_route(r'revision/(?P[0-9a-f]+)/log/', @@ -181,50 +180,57 @@ 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/`. + The url that points to it is :http:get:`/browse/revision/(sha1_git)/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) + offset = int(request.GET.get('offset', 0)) + revs_ordering = request.GET.get('revs_ordering', 'committer_date') + session_key = 'rev_%s_log_ordering_%s' % (sha1_git, revs_ordering) + rev_log_session = request.session.get(session_key, None) + rev_log = [] + revs_walker_state = None + if rev_log_session: + rev_log = rev_log_session['rev_log'] + revs_walker_state = rev_log_session['revs_walker_state'] + + if len(rev_log) < offset+per_page: + revs_walker = \ + service.get_revisions_walker(revs_ordering, sha1_git, + max_revs=offset+per_page+1, + state=revs_walker_state) + + rev_log += list(revs_walker) + revs_walker_state = revs_walker.export_state() + + revision_log = rev_log[offset:offset+per_page] + + request.session[session_key] = { + 'rev_log': rev_log, + 'revs_walker_state': revs_walker_state + } 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) + revs_ordering = request.GET.get('revs_ordering', '') - 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', - url_args={'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'] + if len(rev_log) > offset + per_page: + prev_log_url = reverse('browse-revision-log', + url_args={'sha1_git': sha1_git}, + query_params={'per_page': per_page, + 'offset': offset + per_page, + 'revs_ordering': revs_ordering}) + next_log_url = None - if next_rev: - next_log_url = \ - reverse('browse-revision-log', - url_args={'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-default btn-sm', - 'role': 'button'}) + if offset != 0: + next_log_url = reverse('browse-revision-log', + url_args={'sha1_git': sha1_git}, + query_params={'per_page': per_page, + 'offset': offset - per_page, + 'revs_ordering': revs_ordering}) + + revision_log_data = format_log_entries(revision_log, per_page) swh_rev_id = persistent_identifier('revision', sha1_git) @@ -234,6 +240,7 @@ 'swh_object_name': 'Revisions history', 'swh_object_metadata': None, 'revision_log': revision_log_data, + 'revs_ordering': revs_ordering, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, @@ -335,8 +342,8 @@ revision_data['committer'] = \ gen_person_link(revision['committer']['id'], revision['committer']['name'], snapshot_context) - revision_data['committer date'] = format_utc_iso_date( - revision['committer_date']) + revision_data['committer date'] = \ + format_utc_iso_date(revision['committer_date']) revision_data['date'] = format_utc_iso_date(revision['date']) if snapshot_context: revision_data['snapshot id'] = snapshot_id diff --git a/swh/web/browse/views/utils/snapshot_context.py b/swh/web/browse/views/utils/snapshot_context.py --- a/swh/web/browse/views/utils/snapshot_context.py +++ b/swh/web/browse/views/utils/snapshot_context.py @@ -17,9 +17,8 @@ get_snapshot_context, get_directory_entries, gen_directory_link, gen_revision_link, request_content, gen_content_link, prepare_content_for_display, content_display_max_size, - prepare_revision_log_for_display, gen_snapshot_directory_link, - gen_revision_log_link, gen_link, get_readme_to_display, - get_swh_persistent_ids, process_snapshot_branches + format_log_entries, gen_revision_log_link, gen_link, + get_readme_to_display, get_swh_persistent_ids, process_snapshot_branches ) from swh.web.common import service @@ -626,16 +625,33 @@ timestamp, browse_context='log') # noqa revision_id = snapshot_context['revision_id'] - current_rev = revision_id - per_page = int(request.GET.get('per_page', PER_PAGE)) - revs_breadcrumb = request.GET.get('revs_breadcrumb', None) - if revs_breadcrumb: - current_rev = revs_breadcrumb.split('/')[-1] - revision_log = [] - if current_rev: - revision_log = list(service.lookup_revision_log(current_rev, - limit=per_page+1)) + per_page = int(request.GET.get('per_page', PER_PAGE)) + offset = int(request.GET.get('offset', 0)) + revs_ordering = request.GET.get('revs_ordering', 'committer_date') + session_key = 'rev_%s_log_ordering_%s' % (revision_id, revs_ordering) + rev_log_session = request.session.get(session_key, None) + rev_log = [] + revs_walker_state = None + if rev_log_session: + rev_log = rev_log_session['rev_log'] + revs_walker_state = rev_log_session['revs_walker_state'] + + if len(rev_log) < offset+per_page: + revs_walker = \ + service.get_revisions_walker(revs_ordering, + revision_id, + max_revs=offset+per_page+1, + state=revs_walker_state) + rev_log += list(revs_walker) + revs_walker_state = revs_walker.export_state() + + revision_log = rev_log[offset:offset+per_page] + + request.session[session_key] = { + 'rev_log': rev_log, + 'revs_walker_state': revs_walker_state + } except Exception as exc: return handle_view_exception(request, exc) @@ -648,50 +664,27 @@ snapshot_id = snapshot_context['snapshot_id'] query_params['per_page'] = per_page + revs_ordering = request.GET.get('revs_ordering', '') + query_params['revs_ordering'] = revs_ordering - revision_log_data = [] - next_log_url = '' - prev_log_url = '' - if revision_log: - revision_log_display_data = prepare_revision_log_for_display( - revision_log, per_page, revs_breadcrumb, snapshot_context) - - browse_view_name = 'browse-' + swh_type + '-log' - - prev_rev = revision_log_display_data['prev_rev'] - prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb'] # noqa - prev_log_url = None - query_params['revs_breadcrumb'] = prev_revs_breadcrumb - if prev_rev: - prev_log_url = \ - reverse(browse_view_name, - url_args=url_args, - query_params=query_params) - - next_rev = revision_log_display_data['next_rev'] - next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb'] # noqa - next_log_url = None - query_params['revs_breadcrumb'] = next_revs_breadcrumb - if next_rev: - next_log_url = \ - reverse(browse_view_name, - url_args=url_args, - query_params=query_params) - - revision_log_data = revision_log_display_data['revision_log_data'] - - for i, log in enumerate(revision_log_data): - params = { - 'revision': revision_log[i]['id'], - } - if 'visit_id' in query_params: - params['visit_id'] = query_params['visit_id'] - log['directory'] = gen_snapshot_directory_link( - snapshot_context, revision_log[i]['id'], - link_text='Browse files', - link_attrs={'class': 'btn btn-default btn-sm', - 'role': 'button'}) + browse_view_name = 'browse-' + swh_type + '-log' + + prev_log_url = None + if len(rev_log) > offset + per_page: + query_params['offset'] = offset + per_page + prev_log_url = reverse(browse_view_name, + url_args=url_args, + query_params=query_params) + + next_log_url = None + if offset != 0: + query_params['offset'] = offset - per_page + next_log_url = reverse(browse_view_name, + url_args=url_args, + query_params=query_params) + + revision_log_data = format_log_entries(revision_log, per_page, + snapshot_context) browse_log_link = \ gen_revision_log_link(revision_id, link_text='Browse', @@ -741,6 +734,7 @@ 'swh_object_name': 'Revisions history', 'swh_object_metadata': revision_metadata, 'revision_log': revision_log_data, + 'revs_ordering': revs_ordering, 'next_log_url': next_log_url, 'prev_log_url': prev_log_url, 'breadcrumbs': None, diff --git a/swh/web/common/service.py b/swh/web/common/service.py --- a/swh/web/common/service.py +++ b/swh/web/common/service.py @@ -9,6 +9,8 @@ from swh.model import hashutil +from swh.storage.algos import revisions_walker + from swh.web.common import converters from swh.web.common import query from swh.web.common.exc import NotFoundExc @@ -981,3 +983,47 @@ change['to_path'] = change['to_path'].decode('utf-8') return changes + + +class _RevisionsWalkerProxy(object): + """ + Proxy class wrapping a revisions walker iterator from + swh-storage and performing needed conversions. + """ + def __init__(self, rev_walker_type, rev_start, *args, **kwargs): + rev_start_bin = hashutil.hash_to_bytes(rev_start) + self.revisions_walker = \ + revisions_walker.get_revisions_walker(rev_walker_type, + storage, + rev_start_bin, + *args, **kwargs) + + def export_state(self): + return self.revisions_walker.export_state() + + def __next__(self): + return converters.from_revision(next(self.revisions_walker)) + + def __iter__(self): + return self + + +def get_revisions_walker(rev_walker_type, rev_start, *args, **kwargs): + """ + Utility function to instantiate a revisions walker of a given type, + see :mod:`swh.storage.algos.revisions_walker`. + + Args: + rev_walker_type (str): the type of revisions walker to return, + possible values are: *committer_date*, *dfs*, *dfs_post*, + *bfs* and *path* + rev_start (str): hexadecimal representation of a revision identifier + args (list): position arguments to pass to the revisions walker + constructor + kwargs (dict): keyword arguments to pass to the revisions walker + constructor + + """ + # first check if the provided revision is valid + lookup_revision(rev_start) + return _RevisionsWalkerProxy(rev_walker_type, rev_start, *args, **kwargs) diff --git a/swh/web/settings/common.py b/swh/web/settings/common.py --- a/swh/web/settings/common.py +++ b/swh/web/settings/common.py @@ -88,7 +88,6 @@ WSGI_APPLICATION = 'swh.web.wsgi.application' - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -232,3 +231,5 @@ LOGIN_URL = '/admin/login/' LOGIN_REDIRECT_URL = 'admin' + +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' diff --git a/swh/web/templates/browse/revision-log.html b/swh/web/templates/browse/revision-log.html --- a/swh/web/templates/browse/revision-log.html +++ b/swh/web/templates/browse/revision-log.html @@ -23,30 +23,80 @@ {% if snapshot_context and snapshot_context.is_empty %} {% include "includes/empty-snapshot.html" %} {% else %} + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
- - + + - {% for log in revision_log %} - - - - - - + {% for rev in revision_log %} + + + + + + {% endfor %}
Revision AuthorMessage DateMessageCommit Date
{{ log.revision }}{{ log.message }}{{ log.directory }}
+ + {{ rev.id }} + + + + {{ rev.message }} + + + + {{ rev.commit_date }} + +
+ + {% endif %} {% endblock %} @@ -54,14 +104,22 @@ {% if not snapshot_context or not snapshot_context.is_empty %} {% endif %} diff --git a/swh/web/templates/browse/revision.html b/swh/web/templates/browse/revision.html --- a/swh/web/templates/browse/revision.html +++ b/swh/web/templates/browse/revision.html @@ -18,8 +18,9 @@ {% block swh-browse-content %}
- Revision {{ swh_object_metadata.id }} - authored by {{ swh_object_metadata.author }} on {{ swh_object_metadata.date }} + Revision {{ swh_object_metadata.id }} + authored by {{ swh_object_metadata.author }} on {{ swh_object_metadata.date }}, + committed by {{ swh_object_metadata.committer }} on {{ swh_object_metadata|key_value:'committer date' }}
diff --git a/swh/web/tests/browse/test_utils.py b/swh/web/tests/browse/test_utils.py --- a/swh/web/tests/browse/test_utils.py +++ b/swh/web/tests/browse/test_utils.py @@ -242,177 +242,3 @@ '%s' % (revision_url, revision_id)) self.assertEqual(utils.gen_revision_link(revision_id, shorten_id=True), '%s' % (revision_url, revision_id[:7])) - - def test_prepare_revision_log_for_display_no_contex(self): - per_page = 10 - first_page_logs_data = revision_history_log_test[:per_page+1] - second_page_logs_data = revision_history_log_test[per_page:2*per_page+1] - third_page_logs_data = revision_history_log_test[2*per_page:3*per_page+1] - last_page_logs_data = revision_history_log_test[3*per_page:3*per_page+5] - - revision_log_display_data = utils.prepare_revision_log_for_display( - first_page_logs_data, per_page, None) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(first_page_logs_data, - per_page)) - - self.assertEqual(revision_log_display_data['prev_rev'], - first_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - first_page_logs_data[0]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], None) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - None) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - second_page_logs_data, per_page, old_prev_revs_bc) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(second_page_logs_data, - per_page)) - - self.assertEqual(revision_log_display_data['prev_rev'], - second_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - old_prev_revs_bc + '/' + second_page_logs_data[0]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], - old_prev_revs_bc) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - None) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - third_page_logs_data, per_page, old_prev_revs_bc) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(third_page_logs_data, per_page)) - - self.assertEqual(revision_log_display_data['prev_rev'], - third_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - old_prev_revs_bc + '/' + third_page_logs_data[0]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], - old_prev_revs_bc.split('/')[-1]) - - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - '/'.join(old_prev_revs_bc.split('/')[:-1])) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - last_page_logs_data, per_page, old_prev_revs_bc) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(last_page_logs_data, per_page)) - - self.assertEqual(revision_log_display_data['prev_rev'], - None) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - None) - - self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - '/'.join(old_prev_revs_bc.split('/')[:-1])) - - def test_prepare_revision_log_for_display_snapshot_context(self): - per_page = 10 - first_page_logs_data = revision_history_log_test[:per_page+1] - second_page_logs_data = revision_history_log_test[per_page:2*per_page+1] - third_page_logs_data = revision_history_log_test[2*per_page:3*per_page+1] - last_page_logs_data = revision_history_log_test[3*per_page:3*per_page+5] - - snapshot_context = { - 'origin_info': {'type': 'git', - 'url': 'https://github.com/git/git'}, - 'origin_type': 'git', - 'url_args': {}, - 'query_params': {} - } - - revision_log_display_data = utils.prepare_revision_log_for_display( - first_page_logs_data, per_page, None, snapshot_context=snapshot_context) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(first_page_logs_data, - per_page, snapshot_context=snapshot_context)) - - self.assertEqual(revision_log_display_data['prev_rev'], - first_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - first_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], None) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - None) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - second_page_logs_data, per_page, old_prev_revs_bc, snapshot_context=snapshot_context) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(second_page_logs_data, - per_page, snapshot_context=snapshot_context)) - - self.assertEqual(revision_log_display_data['prev_rev'], - second_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - old_prev_revs_bc + '/' + second_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], - old_prev_revs_bc) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - None) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - third_page_logs_data, per_page, old_prev_revs_bc, snapshot_context=snapshot_context) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(third_page_logs_data, per_page, - snapshot_context=snapshot_context)) - - self.assertEqual(revision_log_display_data['prev_rev'], - third_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - old_prev_revs_bc + '/' + third_page_logs_data[-1]['id']) - - self.assertEqual(revision_log_display_data['next_rev'], - old_prev_revs_bc.split('/')[-1]) - - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - '/'.join(old_prev_revs_bc.split('/')[:-1])) - - old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) - - revision_log_display_data = utils.prepare_revision_log_for_display( - last_page_logs_data, per_page, old_prev_revs_bc, snapshot_context=snapshot_context) - - self.assertEqual(revision_log_display_data['revision_log_data'], - utils._format_log_entries(last_page_logs_data, per_page, - snapshot_context=snapshot_context)) - - self.assertEqual(revision_log_display_data['prev_rev'], - None) - - self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], - None) - - self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) - self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], - '/'.join(old_prev_revs_bc.split('/')[:-1])) diff --git a/swh/web/tests/browse/views/data/revision_test_data.py b/swh/web/tests/browse/views/data/revision_test_data.py --- a/swh/web/tests/browse/views/data/revision_test_data.py +++ b/swh/web/tests/browse/views/data/revision_test_data.py @@ -855,5 +855,51 @@ 'metadata': {}, 'parents': ['8aa8a7b63fed3c7909a6d4f15159c036a0561d64'], 'synthetic': False, - 'type': 'git'} + 'type': 'git'}, + {'author': { + 'email': 'tobias.koppers@googlemail.com', + 'fullname': 'Tobias Koppers ', + 'id': 141959, + 'name': 'Tobias Koppers' + }, + 'committer': { + 'email': 'noreply@github.com', + 'fullname': 'GitHub ', + 'id': 10932771, + 'name': 'GitHub' + }, + 'committer_date': '2017-04-21T19:00:50+02:00', + 'committer_url': '/api/1/person/10932771/', + 'date': '2017-04-21T19:00:50+02:00', + 'directory': '52cf6f28b1dbfe98f485ea78ae03942f55cd8fa0', + 'id': 'd7f30392ddda27613e0ea05cb60ec985b4f75e5c', + 'merge': True, + 'message': 'Merge pull request #4729 from simon04/provide-plugin-es2015\n\nProvidePlugin: add test case for ES2015 modules', + 'metadata': {}, + 'parents': ['88f37348e7de240d794713c0c38170f17a0a8c0e', + 'd0bbf967fb51c031e16c5dfe040afce9a4113b5b'], + 'synthetic': False, + 'type': 'git'}, + {'author': { + 'email': 'kevin@fossa.io', + 'fullname': 'Kevin Wang ', + 'id': 14855611, + 'name': 'Kevin Wang' + }, + 'committer': { + 'email': 'noreply@github.com', + 'fullname': 'GitHub ', + 'id': 10932771, + 'name': 'GitHub' + }, + 'committer_date': '2017-04-21T18:28:41-07:00', + 'date': '2017-04-21T18:28:41-07:00', + 'directory': '412c6f5c00ce7d02efcdd6f23160a36eb4e5a1f4', + 'id': 'ff211108d888908d41470cea6187133ccdb56e87', + 'merge': False, + 'message': 'Add license scan report and status', + 'metadata': {}, + 'parents': ['d7f30392ddda27613e0ea05cb60ec985b4f75e5c'], + 'synthetic': False, + 'type': 'git'}, ] \ No newline at end of file diff --git a/swh/web/tests/browse/views/test_revision.py b/swh/web/tests/browse/views/test_revision.py --- a/swh/web/tests/browse/views/test_revision.py +++ b/swh/web/tests/browse/views/test_revision.py @@ -5,12 +5,13 @@ # flake8: noqa -from unittest.mock import patch +from unittest.mock import patch, MagicMock from django.utils.html import escape from swh.web.common.exc import NotFoundExc from swh.web.common.utils import ( - reverse, format_utc_iso_date, get_swh_persistent_id + reverse, format_utc_iso_date, get_swh_persistent_id, + parse_timestamp ) from swh.web.tests.testcase import SWHWebTestCase @@ -95,19 +96,16 @@ mock_service_common.MAX_LIMIT = 20 origin_directory_url = reverse('browse-origin-directory', - url_args={'origin_type': origin_info['type'], - 'origin_url': origin_info['url']}, + url_args={'origin_url': origin_info['url']}, query_params={'revision': revision_id_test}) origin_revision_log_url = reverse('browse-origin-log', - url_args={'origin_type': origin_info['type'], - 'origin_url': origin_info['url']}, + url_args={'origin_url': origin_info['url']}, query_params={'revision': revision_id_test}) url = reverse('browse-revision', url_args={'sha1_git': revision_id_test}, - query_params={'origin_type': origin_info['type'], - 'origin': origin_info['url']}) + query_params={'origin': origin_info['url']}) resp = self.client.get(url) @@ -118,8 +116,7 @@ for parent in revision_metadata_test['parents']: parent_url = reverse('browse-revision', url_args={'sha1_git': parent}, - query_params={'origin_type': origin_info['type'], - 'origin': origin_info['url']}) + query_params={'origin': origin_info['url']}) self.assertContains(resp, '%s' % (parent_url, parent)) @@ -142,8 +139,15 @@ def test_revision_log_browse(self, mock_service): per_page = 10 - mock_service.lookup_revision_log.return_value = \ - revision_history_log_test[:per_page+1] + revision_history_log_test_sorted = \ + sorted(revision_history_log_test, + key=lambda rev: -parse_timestamp(rev['committer_date']).timestamp()) + + mock_revs_walker = MagicMock() + mock_revs_walker.__iter__.return_value = revision_history_log_test_sorted + mock_revs_walker.export_state.return_value = {} + + mock_service.get_revisions_walker.return_value = mock_revs_walker url = reverse('browse-revision-log', url_args={'sha1_git': revision_id_test}, @@ -151,98 +155,70 @@ resp = self.client.get(url) - prev_rev = revision_history_log_test[per_page]['id'] next_page_url = reverse('browse-revision-log', - url_args={'sha1_git': prev_rev}, - query_params={'revs_breadcrumb': revision_id_test, + url_args={'sha1_git': revision_id_test}, + query_params={'offset': per_page, 'per_page': per_page}) self.assertEqual(resp.status_code, 200) self.assertTemplateUsed('browse/revision-log.html') - self.assertContains(resp, '', + self.assertContains(resp, 'Newer') - self.assertContains(resp, '
  • Older
  • ' % + self.assertContains(resp, 'Newer') + self.assertContains(resp, 'Older' % escape(next_page_url)) - for log in revision_history_log_test[:per_page]: + for log in revision_history_log_test_sorted[:per_page]: author_url = reverse('browse-person', url_args={'person_id': log['author']['id']}) revision_url = reverse('browse-revision', url_args={'sha1_git': log['id']}) - directory_url = reverse('browse-directory', - url_args={'sha1_git': log['directory']}) - self.assertContains(resp, '%s' % - (author_url, log['author']['name'])) - self.assertContains(resp, '%s' % - (revision_url, log['id'][:7])) - self.assertContains(resp, directory_url) - - mock_service.lookup_revision_log.return_value = \ - revision_history_log_test[per_page:2*per_page+1] + self.assertContains(resp, log['id'][:7]) + self.assertContains(resp, log['author']['name']) + self.assertContains(resp, format_utc_iso_date(log['date'])) + self.assertContains(resp, escape(log['message'])) + self.assertContains(resp, format_utc_iso_date(log['committer_date'])) + self.assertContains(resp, revision_url) resp = self.client.get(next_page_url) - prev_prev_rev = revision_history_log_test[2*per_page]['id'] prev_page_url = reverse('browse-revision-log', url_args={'sha1_git': revision_id_test}, query_params={'per_page': per_page}) next_page_url = reverse('browse-revision-log', - url_args={'sha1_git': prev_prev_rev}, - query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, + url_args={'sha1_git': revision_id_test}, + query_params={'offset': 2 * per_page, 'per_page': per_page}) self.assertEqual(resp.status_code, 200) self.assertTemplateUsed('browse/revision-log.html') - self.assertContains(resp, '', + self.assertContains(resp, 'Newer' % + self.assertContains(resp, 'Newer' % escape(prev_page_url)) - self.assertContains(resp, '
  • Older
  • ' % + self.assertContains(resp, 'Older' % escape(next_page_url)) - mock_service.lookup_revision_log.return_value = \ - revision_history_log_test[2*per_page:3*per_page+1] - resp = self.client.get(next_page_url) - prev_prev_prev_rev = revision_history_log_test[3*per_page]['id'] prev_page_url = reverse('browse-revision-log', - url_args={'sha1_git': prev_rev}, - query_params={'revs_breadcrumb': revision_id_test, + url_args={'sha1_git': revision_id_test}, + query_params={'offset': per_page, 'per_page': per_page}) next_page_url = reverse('browse-revision-log', - url_args={'sha1_git': prev_prev_prev_rev}, - query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev + '/' + prev_prev_rev, + url_args={'sha1_git': revision_id_test}, + query_params={'offset': 3 * per_page, 'per_page': per_page}) self.assertEqual(resp.status_code, 200) self.assertTemplateUsed('browse/revision-log.html') - self.assertContains(resp, '', + self.assertContains(resp, 'Newer' % + self.assertContains(resp, 'Newer' % escape(prev_page_url)) - self.assertContains(resp, '
  • Older
  • ' % + self.assertContains(resp, 'Older' % escape(next_page_url)) - mock_service.lookup_revision_log.return_value = \ - revision_history_log_test[3*per_page:3*per_page+per_page//2] - - resp = self.client.get(next_page_url) - - prev_page_url = reverse('browse-revision-log', - url_args={'sha1_git': prev_prev_rev}, - query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, - 'per_page': per_page}) - - self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed('browse/revision-log.html') - self.assertContains(resp, '', - count=per_page//2) - self.assertContains(resp, '
  • Older
  • ') - self.assertContains(resp, '
  • Newer
  • ' % - escape(prev_page_url)) - @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.revision.service') def test_revision_request_errors(self, mock_service, mock_utils_service): @@ -255,7 +231,7 @@ self.assertTemplateUsed('error.html') self.assertContains(resp, 'Revision not found', status_code=404) - mock_service.lookup_revision_log.side_effect = \ + mock_service.get_revisions_walker.side_effect = \ NotFoundExc('Revision not found') url = reverse('browse-revision-log', url_args={'sha1_git': revision_id_test})