diff --git a/swh/web/ui/converters.py b/swh/web/ui/converters.py --- a/swh/web/ui/converters.py +++ b/swh/web/ui/converters.py @@ -181,6 +181,8 @@ dates={'date', 'committer_date'}) if revision: + if 'parents' in revision: + revision['merge'] = len(revision['parents']) > 1 if 'message' in revision: try: revision['message'] = revision['message'].decode('utf-8') diff --git a/swh/web/ui/main.py b/swh/web/ui/main.py --- a/swh/web/ui/main.py +++ b/swh/web/ui/main.py @@ -11,6 +11,7 @@ from swh.web.ui.renderers import RENDERERS, urlize_api_links from swh.web.ui.renderers import safe_docstring_display +from swh.web.ui.renderers import revision_id_from_url from swh.storage import get_storage @@ -32,7 +33,7 @@ app = FlaskAPI(__name__) app.jinja_env.filters['urlize_api_links'] = urlize_api_links app.jinja_env.filters['safe_docstring_display'] = safe_docstring_display - +app.jinja_env.filters['revision_id_from_url'] = revision_id_from_url AUTODOC_ENDPOINT_INSTALLED = False diff --git a/swh/web/ui/renderers.py b/swh/web/ui/renderers.py --- a/swh/web/ui/renderers.py +++ b/swh/web/ui/renderers.py @@ -92,6 +92,12 @@ return re.sub(src, dest, docstring) +def revision_id_from_url(url): + """Utility function to obtain a revision's ID from its browsing URL.""" + return re.sub(r'/browse/revision/([0-9a-f]{40}|[0-9a-f]{64})/.*', + r'\1', url) + + class SWHBrowsableAPIRenderer(renderers.BrowsableAPIRenderer): """SWH's browsable api renderer. diff --git a/swh/web/ui/templates/revision-log.html b/swh/web/ui/templates/revision-log.html --- a/swh/web/ui/templates/revision-log.html +++ b/swh/web/ui/templates/revision-log.html @@ -3,133 +3,154 @@ {% block content %} {% if message is not none %} +
{{ message }} +
{% endif %} -
-

Queried revision:

- {% if sha1_git is not none %} -
Revision with git SHA1 {{ sha1_git }}
- {% else %} -
Origin ID {{ origin_id }}
-
Branch name {{ branch_name }}
- {% if timestamp is not none %} -
Time stamp {{ timestamp }}
- {% endif %} -
-
- {% endif %} -
+
+

Queried revision:

+ {% if sha1_git is not none %} +
Revision with git SHA1 {{ sha1_git }}
+ {% else %} +
Origin ID {{ origin_id }}
+
Branch name {{ branch_name }}
+ {% if timestamp is not none %} +
Time stamp {{ timestamp }}
+ {% endif %} + {% endif %} +
{% if revisions is not none %} +
{% for revision in revisions %} - -
- {% if revision['url'] is not none %} -
-
Revision
- + {% if revision['merge'] %} +
+
+ Merge +
+
+ {% for url in revision['parent_urls'] %} + {{ url | revision_id_from_url }} + {% endfor %}
- {% endif %} +
+ {% endif %} + + {% if revision['url'] is not none %} +
+
Revision
+ +
+ {% endif %} - {% if revision['history_url'] is not none %} -
-
Revision Log
- + {% if revision['history_url'] is not none %} +
+
Revision Log
+ +
+ {% endif %} + + {% if revision['history_context_url'] is not none %} +
+
Contextual Revision Log
+ +
+ {% endif %} + + {% if revision['directory_url'] is not none %} + + {% endif %} + + {% if revision['author'] is not none %} +
+
Author
+
+

+ {{ revision['author']['name'] }} + {% if 'decoding_failures' in revision['author'] %}(some decoding errors){% endif %} +

- {% endif %} +
+
+
Date
+

{{ revision['date'] }}

+
+ {% endif %} - {% if revision['history_context_url'] is not none %} -
-
Contextual Revision Log
- + {% if revision['committer'] is not none %} +
+
Committer
+
+

+ {{ revision['committer']['name'] }} + {% if 'decoding_failures' in revision['committer'] %}(some decoding errors){% endif %} +

- {% endif %} - - {% if revision['directory_url'] is not none %} - - {% endif %} +
+
+
Committer Date
+

{{ revision['committer_date'] }}

+
+ {% endif %} - {% if revision['author'] is not none %} -
-
Author
-
-

- {{ revision['author']['name'] }} - {% if 'decoding_failures' in revision['author'] %}(some decoding errors){% endif %} -

-
-
-
-
Date
-

{{ revision['date'] }}

-
- {% endif %} + {% if revision['message'] is not none %} +
+
Message
+
{{ revision['message'] }}
+
+ {% elif revision['message_encoding_failed'] %} +
+
Message
+ +
+ {% else %} +
+
Message
+
No message found.
+
+ {% endif %} - {% if revision['committer'] is not none %} -
-
Committer
-
-

- {{ revision['committer']['name'] }} - {% if 'decoding_failures' in revision['committer'] %}(some decoding errors){% endif %} -

-
-
-
-
Committer Date
-

{{ revision['committer_date'] }}

-
- {% endif %} + {% for key in revision.keys() %} + {% if key in ['type', 'synthetic'] and key not in ['decoding_failures'] and revision[key] is not none %} +
+
{{ key }}
+
{{ revision[key] }}
+
+ {% endif %} + {% endfor %} - {% if revision['message'] is not none %} -
-
Message
-
{{ revision['message'] }}
-
- {% elif revision['message_encoding_failed'] %} -
-
Message
- -
-
Message
-
No message found.
-
- {% endif %} - - {% for key in revision.keys() %} - {% if key in ['type', 'synthetic'] and key not in ['decoding_failures'] and revision[key] is not none %} -
-
{{ key }}
-

{{ revision[key] }}

-
- {% endif %} + {% for key in ['children_urls', 'parent_urls'] %} + {% if revision[key] is not none %} +
+
{{ key }}
+ {% for link in revision[key] %} + {% endfor %} - {% for key in ['parent_urls', 'children_urls'] %} - {% if revision[key] is not none %} -
-
{{ key }}
- {% for link in revision[key] %} - - {% endfor %} -
- {% endif %} - {% endfor %} - {% if 'decoding_failures' in revision %} -
-
(some decoding errors occurred)
-
- {% endif %} -
+ {% endif %} + {% endfor %} + + {% if 'decoding_failures' in revision %} +
+
(some decoding errors occurred)
+
+ {% endif %}
{% endfor %} - -{% endif %} - + + {% if next_revs_url is not none %} + + + Next revisions + + + + {% endif %} + {% endif %} +
+
{% endblock %} diff --git a/swh/web/ui/tests/test_converters.py b/swh/web/ui/tests/test_converters.py --- a/swh/web/ui/tests/test_converters.py +++ b/swh/web/ui/tests/test_converters.py @@ -344,6 +344,122 @@ '309d36484e7edf7bb912' }] }, + 'merge': True + } + + # when + actual_revision = converters.from_revision(revision_input) + + # then + self.assertEqual(actual_revision, expected_revision) + + @istest + def from_revision_nomerge(self): + revision_input = { + 'id': hashutil.hex_to_hash( + '18d8be353ed3480476f032475e7c233eff7371d5'), + 'parents': [ + hashutil.hex_to_hash( + '29d8be353ed3480476f032475e7c244eff7371d5') + ] + } + + expected_revision = { + 'id': '18d8be353ed3480476f032475e7c233eff7371d5', + 'parents': [ + '29d8be353ed3480476f032475e7c244eff7371d5' + ], + 'merge': False + } + + # when + actual_revision = converters.from_revision(revision_input) + + # then + self.assertEqual(actual_revision, expected_revision) + + @istest + def from_revision_noparents(self): + revision_input = { + 'id': hashutil.hex_to_hash( + '18d8be353ed3480476f032475e7c233eff7371d5'), + 'directory': hashutil.hex_to_hash( + '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), + 'author': { + 'name': b'Software Heritage', + 'fullname': b'robot robot@softwareheritage.org', + 'email': b'robot@softwareheritage.org', + }, + 'committer': { + 'name': b'Software Heritage', + 'fullname': b'robot robot@softwareheritage.org', + 'email': b'robot@softwareheritage.org', + }, + 'message': b'synthetic revision message', + 'date': { + 'timestamp': datetime.datetime( + 2000, 1, 17, 11, 23, 54, + tzinfo=datetime.timezone.utc).timestamp(), + 'offset': 0, + 'negative_utc': False, + }, + 'committer_date': { + 'timestamp': datetime.datetime( + 2000, 1, 17, 11, 23, 54, + tzinfo=datetime.timezone.utc).timestamp(), + 'offset': 0, + 'negative_utc': False, + }, + 'synthetic': True, + 'type': 'tar', + 'children': [ + hashutil.hex_to_hash( + '123546353ed3480476f032475e7c244eff7371d5'), + ], + 'metadata': { + 'original_artifact': [{ + 'archive_type': 'tar', + 'name': 'webbase-5.7.0.tar.gz', + 'sha1': '147f73f369733d088b7a6fa9c4e0273dcd3c7ccd', + 'sha1_git': '6a15ea8b881069adedf11feceec35588f2cfe8f1', + 'sha256': '401d0df797110bea805d358b85bcc1ced29549d3d73f' + '309d36484e7edf7bb912', + + }] + }, + } + + expected_revision = { + 'id': '18d8be353ed3480476f032475e7c233eff7371d5', + 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', + 'author': { + 'name': 'Software Heritage', + 'fullname': 'robot robot@softwareheritage.org', + 'email': 'robot@softwareheritage.org', + }, + 'committer': { + 'name': 'Software Heritage', + 'fullname': 'robot robot@softwareheritage.org', + 'email': 'robot@softwareheritage.org', + }, + 'message': 'synthetic revision message', + 'date': "2000-01-17T11:23:54+00:00", + 'committer_date': "2000-01-17T11:23:54+00:00", + 'children': [ + '123546353ed3480476f032475e7c244eff7371d5' + ], + 'type': 'tar', + 'synthetic': True, + 'metadata': { + 'original_artifact': [{ + 'archive_type': 'tar', + 'name': 'webbase-5.7.0.tar.gz', + 'sha1': '147f73f369733d088b7a6fa9c4e0273dcd3c7ccd', + 'sha1_git': '6a15ea8b881069adedf11feceec35588f2cfe8f1', + 'sha256': '401d0df797110bea805d358b85bcc1ced29549d3d73f' + '309d36484e7edf7bb912' + }] + } } # when @@ -445,6 +561,7 @@ '309d36484e7edf7bb912' }] }, + 'merge': True } # when diff --git a/swh/web/ui/tests/test_service.py b/swh/web/ui/tests/test_service.py --- a/swh/web/ui/tests/test_service.py +++ b/swh/web/ui/tests/test_service.py @@ -16,6 +16,87 @@ class ServiceTestCase(test_app.SWHApiTestCase): + def setUp(self): + self.SHA1_SAMPLE = '18d8be353ed3480476f032475e7c233eff7371d5' + self.SHA1_SAMPLE_BIN = hex_to_hash(self.SHA1_SAMPLE) + self.SHA256_SAMPLE = ('39007420ca5de7cb3cfc15196335507e' + 'e76c98930e7e0afa4d2747d3bf96c926') + self.SHA256_SAMPLE_BIN = hex_to_hash(self.SHA256_SAMPLE) + self.SHA1GIT_SAMPLE = '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' + self.SHA1GIT_SAMPLE_BIN = hex_to_hash(self.SHA1GIT_SAMPLE) + self.DIRECTORY_ID = '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6' + self.DIRECTORY_ID_BIN = hex_to_hash(self.DIRECTORY_ID) + self.AUTHOR_ID_BIN = { + 'name': b'author', + 'email': b'author@company.org', + } + self.AUTHOR_ID = { + 'name': 'author', + 'email': 'author@company.org', + } + self.COMMITTER_ID_BIN = { + 'name': b'committer', + 'email': b'committer@corp.org', + } + self.COMMITTER_ID = { + 'name': 'committer', + 'email': 'committer@corp.org', + } + self.SAMPLE_DATE_RAW = { + 'timestamp': datetime.datetime( + 2000, 1, 17, 11, 23, 54, + tzinfo=datetime.timezone.utc, + ).timestamp(), + 'offset': 0, + 'negative_utc': False, + } + self.SAMPLE_DATE = '2000-01-17T11:23:54+00:00' + self.SAMPLE_MESSAGE_BIN = b'elegant fix for bug 31415957' + self.SAMPLE_MESSAGE = 'elegant fix for bug 31415957' + + self.SAMPLE_REVISION = { + 'id': self.SHA1_SAMPLE, + 'directory': self.DIRECTORY_ID, + 'author': self.AUTHOR_ID, + 'committer': self.COMMITTER_ID, + 'message': self.SAMPLE_MESSAGE, + 'date': self.SAMPLE_DATE, + 'committer_date': self.SAMPLE_DATE, + 'synthetic': False, + 'type': 'git', + 'parents': [], + 'metadata': [], + 'merge': False + } + self.SAMPLE_REVISION_RAW = { + 'id': self.SHA1_SAMPLE_BIN, + 'directory': self.DIRECTORY_ID_BIN, + 'author': self.AUTHOR_ID_BIN, + 'committer': self.COMMITTER_ID_BIN, + 'message': self.SAMPLE_MESSAGE_BIN, + 'date': self.SAMPLE_DATE_RAW, + 'committer_date': self.SAMPLE_DATE_RAW, + 'synthetic': False, + 'type': 'git', + 'parents': [], + 'metadata': [], + } + + self.SAMPLE_CONTENT = { + 'sha1': self.SHA1_SAMPLE, + 'sha256': self.SHA256_SAMPLE, + 'sha1_git': self.SHA1GIT_SAMPLE, + 'length': 190, + 'status': 'absent' + } + self.SAMPLE_CONTENT_RAW = { + 'sha1': self.SHA1_SAMPLE_BIN, + 'sha256': self.SHA256_SAMPLE_BIN, + 'sha1_git': self.SHA1GIT_SAMPLE_BIN, + 'length': 190, + 'status': 'hidden' + } + @patch('swh.web.ui.service.backend') @istest def lookup_multiple_hashes_ball_missing(self, mock_backend): @@ -574,6 +655,7 @@ 'parents': [], 'children': [hash_to_hex(b'999'), hash_to_hex(b'777')], 'directory': hash_to_hex(b'278'), + 'merge': False }) mock_query.parse_hash_with_algorithms_or_throws.assert_has_calls( @@ -646,6 +728,7 @@ 'parents': [], 'children': [hash_to_hex(b'999'), hash_to_hex(b'777')], 'directory': hash_to_hex(b'278'), + 'merge': False }) mock_query.parse_hash_with_algorithms_or_throws.assert_called_once_with( # noqa @@ -952,232 +1035,72 @@ @istest def lookup_revision(self, mock_backend): # given - mock_backend.revision_get = MagicMock(return_value={ - 'id': hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug 31415957', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }) + mock_backend.revision_get = MagicMock( + return_value=self.SAMPLE_REVISION_RAW) # when actual_revision = service.lookup_revision( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) # then - self.assertEqual(actual_revision, { - 'id': '18d8be353ed3480476f032475e7c233eff7371d5', - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'bill & boule', - 'email': 'bill@boule.org', - }, - 'committer': { - 'name': 'boule & bill', - 'email': 'boule@bill.org', - }, - 'message': 'elegant fix for bug 31415957', - 'date': "2000-01-17T11:23:54+00:00", - 'committer_date': "2000-01-17T11:23:54+00:00", - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }) + self.assertEqual(actual_revision, self.SAMPLE_REVISION) mock_backend.revision_get.assert_called_with( - hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + self.SHA1_SAMPLE_BIN) @patch('swh.web.ui.service.backend') @istest def lookup_revision_invalid_msg(self, mock_backend): # given - stub_rev = { - 'id': hex_to_hash('123456'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug \xff', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - } + stub_rev = self.SAMPLE_REVISION_RAW + stub_rev['message'] = b'elegant fix for bug \xff' + + expected_revision = self.SAMPLE_REVISION + expected_revision['message'] = None + expected_revision['message_decoding_failed'] = True mock_backend.revision_get = MagicMock(return_value=stub_rev) # when actual_revision = service.lookup_revision( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) # then - self.assertEqual(actual_revision, { - 'id': '123456', - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'bill & boule', - 'email': 'bill@boule.org', - }, - 'committer': { - 'name': 'boule & bill', - 'email': 'boule@bill.org', - }, - 'message': None, - 'message_decoding_failed': True, - 'date': "2000-01-17T11:23:54+00:00", - 'committer_date': "2000-01-17T11:23:54+00:00", - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }) + self.assertEqual(actual_revision, expected_revision) mock_backend.revision_get.assert_called_with( - hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + self.SHA1_SAMPLE_BIN) @patch('swh.web.ui.service.backend') @istest def lookup_revision_msg_ok(self, mock_backend): # given - mock_backend.revision_get.return_value = { - 'id': hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug 31415957', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - } + mock_backend.revision_get.return_value = self.SAMPLE_REVISION_RAW # when rv = service.lookup_revision_message( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) # then - self.assertEquals(rv, {'message': b'elegant fix for bug 31415957'}) + self.assertEquals(rv, {'message': self.SAMPLE_MESSAGE_BIN}) mock_backend.revision_get.assert_called_with( - hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + self.SHA1_SAMPLE_BIN) @patch('swh.web.ui.service.backend') @istest def lookup_revision_msg_absent(self, mock_backend): # given - mock_backend.revision_get.return_value = { - 'id': hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - } + stub_revision = self.SAMPLE_REVISION_RAW + del stub_revision['message'] + mock_backend.revision_get.return_value = stub_revision # when with self.assertRaises(NotFoundExc) as cm: service.lookup_revision_message( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) # then mock_backend.revision_get.assert_called_with( - hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + self.SHA1_SAMPLE_BIN) self.assertEqual(cm.exception.args[0], 'No message for revision ' 'with sha1_git ' '18d8be353ed3480476f032475e7c233eff7371d5.') @@ -1191,11 +1114,11 @@ # when with self.assertRaises(NotFoundExc) as cm: service.lookup_revision_message( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) # then mock_backend.revision_get.assert_called_with( - hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + self.SHA1_SAMPLE_BIN) self.assertEqual(cm.exception.args[0], 'Revision with sha1_git ' '18d8be353ed3480476f032475e7c233eff7371d5 ' 'not found.') @@ -1204,44 +1127,11 @@ @istest def lookup_revision_multiple(self, mock_backend): # given - - sha1_bin = '18d8be353ed3480476f032475e7c233eff7371d5' + sha1 = self.SHA1_SAMPLE sha1_other = 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc' stub_revisions = [ - { - 'id': hex_to_hash(sha1_bin), - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug 31415957', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc).timestamp(), - 'offset': 0, - 'negative_utc': False - }, - 'date_offset': 0, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc).timestamp(), - 'offset': 0, - 'negative_utc': False - }, - 'committer_date_offset': 0, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }, + self.SAMPLE_REVISION_RAW, { 'id': hex_to_hash(sha1_other), 'directory': 'abcdbe353ed3480476f032475e7c233eff7371d5', @@ -1281,31 +1171,11 @@ # when actual_revisions = service.lookup_revision_multiple( - [sha1_bin, sha1_other]) + [sha1, sha1_other]) # then self.assertEqual(list(actual_revisions), [ - { - 'id': sha1_bin, - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'bill & boule', - 'email': 'bill@boule.org', - }, - 'committer': { - 'name': 'boule & bill', - 'email': 'boule@bill.org', - }, - 'message': 'elegant fix for bug 31415957', - 'date': '2000-01-17T11:23:54+00:00', - 'date_offset': 0, - 'committer_date': '2000-01-17T11:23:54+00:00', - 'committer_date_offset': 0, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }, + self.SAMPLE_REVISION, { 'id': sha1_other, 'directory': 'abcdbe353ed3480476f032475e7c233eff7371d5', @@ -1326,21 +1196,20 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False } ]) self.assertEqual( list(mock_backend.revision_get_multiple.call_args[0][0]), - [hex_to_hash( - '18d8be353ed3480476f032475e7c233eff7371d5'), - hex_to_hash( - 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc')]) + [hex_to_hash(sha1), + hex_to_hash(sha1_other)]) @patch('swh.web.ui.service.backend') @istest def lookup_revision_multiple_none_found(self, mock_backend): # given - sha1_bin = '18d8be353ed3480476f032475e7c233eff7371d5' + sha1_bin = self.SHA1_SAMPLE sha1_other = 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc' mock_backend.revision_get_multiple.return_value = [] @@ -1353,49 +1222,14 @@ self.assertEqual( list(mock_backend.revision_get_multiple.call_args[0][0]), - [hex_to_hash( - '18d8be353ed3480476f032475e7c233eff7371d5'), - hex_to_hash( - 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc')]) + [hex_to_hash(self.SHA1_SAMPLE), + hex_to_hash(sha1_other)]) @patch('swh.web.ui.service.backend') @istest def lookup_revision_log(self, mock_backend): # given - stub_revision_log = [{ - 'id': hex_to_hash('28d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug 31415957', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }] + stub_revision_log = [self.SAMPLE_REVISION_RAW] mock_backend.revision_log = MagicMock(return_value=stub_revision_log) # when @@ -1403,25 +1237,7 @@ 'abcdbe353ed3480476f032475e7c233eff7371d5') # then - self.assertEqual(list(actual_revision), [{ - 'id': '28d8be353ed3480476f032475e7c233eff7371d5', - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'bill & boule', - 'email': 'bill@boule.org', - }, - 'committer': { - 'name': 'boule & bill', - 'email': 'boule@bill.org', - }, - 'message': 'elegant fix for bug 31415957', - 'date': "2000-01-17T11:23:54+00:00", - 'committer_date': "2000-01-17T11:23:54+00:00", - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }]) + self.assertEqual(list(actual_revision), [self.SAMPLE_REVISION]) mock_backend.revision_log.assert_called_with( hex_to_hash('abcdbe353ed3480476f032475e7c233eff7371d5'), 100) @@ -1430,40 +1246,7 @@ @istest def lookup_revision_log_by(self, mock_backend): # given - stub_revision_log = [{ - 'id': hex_to_hash('28d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'bill & boule', - 'email': b'bill@boule.org', - }, - 'committer': { - 'name': b'boule & bill', - 'email': b'boule@bill.org', - }, - 'message': b'elegant fix for bug 31415957', - 'date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2000, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc, - ).timestamp(), - 'offset': 0, - 'negative_utc': False, - }, - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }] + stub_revision_log = [self.SAMPLE_REVISION_RAW] mock_backend.revision_log_by = MagicMock( return_value=stub_revision_log) @@ -1471,25 +1254,7 @@ actual_log = service.lookup_revision_log_by( 1, 'refs/heads/master', None) # then - self.assertEqual(list(actual_log), [{ - 'id': '28d8be353ed3480476f032475e7c233eff7371d5', - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'bill & boule', - 'email': 'bill@boule.org', - }, - 'committer': { - 'name': 'boule & bill', - 'email': 'boule@bill.org', - }, - 'message': 'elegant fix for bug 31415957', - 'date': "2000-01-17T11:23:54+00:00", - 'committer_date': "2000-01-17T11:23:54+00:00", - 'synthetic': False, - 'type': 'git', - 'parents': [], - 'metadata': [], - }]) + self.assertEqual(list(actual_log), [self.SAMPLE_REVISION]) mock_backend.revision_log_by.assert_called_with( 1, 'refs/heads/master', None) @@ -1522,31 +1287,29 @@ self.assertIsNone(actual_content) mock_backend.content_find.assert_called_with( - 'sha1', hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + 'sha1', hex_to_hash(self.SHA1_SAMPLE)) @patch('swh.web.ui.service.backend') @istest def lookup_content_raw(self, mock_backend): # given mock_backend.content_find = MagicMock(return_value={ - 'sha1': '18d8be353ed3480476f032475e7c233eff7371d5', + 'sha1': self.SHA1_SAMPLE, }) mock_backend.content_get = MagicMock(return_value={ 'data': b'binary data'}) # when actual_content = service.lookup_content_raw( - 'sha256:39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926') + 'sha256:%s' % self.SHA256_SAMPLE) # then self.assertEquals(actual_content, {'data': b'binary data'}) mock_backend.content_find.assert_called_once_with( - 'sha256', hex_to_hash('39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926')) + 'sha256', self.SHA256_SAMPLE_BIN) mock_backend.content_get.assert_called_once_with( - '18d8be353ed3480476f032475e7c233eff7371d5') + self.SHA1_SAMPLE) @patch('swh.web.ui.service.backend') @istest @@ -1556,77 +1319,52 @@ # when actual_content = service.lookup_content( - 'sha1:18d8be353ed3480476f032475e7c233eff7371d5') + 'sha1:%s' % self.SHA1_SAMPLE) # then self.assertIsNone(actual_content) mock_backend.content_find.assert_called_with( - 'sha1', hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + 'sha1', self.SHA1_SAMPLE_BIN) @patch('swh.web.ui.service.backend') @istest def lookup_content_with_sha1(self, mock_backend): # given - mock_backend.content_find = MagicMock(return_value={ - 'sha1': hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5'), - 'sha256': hex_to_hash('39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926'), - 'sha1_git': hex_to_hash('40e71b8614fcd89ccd17ca2b1d9e66' - 'c5b00a6d03'), - 'length': 190, - 'status': 'hidden', - }) + mock_backend.content_find = MagicMock( + return_value=self.SAMPLE_CONTENT_RAW) # when actual_content = service.lookup_content( - 'sha1:18d8be353ed3480476f032475e7c233eff7371d5') + 'sha1:%s' % self.SHA1_SAMPLE) # then - self.assertEqual(actual_content, { - 'sha1': '18d8be353ed3480476f032475e7c233eff7371d5', - 'sha256': '39007420ca5de7cb3cfc15196335507ee76c98930e7e0afa4d274' - '7d3bf96c926', - 'sha1_git': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', - 'length': 190, - 'status': 'absent', - }) + self.assertEqual(actual_content, self.SAMPLE_CONTENT) mock_backend.content_find.assert_called_with( - 'sha1', hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5')) + 'sha1', hex_to_hash(self.SHA1_SAMPLE)) @patch('swh.web.ui.service.backend') @istest def lookup_content_with_sha256(self, mock_backend): # given - mock_backend.content_find = MagicMock(return_value={ - 'sha1': hex_to_hash('18d8be353ed3480476f032475e7c233eff7371d5'), - 'sha256': hex_to_hash('39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926'), - 'sha1_git': hex_to_hash('40e71b8614fcd89ccd17ca2b1d9e66' - 'c5b00a6d03'), - 'length': 360, - 'status': 'visible', - }) + stub_content = self.SAMPLE_CONTENT_RAW + stub_content['status'] = 'visible' + + expected_content = self.SAMPLE_CONTENT + expected_content['status'] = 'visible' + mock_backend.content_find = MagicMock( + return_value=stub_content) # when actual_content = service.lookup_content( - 'sha256:39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926') + 'sha256:%s' % self.SHA256_SAMPLE) # then - self.assertEqual(actual_content, { - 'sha1': '18d8be353ed3480476f032475e7c233eff7371d5', - 'sha256': '39007420ca5de7cb3cfc15196335507ee76c98930e7e0afa4d274' - '7d3bf96c926', - 'sha1_git': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', - 'length': 360, - 'status': 'visible', - }) + self.assertEqual(actual_content, expected_content) mock_backend.content_find.assert_called_with( - 'sha256', hex_to_hash('39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926')) + 'sha256', self.SHA256_SAMPLE_BIN) @patch('swh.web.ui.service.backend') @istest @@ -1697,27 +1435,22 @@ # given stub_dir_entries = [{ - 'sha1': hex_to_hash('5c6f0e2750f48fa0bd0c4cf5976ba0b9e0' - '2ebda5'), - 'sha256': hex_to_hash('39007420ca5de7cb3cfc15196335507e' - 'e76c98930e7e0afa4d2747d3bf96c926'), - 'sha1_git': hex_to_hash('40e71b8614fcd89ccd17ca2b1d9e66' - 'c5b00a6d03'), + 'sha1': self.SHA1_SAMPLE_BIN, + 'sha256': self.SHA256_SAMPLE_BIN, + 'sha1_git': self.SHA1GIT_SAMPLE_BIN, 'target': hex_to_hash('40e71b8614fcd89ccd17ca2b1d9e66' 'c5b00a6d03'), - 'dir_id': hex_to_hash('40e71b8614fcd89ccd17ca2b1d9e66' - 'c5b00a6d03'), + 'dir_id': self.DIRECTORY_ID_BIN, 'name': b'bob', 'type': 10, }] expected_dir_entries = [{ - 'sha1': '5c6f0e2750f48fa0bd0c4cf5976ba0b9e02ebda5', - 'sha256': '39007420ca5de7cb3cfc15196335507ee76c98930e7e0afa4d2747' - 'd3bf96c926', - 'sha1_git': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', + 'sha1': self.SHA1_SAMPLE, + 'sha256': self.SHA256_SAMPLE, + 'sha1_git': self.SHA1GIT_SAMPLE, 'target': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', - 'dir_id': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', + 'dir_id': self.DIRECTORY_ID, 'name': 'bob', 'type': 10, }] @@ -1754,50 +1487,56 @@ @istest def lookup_revision_by(self, mock_backend): # given - stub_rev = { - 'id': hex_to_hash('28d8be353ed3480476f032475e7c233eff7371d5'), - 'directory': hex_to_hash( - '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), - 'author': { - 'name': b'ynot', - 'email': b'ynot@blah.org', - }, - 'committer': { - 'name': b'ynot', - 'email': b'ynot@blah.org', - }, - 'message': b'elegant solution 31415', - 'date': { - 'timestamp': datetime.datetime( - 2016, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc).timestamp(), - 'offset': 420, - 'negative_utc': None, - }, - 'committer_date': { - 'timestamp': datetime.datetime( - 2016, 1, 17, 11, 23, 54, - tzinfo=datetime.timezone.utc).timestamp(), - 'offset': 420, - 'negative_utc': None, - }, - } + stub_rev = self.SAMPLE_REVISION_RAW - expected_rev = { - 'id': '28d8be353ed3480476f032475e7c233eff7371d5', - 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', - 'author': { - 'name': 'ynot', - 'email': 'ynot@blah.org', - }, - 'committer': { - 'name': 'ynot', - 'email': 'ynot@blah.org', - }, - 'message': 'elegant solution 31415', - 'date': '2016-01-17T18:23:54+07:00', - 'committer_date': '2016-01-17T18:23:54+07:00', - } + expected_rev = self.SAMPLE_REVISION + + mock_backend.revision_get_by.return_value = stub_rev + + # when + actual_revision = service.lookup_revision_by(10, 'master2', 'some-ts') + + # then + self.assertEquals(actual_revision, expected_rev) + + mock_backend.revision_get_by(1, 'master2', 'some-ts') + + @patch('swh.web.ui.service.backend') + @istest + def lookup_revision_by_nomerge(self, mock_backend): + # given + stub_rev = self.SAMPLE_REVISION_RAW + stub_rev['parents'] = [ + hex_to_hash('adc83b19e793491b1c6ea0fd8b46cd9f32e592fc')] + + expected_rev = self.SAMPLE_REVISION + expected_rev['parents'] = ['adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'] + mock_backend.revision_get_by.return_value = stub_rev + + # when + actual_revision = service.lookup_revision_by(10, 'master2', 'some-ts') + + # then + self.assertEquals(actual_revision, expected_rev) + + mock_backend.revision_get_by(1, 'master2', 'some-ts') + + @patch('swh.web.ui.service.backend') + @istest + def lookup_revision_by_merge(self, mock_backend): + # given + stub_rev = self.SAMPLE_REVISION_RAW + stub_rev['parents'] = [ + hex_to_hash('adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'), + hex_to_hash('ffff3b19e793491b1c6db0fd8b46cd9f32e592fc') + ] + + expected_rev = self.SAMPLE_REVISION + expected_rev['parents'] = [ + 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc', + 'ffff3b19e793491b1c6db0fd8b46cd9f32e592fc' + ] + expected_rev['merge'] = True mock_backend.revision_get_by.return_value = stub_rev diff --git a/swh/web/ui/tests/views/test_api.py b/swh/web/ui/tests/views/test_api.py --- a/swh/web/ui/tests/views/test_api.py +++ b/swh/web/ui/tests/views/test_api.py @@ -1443,10 +1443,10 @@ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'url': '/api/1/revision/18d8be353ed3480476f032475e7c233eff7371d5/', 'history_url': '/api/1/revision/18d8be353ed3480476f032475e7c233ef' - 'f7371d5/log/', + 'f7371d5/log/', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory_url': '/api/1/directory/7834ef7e7c357ce2af928115c6c6a' - '42b7e2a44e6/', + '42b7e2a44e6/', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', @@ -1465,6 +1465,11 @@ 'synthetic': True, }] + expected_response = { + 'revisions': expected_revisions, + 'next_revs_url': None + } + # when rv = self.app.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6a42' 'b7e2a44e6/log/') @@ -1474,10 +1479,44 @@ self.assertEquals(rv.mimetype, 'application/json') response_data = json.loads(rv.data.decode('utf-8')) - self.assertEquals(response_data, expected_revisions) + self.assertEquals(response_data, expected_response) mock_service.lookup_revision_log.assert_called_once_with( - '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 100) + '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 26) + + @patch('swh.web.ui.views.api.service') + @istest + def api_revision_log_with_next(self, mock_service): + # given + stub_revisions = [] + for i in range(27): + stub_revisions.append({'id': i}) + + mock_service.lookup_revision_log.return_value = stub_revisions[:26] + + expected_revisions = [x for x in stub_revisions if x['id'] < 25] + for e in expected_revisions: + e['url'] = '/api/1/revision/%s/' % e['id'] + e['history_url'] = '/api/1/revision/%s/log/' % e['id'] + + expected_response = { + 'revisions': expected_revisions, + 'next_revs_url': '/api/1/revision/25/log/' + } + + # when + rv = self.app.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6a42' + 'b7e2a44e6/log/') + + # then + self.assertEquals(rv.status_code, 200) + self.assertEquals(rv.mimetype, 'application/json') + + response_data = json.loads(rv.data.decode('utf-8')) + self.assertEquals(response_data, expected_response) + + mock_service.lookup_revision_log.assert_called_once_with( + '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 26) @patch('swh.web.ui.views.api.service') @istest @@ -1487,7 +1526,7 @@ # when rv = self.app.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6a42b7' - 'e2a44e6/log/?limit=10') + 'e2a44e6/log/') # then self.assertEquals(rv.status_code, 404) @@ -1499,7 +1538,7 @@ ' 8834ef7e7c357ce2af928115c6c6a42b7e2a44e6 not found.'}) mock_service.lookup_revision_log.assert_called_once_with( - '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 10) + '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 26) @patch('swh.web.ui.views.api.service') @istest @@ -1536,15 +1575,7 @@ 'synthetic': True, }] - # when - rv = self.app.get('/api/1/revision/18d8be353ed3480476f0' - '32475e7c233eff7371d5/prev/prev-rev/log/') - - # then - self.assertEquals(rv.status_code, 200) - self.assertEquals(rv.mimetype, 'application/json') - response_data = json.loads(rv.data.decode('utf-8')) - self.assertEquals(response_data, [ + expected_revisions = [ { 'url': '/api/1/revision/' '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6/', @@ -1592,10 +1623,25 @@ ], 'type': 'tar', 'synthetic': True, - }]) + }] + + expected_response = { + 'revisions': expected_revisions, + 'next_revs_url': None + } + + # when + rv = self.app.get('/api/1/revision/18d8be353ed3480476f0' + '32475e7c233eff7371d5/prev/prev-rev/log/') + + # then + self.assertEquals(rv.status_code, 200) + self.assertEquals(rv.mimetype, 'application/json') + response_data = json.loads(rv.data.decode('utf-8')) + self.assertEquals(response_data, expected_response) mock_service.lookup_revision_log.assert_called_once_with( - '18d8be353ed3480476f032475e7c233eff7371d5', 100) + '18d8be353ed3480476f032475e7c233eff7371d5', 26) mock_service.lookup_revision_multiple.assert_called_once_with( ['prev-rev']) diff --git a/swh/web/ui/tests/views/test_browse.py b/swh/web/ui/tests/views/test_browse.py --- a/swh/web/ui/tests/views/test_browse.py +++ b/swh/web/ui/tests/views/test_browse.py @@ -913,27 +913,30 @@ @istest def browse_revision_log(self, mock_api): # given - stub_revisions = [{ - 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', - 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', - 'committer': { - 'email': 'torvalds@linux-foundation.org', - 'name': 'Linus Torvalds' - }, - 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', - 'type': 'git', - 'author': { - 'email': 'torvalds@linux-foundation.org', - 'name': 'Linus Torvalds' - }, - 'message': 'Linux 4.2-rc1\n', - 'synthetic': False, - 'directory_url': '/api/1/directory/' - '2a1dbabeed4dcf1f4a4c441993b2ffc9d972780b/', - 'parent_url': [ - '/api/1/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' - ], - }] + stub_revisions = { + 'revisions': [{ + 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', + 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', + 'committer': { + 'email': 'torvalds@linux-foundation.org', + 'name': 'Linus Torvalds' + }, + 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', + 'type': 'git', + 'author': { + 'email': 'torvalds@linux-foundation.org', + 'name': 'Linus Torvalds' + }, + 'message': 'Linux 4.2-rc1\n', + 'synthetic': False, + 'directory_url': '/api/1/directory/' + '2a1dbabeed4dcf1f4a4c441993b2ffc9d972780b/', + 'parent_url': [ + '/api/1/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' + ], + }], + 'next_revs_url': '/api/1/revision/1234/log/' + } mock_api.api_revision_log.return_value = stub_revisions # when @@ -945,6 +948,9 @@ self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertTrue( isinstance(self.get_context_variable('revisions'), map)) + self.assertEqual( + self.get_context_variable('next_revs_url'), + '/browse/revision/1234/log/') self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision_log.assert_called_once_with('426', None) diff --git a/swh/web/ui/views/api.py b/swh/web/ui/views/api.py --- a/swh/web/ui/views/api.py +++ b/swh/web/ui/views/api.py @@ -705,7 +705,7 @@ @app.route('/api/1/revision//log/') @app.route('/api/1/revision//prev//log/') -def api_revision_log(sha1_git, prev_sha1s=None): +def api_revision_log(sha1_git, prev_sha1s=None, limit=25): """Show all revisions (~git log) starting from sha1_git. The first element returned is the given sha1_git. @@ -724,26 +724,42 @@ NotFoundExc if the revision is not found. """ - limit = int(request.args.get('limit', '100')) - def lookup_revision_log_with_limit(s, limit=limit): + response = {'revisions': None, 'next_revs_url': None} + revisions = None + next_revs_url = None + + def lookup_revision_log_with_limit(s, limit=limit+1): return service.lookup_revision_log(s, limit) error_msg = 'Revision with sha1_git %s not found.' % sha1_git - rev_backward = _api_lookup(sha1_git, - lookup_fn=lookup_revision_log_with_limit, - error_msg_if_not_found=error_msg, - enrich_fn=utils.enrich_revision) + rev_get = _api_lookup(sha1_git, + lookup_fn=lookup_revision_log_with_limit, + error_msg_if_not_found=error_msg, + enrich_fn=utils.enrich_revision) + + if len(rev_get) == limit+1: + rev_backward = rev_get[:-1] + next_revs_url = url_for('api_revision_log', + sha1_git=rev_get[-1]['id']) + else: + rev_backward = rev_get if not prev_sha1s: # no nav breadcrumbs, so we're done - return rev_backward - - rev_forward_ids = prev_sha1s.split('/') - rev_forward = _api_lookup(rev_forward_ids, - lookup_fn=service.lookup_revision_multiple, - error_msg_if_not_found=error_msg, - enrich_fn=utils.enrich_revision) - return rev_forward + rev_backward + revisions = rev_backward + + else: + rev_forward_ids = prev_sha1s.split('/') + rev_forward = _api_lookup(rev_forward_ids, + lookup_fn=service.lookup_revision_multiple, + error_msg_if_not_found=error_msg, + enrich_fn=utils.enrich_revision) + revisions = rev_forward + rev_backward + + response['revisions'] = revisions + response['next_revs_url'] = next_revs_url + + return response @app.route('/api/1/revision' diff --git a/swh/web/ui/views/browse.py b/swh/web/ui/views/browse.py --- a/swh/web/ui/views/browse.py +++ b/swh/web/ui/views/browse.py @@ -364,8 +364,12 @@ 'revisions': []} try: - revisions = api.api_revision_log(sha1_git, prev_sha1s) + revision_data = api.api_revision_log(sha1_git, prev_sha1s) + revisions = revision_data['revisions'] + next_revs_url = revision_data['next_revs_url'] env['revisions'] = map(utils.prepare_data_for_view, revisions) + env['next_revs_url'] = utils.prepare_data_for_view(next_revs_url) + except (NotFoundExc, BadInputExc) as e: env['message'] = str(e)