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/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 @@ -2,134 +2,149 @@ {% block title %}Revision Log{% endblock %} {% block content %} + {% if message is not none %} - {{ message }} +{{ 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
+ +
+ {% endif %} + + {% if revision['history_context_url'] is not none %} +
+
Contextual Revision Log
+ +
+ {% endif %} + + {% if revision['directory_url'] is not none %} + + {% endif %} - {% if revision['history_url'] is not none %} -
-
Revision Log
- + {% 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['message'] is not none %} +
+
Message
+
{{ revision['message'] }}
+
+ {% elif revision['message_encoding_failed'] %} +
+
Message
+ +
+ {% else %} +
+
Message
+
No message found.
+
+ {% 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 %} + {% 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['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 ['children_urls', 'parent_urls'] %} - {% 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 %} - {% endfor %} - {% for key in ['parent_urls', 'children_urls'] %} - {% if revision[key] is not none %} -
-
{{ key }}
- {% for link in revision[key] %} - - {% endfor %} -
- {% endif %} + {% if revision[key] is not none %} +
+
{{ key }}
+ {% for link in revision[key] %} + {% 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 %} - + {% 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,195 @@ '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'), + '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', + 'parents': [ + hashutil.hex_to_hash( + '29d8be353ed3480476f032475e7c244eff7371d5') + ], + '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' + ], + 'parents': [ + '29d8be353ed3480476f032475e7c244eff7371d5' + ], + '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' + }] + }, + '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 +634,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 @@ -574,6 +574,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 +647,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 @@ -1010,6 +1012,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False }) mock_backend.revision_get.assert_called_with( @@ -1079,6 +1082,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False }) mock_backend.revision_get.assert_called_with( @@ -1305,6 +1309,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False }, { 'id': sha1_other, @@ -1326,6 +1331,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False } ]) @@ -1421,6 +1427,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False }]) mock_backend.revision_log.assert_called_with( @@ -1489,6 +1496,7 @@ 'type': 'git', 'parents': [], 'metadata': [], + 'merge': False }]) mock_backend.revision_log_by.assert_called_with( @@ -1811,6 +1819,83 @@ @patch('swh.web.ui.service.backend') @istest + def lookup_revision_by_nomerge(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', + }, + 'parents': [ + hex_to_hash('adc83b19e793491b1c6ea0fd8b46cd9f32e592fc')] + } + + expected_rev = { + 'id': '28d8be353ed3480476f032475e7c233eff7371d5', + 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', + 'author': { + 'name': 'ynot', + 'email': 'ynot@blah.org', + }, + 'parents': ['adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'], + 'merge': False + } + + 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 = { + 'id': hex_to_hash('28d8be353ed3480476f032475e7c233eff7371d5'), + 'directory': hex_to_hash( + '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6'), + 'author': { + 'name': b'ynot', + 'email': b'ynot@blah.org', + }, + 'parents': [ + hex_to_hash('adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'), + hex_to_hash('adc83b19e793491b1c6db0fd8b46cd9f32e592fc') + ] + } + + expected_rev = { + 'id': '28d8be353ed3480476f032475e7c233eff7371d5', + 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', + 'author': { + 'name': 'ynot', + 'email': 'ynot@blah.org', + }, + 'parents': ['adc83b19e793491b1c6ea0fd8b46cd9f32e592fc', + 'adc83b19e793491b1c6db0fd8b46cd9f32e592fc'], + 'merge': True + } + + 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_with_context_by_ko(self, mock_backend): # given mock_backend.revision_get_by.return_value = None