diff --git a/assets/src/bundles/browse/browse-utils.js b/assets/src/bundles/browse/browse-utils.js index ce3ee780..b739142b 100644 --- a/assets/src/bundles/browse/browse-utils.js +++ b/assets/src/bundles/browse/browse-utils.js @@ -1,94 +1,95 @@ /** * Copyright (C) 2018-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import {BREAKPOINT_SM} from 'utils/constants'; $(document).ready(() => { $('.dropdown-submenu a.dropdown-item').on('click', e => { $(e.target).next('div').toggle(); if ($(e.target).next('div').css('display') !== 'none') { $(e.target).focus(); } else { $(e.target).blur(); } e.stopPropagation(); e.preventDefault(); }); $('.swh-popover-toggler').popover({ boundary: 'viewport', container: 'body', html: true, placement: function() { const width = $(window).width(); if (width < BREAKPOINT_SM) { return 'top'; } else { return 'right'; } }, template: ``, content: function() { var content = $(this).attr('data-popover-content'); return $(content).children('.popover-body').remove().html(); }, title: function() { var title = $(this).attr('data-popover-content'); return $(title).children('.popover-heading').html(); }, offset: '50vh', sanitize: false }); $('.swh-vault-menu a.dropdown-item').on('click', e => { $('.swh-popover-toggler').popover('hide'); }); $('.swh-popover-toggler').on('show.bs.popover', (e) => { $(`.swh-popover-toggler:not(#${e.currentTarget.id})`).popover('hide'); $('.swh-vault-menu .dropdown-menu').hide(); }); $('.swh-actions-dropdown').on('hide.bs.dropdown', () => { $('.swh-vault-menu .dropdown-menu').hide(); $('.swh-popover-toggler').popover('hide'); }); $('#swh-branch-search-form').submit(function(e) { var searchParams = new URLSearchParams(window.location.search); searchParams.set('name_include', $('#swh-branch-search-string').val().trim()); window.location.search = searchParams.toString(); e.preventDefault(); }); $('body').on('click', e => { if ($(e.target).parents('.swh-popover').length) { e.stopPropagation(); } }); }); export function initBrowseNavbar() { if (window.location.pathname === Urls.browse_origin_visits()) { $('#swh-browse-origin-visits-nav-link').addClass('active'); } else if (window.location.pathname === Urls.browse_origin_branches() || - window.location.pathname === Urls.browse_snapshot_branches()) { + window.location.pathname === Urls.browse_snapshot_branches() || + window.location.pathname.endsWith('branches/')) { $('#swh-browse-snapshot-branches-nav-link').addClass('active'); } else if (window.location.pathname === Urls.browse_origin_releases() || window.location.pathname === Urls.browse_snapshot_releases()) { $('#swh-browse-snapshot-releases-nav-link').addClass('active'); } else { $('#swh-browse-code-nav-link').addClass('active'); } } diff --git a/cypress/integration/origin-browse.spec.js b/cypress/integration/origin-browse.spec.js index 6564fe6c..5a52d94f 100644 --- a/cypress/integration/origin-browse.spec.js +++ b/cypress/integration/origin-browse.spec.js @@ -1,168 +1,168 @@ /** * Copyright (C) 2020-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ describe('Test origin browse', function() { beforeEach(function() { const url = `${this.Urls.browse_origin()}?origin_url=${this.origin[1].url}`; cy.visit(url); }); it('should have code tab active by default', function() { cy.get('#swh-browse-code-nav-link') .should('have.class', 'active'); }); it('should load branches view when clicking on the Branches tab', function() { cy.get('#swh-browse-snapshot-branches-nav-link') .click(); cy.location('pathname') - .should('eq', this.Urls.browse_origin_branches()); + .should('eq', this.Urls.browse_snapshot_branches(this.origin[1].snapshot)); cy.location('search') .should('eq', `?origin_url=${this.origin[1].url}`); cy.get('#swh-browse-snapshot-branches-nav-link') .should('have.class', 'active'); }); it('should load releases view when clicking on the Releases tab', function() { cy.get('#swh-browse-snapshot-releases-nav-link') .click(); cy.location('pathname') .should('eq', this.Urls.browse_origin_releases()); cy.location('search') .should('eq', `?origin_url=${this.origin[1].url}`); cy.get('#swh-browse-snapshot-releases-nav-link') .should('have.class', 'active'); }); it('should load visits view when clicking on the Visits tab', function() { cy.get('#swh-browse-origin-visits-nav-link') .click(); cy.location('pathname') .should('eq', this.Urls.browse_origin_visits()); cy.location('search') .should('eq', `?origin_url=${this.origin[1].url}`); cy.get('#swh-browse-origin-visits-nav-link') .should('have.class', 'active'); }); it('should load code view when clicking on the Code tab', function() { cy.get('#swh-browse-origin-visits-nav-link') .click(); cy.get('#swh-browse-code-nav-link') .click(); cy.location('pathname') .should('eq', this.Urls.browse_origin_directory()); cy.location('search') .should('eq', `?origin_url=${this.origin[1].url}`); cy.get('#swh-browse-code-nav-link') .should('have.class', 'active'); }); it('should have Releases tab link disabled when there is no releases', function() { const url = `${this.Urls.browse_origin()}?origin_url=${this.origin[0].url}`; cy.visit(url); cy.get('#swh-browse-snapshot-releases-nav-link') .should('have.class', 'disabled'); }); }); describe('Test browse branches', function() { beforeEach(function() { const url = `${this.Urls.browse_origin_branches()}?origin_url=${this.origin[1].url}`; cy.visit(url); }); it('should have the master branch in the list', function() { cy.get('table').contains('td', 'master').should('be.visible'); }); it('should search inside the branches', function() { cy.get('#swh-branch-search-string').type('mas'); cy.get('#swh-branch-serach-button').click(); cy.location('search') .should('include', 'name_include=mas'); cy.get('table').contains('td', 'master').should('be.visible'); cy.get('#swh-branch-search-string').should('have.value', 'mas'); }); it('should show all the branches for empty search', function() { cy.get('#swh-branch-search-string').clear(); cy.get('#swh-branch-serach-button').click(); cy.location('search') .should('include', 'name_include='); cy.get('table').contains('td', 'master').should('be.visible'); cy.get('#swh-branch-search-string').should('have.value', ''); }); it('should show no branch exists message on failed search', function() { cy.get('#swh-branch-search-string').type('random{enter}'); cy.get('table').contains('td', 'No branch names containing random have been found!').should('be.visible'); }); }); describe('Test browse releases', function() { beforeEach(function() { const url = `${this.Urls.browse_origin_releases()}?origin_url=${this.origin[1].url}`; cy.visit(url); }); it('should have the v2 release in the list', function() { cy.get('table').contains('td', 'v2.0').should('be.visible'); }); it('should search inside the releases', function() { cy.get('#swh-branch-search-string').type('v2.4'); cy.get('#swh-branch-serach-button').click(); cy.location('search') .should('include', 'name_include=v2.4'); cy.get('table').contains('td', 'v2.4').should('be.visible'); cy.get('#swh-branch-search-string').should('have.value', 'v2.4'); }); it('should show all the releases for empty search', function() { cy.get('#swh-branch-search-string').clear(); cy.get('#swh-branch-serach-button').click(); cy.location('search') .should('include', 'name_include='); cy.get('table').contains('td', 'v2.0').should('be.visible'); cy.get('#swh-branch-search-string').should('have.value', ''); }); it('should show no release exists message on failed search', function() { cy.get('#swh-branch-search-string').type('random{enter}'); cy.get('table').contains('td', 'No release names containing random have been found!').should('be.visible'); }); }); diff --git a/docs/uri-scheme-browse-origin.rst b/docs/uri-scheme-browse-origin.rst index 6104b616..51086626 100644 --- a/docs/uri-scheme-browse-origin.rst +++ b/docs/uri-scheme-browse-origin.rst @@ -1,852 +1,856 @@ Origin ^^^^^^ This describes the URI scheme when one wants to browse the Software Heritage archive in the context of an origin (for instance, a repository crawled from GitHub or a Debian source package). All the views pointed by that scheme offer quick links to browse objects as found during the associated crawls performed by Software Heritage: * the root directory of the origin * the list of branches of the origin * the list of releases of the origin Origin visits """"""""""""" .. http:get:: /browse/origin/visits/ HTML view that displays visits reporting for a software origin identified by its type and url. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/visits/?origin_url=https://github.com/torvalds/linux` :swh_web_browse:`origin/visits/?origin_url=https://github.com/python/cpython` :swh_web_browse:`origin/visits/?origin_url=deb://Debian-Security/packages/mediawiki` :swh_web_browse:`origin/visits/?origin_url=https://gitorious.org/qt/qtbase.git` .. http:get:: /browse/origin/(origin_url)/visits/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/visits/` instead. HTML view that displays a visits reporting for a software origin identified by its type and url. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/torvalds/linux/visits/` :swh_web_browse:`origin/https://github.com/python/cpython/visits/` :swh_web_browse:`origin/deb://Debian-Security/packages/mediawiki/visits/` :swh_web_browse:`origin/https://gitorious.org/qt/qtbase.git/visits/` Origin directory """""""""""""""" .. http:get:: /browse/origin/directory/ HTML view for browsing the content of a directory reachable from the root directory (including itself) associated to the latest full visit of a software origin. The content of the directory is first sorted in lexicographical order and the sub-directories are displayed before the regular files. The view enables to navigate from the requested directory to directories reachable from it in a recursive way but also up to the origin root directory. A breadcrumb located in the top part of the view allows to keep track of the paths navigated so far. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the directory content can also be specified by using the branch query parameter. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query string path: optional parameter used to specify the path of a directory reachable from the origin root one :query string branch: specify the origin branch name from which to retrieve the root directory :query string release: specify the origin release name from which to retrieve the root directory :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the root directory :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int visit_id: specify a visit id to retrieve the directory from instead of using the latest full visit by default :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive or the provided path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/directory/?origin_url=https://github.com/torvalds/linux` :swh_web_browse:`origin/directory/?origin_url=https://github.com/torvalds/linux&path=net/ethernet` :swh_web_browse:`origin/directory/?origin_url=https://github.com/python/cpython` :swh_web_browse:`origin/directory/?origin_url=https://github.com/python/cpython&path=Python` :swh_web_browse:`origin/directory/?origin_url=https://github.com/python/cpython&branch=refs/heads/2.7` :swh_web_browse:`origin/directory/?origin_url=https://github.com/torvalds/linux&path=net/ethernet×tamp=2016-09-14T10:36:21Z` :swh_web_browse:`origin/directory/?origin_url=https://github.com/python/cpython&path=Python×tamp=2017-05-05` :swh_web_browse:`origin/directory/?origin_url=https://github.com/python/cpython&branch=refs/heads/2.7×tamp=2015-08` .. http:get:: /browse/origin/(origin_url)/directory/[(path)/] :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/directory/` instead. HTML view for browsing the content of a directory reachable from the root directory (including itself) associated to the latest full visit of a software origin. The content of the directory is first sorted in lexicographical order and the sub-directories are displayed before the regular files. The view enables to navigate from the requested directory to directories reachable from it in a recursive way but also up to the origin root directory. A breadcrumb located in the top part of the view allows to keep track of the paths navigated so far. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the directory content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string path: optional parameter used to specify the path of a directory reachable from the origin root one :query string branch: specify the origin branch name from which to retrieve the root directory :query string release: specify the origin release name from which to retrieve the root directory :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the root directory :query int visit_id: specify a visit id to retrieve the directory from instead of using the latest full visit by default :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive or the provided path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/torvalds/linux/directory/` :swh_web_browse:`origin/https://github.com/torvalds/linux/directory/net/ethernet/` :swh_web_browse:`origin/https://github.com/python/cpython/directory/` :swh_web_browse:`origin/https://github.com/python/cpython/directory/Python/` :swh_web_browse:`origin/https://github.com/python/cpython/directory/?branch=refs/heads/2.7` .. http:get:: /browse/origin/(origin_url)/visit/(timestamp)/directory/[(path)/] :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/directory/` instead. HTML view for browsing the content of a directory reachable from the root directory (including itself) associated to a visit of a software origin closest to a provided timestamp. The content of the directory is first sorted in lexicographical order and the sub-directories are displayed before the regular files. The view enables to navigate from the requested directory to directories reachable from it in a recursive way but also up to the origin root directory. A breadcrumb located in the top part of the view allows to keep track of the paths navigated so far. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the directory content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :param path: optional parameter used to specify the path of a directory reachable from the origin root one :type path: string :query string branch: specify the origin branch name from which to retrieve the root directory :query string release: specify the origin release name from which to retrieve the root directory :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the directory :query int visit_id: specify a visit id to retrieve the directory from instead of using the provided timestamp :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive, requested visit timestamp does not exist or the provided path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/torvalds/linux/visit/1493926809/directory/` :swh_web_browse:`origin/https://github.com/torvalds/linux/visit/2016-09-14T10:36:21Z/directory/net/ethernet/` :swh_web_browse:`origin/https://github.com/python/cpython/visit/1474620651/directory/` :swh_web_browse:`origin/https://github.com/python/cpython/visit/2017-05-05/directory/Python/` :swh_web_browse:`origin/https://github.com/python/cpython/visit/2015-08/directory/?branch=refs/heads/2.7` Origin content """""""""""""" .. http:get:: /browse/origin/content/ HTML view that produces a display of a content associated to the latest full visit of a software origin. If the content to display is textual, it will be highlighted client-side if possible using highlightjs_. The procedure to perform that task is described in :http:get:`/browse/content/[(algo_hash):](hash)/`. It is also possible to highlight specific lines of a textual content (not in terms of syntax highlighting but to emphasize some relevant content part) by either: * clicking on line numbers (holding shift to highlight a lines range) * using an url fragment in the form '#Ln' or '#Lm-Ln' The view displays a breadcrumb on top of the rendered content in order to easily navigate up to the origin root directory. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query string path: path of a content reachable from the origin root directory :query string branch: specify the origin branch name from which to retrieve the content :query string release: specify the origin release name from which to retrieve the content :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int visit_id: specify a visit id to retrieve the content from instead of using the latest full visit by default :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive, or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/content/?origin_url=https://github.com/git/git?path=git.c` :swh_web_browse:`origin/content/?origin_url=https://github.com/mozilla/gecko-dev&path=js/src/json.cpp` :swh_web_browse:`origin/content/?origin_url=https://github.com/git/git?path=git.c&branch=refs/heads/next` :swh_web_browse:`origin/content/?origin_url=https://github.com/git/git&path=git.c×tamp=2016-05-05T00:0:00+00:00Z` :swh_web_browse:`origin/content/?origin_url=https://github.com/mozilla/gecko-dev&path=js/src/json.cpp×tamp=2017-03-21#L904-L931` :swh_web_browse:`origin/content/?origin_url=https://github.com/git/git&path=git.c&branch=refs/heads/next×tamp=2017-09-15` .. http:get:: /browse/origin/(origin_url)/content/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/content/` instead. HTML view that produces a display of a content associated to the latest full visit of a software origin. If the content to display is textual, it will be highlighted client-side if possible using highlightjs_. The procedure to perform that task is described in :http:get:`/browse/content/[(algo_hash):](hash)/`. It is also possible to highlight specific lines of a textual content (not in terms of syntax highlighting but to emphasize some relevant content part) by either: * clicking on line numbers (holding shift to highlight a lines range) * using an url fragment in the form '#Ln' or '#Lm-Ln' The view displays a breadcrumb on top of the rendered content in order to easily navigate up to the origin root directory. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :query string path: path of a content reachable from the origin root directory :query string branch: specify the origin branch name from which to retrieve the content :query string release: specify the origin release name from which to retrieve the content :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int visit_id: specify a visit id to retrieve the content from instead of using the latest full visit by default :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive, or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/git/git/content/?path=git.c` :swh_web_browse:`origin/https://github.com/mozilla/gecko-dev/content/?path=js/src/json.cpp` :swh_web_browse:`origin/https://github.com/git/git/content/?path=git.c&branch=refs/heads/next` :swh_web_browse:`origin/https://github.com/git/git/content/?path=git.c×tamp=2016-05-05T00:0:00+00:00Z` :swh_web_browse:`origin/https://github.com/mozilla/gecko-dev/content?path=js/src/json.cpp×tamp=2017-03-21#L904-L931` :swh_web_browse:`origin/https://github.com/git/git/content/git.c/?branch=refs/heads/next×tamp=2017-09-15` .. http:get:: /browse/origin/(origin_url)/content/(path)/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/content/` instead. HTML view that produces a display of a content associated to the latest full visit of a software origin. If the content to display is textual, it will be highlighted client-side if possible using highlightjs_. The procedure to perform that task is described in :http:get:`/browse/content/[(algo_hash):](hash)/`. It is also possible to highlight specific lines of a textual content (not in terms of syntax highlighting but to emphasize some relevant content part) by either: * clicking on line numbers (holding shift to highlight a lines range) * using an url fragment in the form '#Ln' or '#Lm-Ln' The view displays a breadcrumb on top of the rendered content in order to easily navigate up to the origin root directory. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string path: path of a content reachable from the origin root directory :query string branch: specify the origin branch name from which to retrieve the content :query string release: specify the origin release name from which to retrieve the content :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :query int visit_id: specify a visit id to retrieve the content from instead of using the latest full visit by default :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive, or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/git/git/content/git.c/` :swh_web_browse:`origin/https://github.com/git/git/content/git.c/` :swh_web_browse:`origin/https://github.com/mozilla/gecko-dev/content/js/src/json.cpp/` :swh_web_browse:`origin/https://github.com/git/git/content/git.c/?branch=refs/heads/next` .. http:get:: /browse/origin/(origin_url)/visit/(timestamp)/content/(path)/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/content/` instead. HTML view that produces a display of a content associated to a visit of a software origin closest to a provided timestamp. If the content to display is textual, it will be highlighted client-side if possible using highlightjs_. The procedure to perform that task is described in :http:get:`/browse/content/[(algo_hash):](hash)/`. It is also possible to highlight specific lines of a textual content (not in terms of syntax highlighting but to emphasize some relevant content part) by either: * clicking on line numbers (holding shift to highlight a lines range) * using an url fragment in the form '#Ln' or '#Lm-Ln' The view displays a breadcrumb on top of the rendered content in order to easily navigate up to the origin root directory. The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :param string path: path of a content reachable from the origin root directory :query string branch: specify the origin branch name from which to retrieve the content :query string release: specify the origin release name from which to retrieve the content :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :query int visit_id: specify a visit id to retrieve the content from instead of using the provided timestamp :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive, requested visit timestamp does not exist or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/git/git/visit/2016-05-05T00:0:00+00:00Z/content/git.c/` :swh_web_browse:`origin/https://github.com/mozilla/gecko-dev/visit/2017-03-21/content/js/src/json.cpp/#L904-L931` :swh_web_browse:`origin/https://github.com/git/git/visit/2017-09-15/content/git.c/?branch=refs/heads/next` Origin history """""""""""""" .. http:get:: /browse/origin/log/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/snapshot/log/` instead. HTML view that produces a display of revisions history heading to the last revision found during the latest visit of a software origin. In other words, it shows the commit log associated to the latest full visit of a software origin. The following data are displayed for each log entry: * link to browse the associated revision in the origin context * author of the revision * date of the revision * message associated 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 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 than the ones currently displayed * **Older**: fetch and display if available the N older log entries than the ones currently displayed The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query int per_page: the number of log entries to display per page :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 to retrieve the commit log :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the commit log :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int visit_id: specify a visit id to retrieve the history log from instead of using the latest visit by default :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/log/?origin_url=https://github.com/videolan/vlc` :swh_web_browse:`origin/log/?origin_url=https://github.com/Kitware/CMake` :swh_web_browse:`origin/log/?origin_url=https://github.com/Kitware/CMake&branch=refs/heads/release` :swh_web_browse:`origin/log/?origin_url=https://github.com/videolan/vlc&visit=1459651262` :swh_web_browse:`origin/log/?origin_url=https://github.com/Kitware/CMake×tamp=2016-04-01` :swh_web_browse:`origin/log/?origin_url=https://github.com/Kitware/CMake&branch=refs/heads/release×tamp=1438116814` :swh_web_browse:`origin/log/?origin_url=https://github.com/Kitware/CMake&branch=refs/heads/release×tamp=2017-05-05T03:14:23Z` .. http:get:: /browse/origin/(origin_url)/log/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/snapshot/log/` instead. HTML view that produces a display of revisions history heading to the last revision found during the latest visit of a software origin. In other words, it shows the commit log associated to the latest full visit of a software origin. The following data are displayed for each log entry: * link to browse the associated revision in the origin context * author of the revision * date of the revision * message associated 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 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 than the ones currently displayed * **Older**: fetch and display if available the N older log entries than the ones currently displayed The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query int per_page: the number of log entries to display per page :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 to retrieve the commit log :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the commit log :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int visit_id: specify a visit id to retrieve the history log from instead of using the latest visit by default :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/videolan/vlc/log/` :swh_web_browse:`origin/https://github.com/Kitware/CMake/log/` :swh_web_browse:`origin/https://github.com/Kitware/CMake/log/?branch=refs/heads/release` :swh_web_browse:`origin/https://github.com/Kitware/CMake/log/?timestamp=2016-04-01` :swh_web_browse:`origin/https://github.com/Kitware/CMake/log/?branch=refs/heads/release×tamp=2017-05-05T03:14:23Z` .. http:get:: /browse/origin/(origin_url)/visit/(timestamp)/log/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/log/` instead. HTML view that produces a display of revisions history heading to the last revision found during a visit of a software origin closest to the provided timestamp. In other words, it shows the commit log associated to a visit of a software origin closest to a provided timestamp. The following data are displayed for each log entry: * 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 N log entries are displayed per page (default is 20). 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 than the ones currently displayed * **Older**: fetch and display if available the N older log entries than the ones currently displayed The view also enables to easily switch between the origin branches and releases through a dropdown menu. The origin branch (default to HEAD) from which to retrieve the content can also be specified by using the branch query parameter. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :query int per_page: the number of log entries to display per page (default is 20, max is 50) :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 to retrieve the commit log :query string revision: specify the origin revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the commit log :query int visit_id: specify a visit id to retrieve the history log from instead of using the provided timestamp :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/Kitware/CMake/visit/2016-04-01/log/` :swh_web_browse:`origin/https://github.com/Kitware/CMake/visit/2017-05-05T03:14:23Z/log/?branch=refs/heads/release` Origin branches """"""""""""""" .. http:get:: /browse/origin/branches/ + :deprecated: + + .. warning:: + That endpoint is deprecated, use :http:get:`/browse/snapshot/branches/` instead. HTML view that produces a display of the list of branches found during the latest full visit of a software origin. The following data are displayed for each branch: * its name * a link to browse the associated directory * a link to browse the associated revision * last commit message * last commit date That list of branches is paginated, each page displaying a maximum of 100 branches. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/branches/?origin_url=deb://Debian/packages/linux` :swh_web_browse:`origin/branches/?origin_url=https://github.com/webpack/webpack` :swh_web_browse:`origin/branches/?origin_url=https://github.com/kripken/emscripten×tamp=2017-05-05T12:02:03Z` :swh_web_browse:`origin/branches/?origin_url=deb://Debian/packages/apache2-mod-xforward×tamp=2017-11-15T05:15:09Z` .. http:get:: /browse/origin/(origin_url)/branches/ :deprecated: .. warning:: - That endpoint is deprecated, use :http:get:`/browse/origin/branches/` instead. + That endpoint is deprecated, use :http:get:`/browse/snapshot/branches/` instead. HTML view that produces a display of the list of branches found during the latest full visit of a software origin. The following data are displayed for each branch: * its name * a link to browse the associated directory * a link to browse the associated revision * last commit message * last commit date That list of branches is paginated, each page displaying a maximum of 100 branches. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/deb://Debian/packages/linux/branches/` :swh_web_browse:`origin/https://github.com/webpack/webpack/branches/` :swh_web_browse:`origin/https://github.com/kripken/emscripten/branches/?timestamp=2017-05-05T12:02:03Z` :swh_web_browse:`origin/deb://Debian/packages/apache2-mod-xforward/branches/?timestamp=2017-11-15T05:15:09` .. http:get:: /browse/origin/(origin_url)/visit/(timestamp)/branches/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/branches/` instead. HTML view that produces a display of the list of branches found during a visit of a software origin closest to the provided timestamp. The following data are displayed for each branch: * its name * a link to browse the associated directory * a link to browse the associated revision * last commit message * last commit date That list of branches is paginated, each page displaying a maximum of 100 branches. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/kripken/emscripten/visit/2017-05-05T12:02:03Z/branches/` :swh_web_browse:`origin/deb://Debian/packages/apache2-mod-xforward/visit/2017-11-15T05:15:09Z/branches/` Origin releases """"""""""""""" .. http:get:: /browse/origin/releases/ HTML view that produces a display of the list of releases found during the latest full visit of a software origin. The following data are displayed for each release: * its name * a link to browse the release details * its target type (revision, directory, content or release) * its associated message * its date That list of releases is paginated, each page displaying a maximum of 100 releases. :query string origin_url: mandatory parameter providing the url of the origin (e.g. https://github.com/(user)/(repo)) :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 400: no origin url has been provided as parameter :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/releases/?origin_url=https://github.com/git/git` :swh_web_browse:`origin/releases/?origin_url=https://github.com/webpack/webpack` :swh_web_browse:`origin/releases/?origin_url=https://github.com/torvalds/linux×tamp=2017-11-21T19:37:42Z` :swh_web_browse:`origin/releases/?origin_url=https://github.com/Kitware/CMake×tamp=2016-09-23T14:06:35Z` .. http:get:: /browse/origin/(origin_url)/releases/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/releases/` instead. HTML view that produces a display of the list of releases found during the latest full visit of a software origin. The following data are displayed for each release: * its name * a link to browse the release details * its target type (revision, directory, content or release) * its associated message * its date That list of releases is paginated, each page displaying a maximum of 100 releases. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :query string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/git/git/releases/` :swh_web_browse:`origin/https://github.com/webpack/webpack/releases/` :swh_web_browse:`origin/https://github.com/torvalds/linux/releases/?timestamp=2017-11-21T19:37:42Z` :swh_web_browse:`origin/https://github.com/Kitware/CMake/releases/?timestamp=2016-09-23T14:06:35Z` .. http:get:: /browse/origin/(origin_url)/visit/(timestamp)/releases/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/origin/releases/` instead. HTML view that produces a display of the list of releases found during a visit of a software origin closest to the provided timestamp. The following data are displayed for each release: * its name * a link to browse the release details * its target type (revision, directory, content or release) * its associated message * its date That list of releases is paginated, each page displaying a maximum of 100 releases. :param string origin_url: the url of the origin (e.g. https://github.com/(user)/(repo)/) :param string timestamp: an ISO 8601 datetime string to parse in order to find the closest visit. :statuscode 200: no error :statuscode 404: requested origin can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`origin/https://github.com/torvalds/linux/visit/2017-11-21T19:37:42Z/releases/` :swh_web_browse:`origin/https://github.com/Kitware/CMake/visit/2016-09-23T14:06:35Z/releases/` .. _highlightjs: https://highlightjs.org/ diff --git a/docs/uri-scheme-browse-snapshot.rst b/docs/uri-scheme-browse-snapshot.rst index 1fac6e34..c2037fee 100644 --- a/docs/uri-scheme-browse-snapshot.rst +++ b/docs/uri-scheme-browse-snapshot.rst @@ -1,279 +1,302 @@ Snapshot ^^^^^^^^ .. http:get:: /browse/snapshot/(snapshot_id)/ HTML view that displays the content of a snapshot from its identifier (see :func:`swh.model.git_objects.snapshot_git_object` in our data model module for details about how they are computed). A snapshot is a set of named branches, which are pointers to objects at any level of the Software Heritage DAG. It represents a full picture of an origin at a given time. Thus, multiple visits of different origins can point to the same snapshot (for instance, when several projects are forks of a common one). Currently, that endpoint simply performs a redirection to :http:get:`/browse/snapshot/(snapshot_id)/directory/` in order to display the root directory associated to the default snapshot branch (usually master). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/baebc2109e4a2ec22a1129a3859647e191d04df4/` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/` Snapshot directory """""""""""""""""" .. http:get:: /browse/snapshot/(snapshot_id)/directory/ HTML view that displays the content of a directory reachable from a snapshot. The features offered by the view are similar to the one for browsing a directory in an origin context (see :http:get:`/browse/origin/(origin_url)/directory/[(path)/]`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :query string path: optional parameter used to specify the path of a directory reachable from the snapshot root one :query string branch: specify the snapshot branch name from which to retrieve the root directory :query string release: specify the snapshot release name from which to retrieve the root directory :query string revision: specify the snapshot revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the root directory :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/baebc2109e4a2ec22a1129a3859647e191d04df4/directory/?path=drivers/gpu` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/directory/?path=src/opengl` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/directory/?release=v5.7.0` .. http:get:: /browse/snapshot/(snapshot_id)/directory/(path)/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/snapshot/(snapshot_id)/directory/` instead. HTML view that displays the content of a directory reachable from a snapshot. The features offered by the view are similar to the one for browsing a directory in an origin context (see :http:get:`/browse/origin/(origin_url)/directory/[(path)/]`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :param string path: optional parameter used to specify the path of a directory reachable from the snapshot root one :query string branch: specify the snapshot branch name from which to retrieve the root directory :query string release: specify the snapshot release name from which to retrieve the root directory :query string revision: specify the snapshot revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the root directory :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/baebc2109e4a2ec22a1129a3859647e191d04df4/directory/drivers/gpu/` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/directory/src/opengl/` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/directory/?release=v5.7.0` Snapshot content """""""""""""""" .. http:get:: /browse/snapshot/(snapshot_id)/content/ HTML view that produces a display of a content reachable from a snapshot. The features offered by the view are similar to the one for browsing a content in an origin context (see :http:get:`/browse/origin/(origin_url)/content/`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :query string path: path of a content reachable from the snapshot root directory :query string branch: specify the snapshot branch name from which to retrieve the content :query string release: specify the snapshot release name from which to retrieve the content :query string revision: specify the snapshot revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive, or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/baebc2109e4a2ec22a1129a3859647e191d04df4/content/?path=init/initramfs.c` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/content/?path=src/opengl/qglbuffer.h` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/content/?path=src/opengl/qglbuffer.h&?release=v5.0.0` .. http:get:: /browse/snapshot/(snapshot_id)/content/(path)/ :deprecated: .. warning:: That endpoint is deprecated, use :http:get:`/browse/snapshot/(snapshot_id)/content/` instead. HTML view that produces a display of a content reachable from a snapshot. The features offered by the view are similar to the one for browsing a content in an origin context (see :http:get:`/browse/origin/(origin_url)/content/(path)/`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :param string path: path of a content reachable from the snapshot root directory :query string branch: specify the snapshot branch name from which to retrieve the content :query string release: specify the snapshot release name from which to retrieve the content :query string revision: specify the snapshot revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the content :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive, or the provided content path does not exist from the origin root directory **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/baebc2109e4a2ec22a1129a3859647e191d04df4/content/init/initramfs.c` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/content/src/opengl/qglbuffer.h/` :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/content/src/opengl/qglbuffer.h/?release=v5.0.0` Snapshot history """""""""""""""" .. http:get:: /browse/snapshot/log/ HTML view that produces a display of the revisions history (aka the commit log) for the last collected revision for the given origin. An origin URL must be given as a query parameter. :query string origin_url: specify the origin from which to retrieve the commit log :query string timestamp: optional; an ISO 8601 datetime string to parse in order to find the closest visit :statuscode 200: no error :statuscode 400: origin_url parameter is missing :statuscode 404: requested origin is either missing or has no associated snapshots **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/log?origin_url=https://github.com/python/cpython` :swh_web_browse:`snapshot/log/?origin_url=https://github.com/python/cpython×tamp=2021-01-23T22:24:10Z` .. http:get:: /browse/snapshot/(snapshot_id)/log/ HTML view that produces a display of revisions history (aka the commit log) heading to the last revision collected in a snapshot. The features offered by the view are similar to the one for browsing the history in an origin context (see :http:get:`/browse/origin/(origin_url)/log/`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :query int per_page: the number of log entries to display per page (default is 20, max is 50) :query string branch: specify the snapshot branch name from which to retrieve the commit log :query string release: specify the snapshot release name from which to retrieve the commit log :query string revision: specify the snapshot revision, identified by the hexadecimal representation of its **sha1_git** value, from which to retrieve the commit log :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/a274b44111f777209556e94920b7e71cf5c305cd/log/` :swh_web_browse:`snapshot/9ca9e75279df5f4e3fee19bf5190ed672dcdfb33/log/?branch=refs/heads/emacs-unicode` Snapshot branches """"""""""""""""" +.. http:get:: /browse/snapshot/branches/ + + HTML view that produces a display of the list of branches collected in a snapshot. + An origin URL must be given as a query parameter. + + :query string origin_url: specify the origin from which to retrieve the snapshot and branches + :query string timestamp: optional; an ISO 8601 datetime string to parse in order to + find the closest visit + :name_include: optional; to filter branches by name + + :statuscode 200: no error + :statuscode 400: origin_url parameter is missing + :statuscode 404: requested origin is either missing or has no associated snapshots + + **Examples:** + + .. parsed-literal:: + + :swh_web_browse:`snapshot/branches?origin_url=https://github.com/python/cpython` + :swh_web_browse:`snapshot/branches/?origin_url=https://github.com/python/cpython×tamp=2021-01-23T22:24:10Z` + :swh_web_browse:`snapshot/branches/?origin_url=https://github.com/python/cpython&name_include=v2` + + .. http:get:: /browse/snapshot/(snapshot_id)/branches/ HTML view that produces a display of the list of branches collected in a snapshot. The features offered by the view are similar to the one for browsing the list of branches in an origin context (see :http:get:`/browse/origin/(origin_url)/branches/`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/03d7897352541e78ee7b13a580dc836778e8126a/branches/` :swh_web_browse:`snapshot/f37563b953327f8fd83e39af6ebb929ef85103d5/branches/` Snapshot releases """"""""""""""""" .. http:get:: /browse/snapshot/(snapshot_id)/releases/ HTML view that produces a display of the list of releases collected in a snapshot. The features offered by the view are similar to the one for browsing the list of releases in an origin context (see :http:get:`/browse/origin/(origin_url)/releases/`). :param string snapshot_id: hexadecimal representation of the snapshot **sha1** identifier :statuscode 200: no error :statuscode 400: an invalid snapshot identifier has been provided :statuscode 404: requested snapshot can not be found in the archive **Examples:** .. parsed-literal:: :swh_web_browse:`snapshot/673156c31a876c5b99b2fe3e89615529de9a3c44/releases/` :swh_web_browse:`snapshot/23e6fb084a60cc909b9e222d80d89fdb98756dee/releases/` diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py index 22b496fb..67d73dd5 100644 --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -1,322 +1,316 @@ -# Copyright (C) 2017-2020 The Software Heritage developers +# Copyright (C) 2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from django.shortcuts import redirect, render from django.urls import resolve from swh.web.browse.browseurls import browse_route from swh.web.browse.snapshot_context import ( - browse_snapshot_branches, browse_snapshot_content, browse_snapshot_directory, browse_snapshot_releases, get_snapshot_context, ) from swh.web.common import archive from swh.web.common.exc import BadInputExc from swh.web.common.origin_visits import get_origin_visits from swh.web.common.utils import format_utc_iso_date, parse_iso8601_date_to_utc, reverse def redirect_to_new_route(request, new_route): request_path = resolve(request.path_info) # Send all the url_args and the request_args as query params # eg /origin//log?path=test # will be send as /log?url=&path=test args = {**request_path.kwargs, **{k: v for k, v in request.GET.items()}} return redirect(reverse(new_route, query_params=args), permanent=True,) @browse_route( r"origin/directory/", view_name="browse-origin-directory", ) def origin_directory_browse(request): """Django view for browsing the content of a directory associated to an origin for a given visit. The URL that points to it is :http:get:`/browse/origin/directory/` """ return browse_snapshot_directory( request, origin_url=request.GET.get("origin_url"), snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), path=request.GET.get("path"), ) @browse_route( r"origin/(?P.+)/visit/(?P.+)/directory/", r"origin/(?P.+)/visit/(?P.+)/directory/(?P.+)/", r"origin/(?P.+)/directory/(?P.+)/", r"origin/(?P.+)/directory/", view_name="browse-origin-directory-legacy", ) def origin_directory_browse_legacy(request, origin_url, timestamp=None, path=None): """Django view for browsing the content of a directory associated to an origin for a given visit. The URLs that point to it are :http:get:`/browse/origin/(origin_url)/directory/[(path)/]` and :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/directory/[(path)/]` """ return browse_snapshot_directory( request, origin_url=origin_url, snapshot_id=request.GET.get("snapshot"), timestamp=timestamp, path=path, ) @browse_route( r"origin/content/", view_name="browse-origin-content", ) def origin_content_browse(request): """Django view that produces an HTML display of a content associated to an origin for a given visit. The URL that points to it is :http:get:`/browse/origin/content/` """ return browse_snapshot_content( request, origin_url=request.GET.get("origin_url"), snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), path=request.GET.get("path"), selected_language=request.GET.get("language"), ) @browse_route( r"origin/(?P.+)/visit/(?P.+)/content/(?P.+)/", r"origin/(?P.+)/content/(?P.+)/", r"origin/(?P.+)/content/", view_name="browse-origin-content-legacy", ) def origin_content_browse_legacy(request, origin_url, path=None, timestamp=None): """Django view that produces an HTML display of a content associated to an origin for a given visit. The URLs that point to it are :http:get:`/browse/origin/(origin_url)/content/(path)/` and :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/content/(path)/` """ return browse_snapshot_content( request, origin_url=origin_url, snapshot_id=request.GET.get("snapshot"), timestamp=timestamp, path=path, selected_language=request.GET.get("language"), ) @browse_route( r"origin/log/", view_name="browse-origin-log", ) def origin_log_browse(request): """ This route is deprecated; use http:get:`/browse/snapshot/log` instead Django view that produces an HTML display of revisions history (aka the commit log) associated to a software origin. The URL that points to it is :http:get:`/browse/origin/log/` """ return redirect_to_new_route(request, "browse-snapshot-log") @browse_route( r"origin/(?P.+)/visit/(?P.+)/log/", r"origin/(?P.+)/log/", view_name="browse-origin-log-legacy", ) def origin_log_browse_legacy(request, origin_url, timestamp=None): """ This route is deprecated; use http:get:`/browse/snapshot/log` instead Django view that produces an HTML display of revisions history (aka the commit log) associated to a software origin. The URLs that point to it are :http:get:`/browse/origin/(origin_url)/log/` and :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/log/` """ return redirect_to_new_route(request, "browse-snapshot-log",) @browse_route( r"origin/branches/", view_name="browse-origin-branches", ) def origin_branches_browse(request): - """Django view that produces an HTML display of the list of branches + """ + This route is deprecated; use http:get:`/browse/snapshot/branches` instead + + Django view that produces an HTML display of the list of branches associated to an origin for a given visit. The URL that points to it is :http:get:`/browse/origin/branches/` """ - return browse_snapshot_branches( - request, - origin_url=request.GET.get("origin_url"), - snapshot_id=request.GET.get("snapshot"), - timestamp=request.GET.get("timestamp"), - branch_name_include=request.GET.get("name_include"), - ) + return redirect_to_new_route(request, "browse-snapshot-branches") @browse_route( r"origin/(?P.+)/visit/(?P.+)/branches/", r"origin/(?P.+)/branches/", view_name="browse-origin-branches-legacy", ) def origin_branches_browse_legacy(request, origin_url, timestamp=None): - """Django view that produces an HTML display of the list of branches + """ + This route is deprecated; use http:get:`/browse/snapshot/branches` instead + + Django view that produces an HTML display of the list of branches associated to an origin for a given visit. The URLs that point to it are :http:get:`/browse/origin/(origin_url)/branches/` and :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/branches/` """ - return browse_snapshot_branches( - request, - origin_url=origin_url, - snapshot_id=request.GET.get("snapshot"), - timestamp=timestamp, - ) + return redirect_to_new_route(request, "browse-snapshot-branches") @browse_route( r"origin/releases/", view_name="browse-origin-releases", ) def origin_releases_browse(request): """Django view that produces an HTML display of the list of releases associated to an origin for a given visit. The URL that points to it is :http:get:`/browse/origin/releases/` """ return browse_snapshot_releases( request, origin_url=request.GET.get("origin_url"), snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), release_name_include=request.GET.get("name_include"), ) @browse_route( r"origin/(?P.+)/visit/(?P.+)/releases/", r"origin/(?P.+)/releases/", view_name="browse-origin-releases-legacy", ) def origin_releases_browse_legacy(request, origin_url, timestamp=None): """Django view that produces an HTML display of the list of releases associated to an origin for a given visit. The URLs that point to it are :http:get:`/browse/origin/(origin_url)/releases/` and :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/releases/` """ return browse_snapshot_releases( request, origin_url=origin_url, snapshot_id=request.GET.get("snapshot"), timestamp=timestamp, ) def _origin_visits_browse(request, origin_url): if origin_url is None: raise BadInputExc("An origin URL must be provided as query parameter.") origin_info = archive.lookup_origin({"url": origin_url}) origin_visits = get_origin_visits(origin_info) snapshot_context = get_snapshot_context(origin_url=origin_url) for i, visit in enumerate(origin_visits): url_date = format_utc_iso_date(visit["date"], "%Y-%m-%dT%H:%M:%SZ") visit["formatted_date"] = format_utc_iso_date(visit["date"]) query_params = {"origin_url": origin_url, "timestamp": url_date} if i < len(origin_visits) - 1: if visit["date"] == origin_visits[i + 1]["date"]: query_params = {"visit_id": visit["visit"]} if i > 0: if visit["date"] == origin_visits[i - 1]["date"]: query_params = {"visit_id": visit["visit"]} snapshot = visit["snapshot"] if visit["snapshot"] else "" visit["url"] = reverse("browse-origin-directory", query_params=query_params,) if not snapshot: visit["snapshot"] = "" visit["date"] = parse_iso8601_date_to_utc(visit["date"]).timestamp() heading = "Origin visits - %s" % origin_url return render( request, "browse/origin-visits.html", { "heading": heading, "swh_object_name": "Visits", "swh_object_metadata": origin_info, "origin_visits": origin_visits, "origin_info": origin_info, "snapshot_context": snapshot_context, "vault_cooking": None, "show_actions": False, }, ) @browse_route(r"origin/visits/", view_name="browse-origin-visits") def origin_visits_browse(request): """Django view that produces an HTML display of visits reporting for a given origin. The URL that points to it is :http:get:`/browse/origin/visits/`. """ return _origin_visits_browse(request, request.GET.get("origin_url")) @browse_route( r"origin/(?P.+)/visits/", view_name="browse-origin-visits-legacy" ) def origin_visits_browse_legacy(request, origin_url): """Django view that produces an HTML display of visits reporting for a given origin. The URL that points to it is :http:get:`/browse/origin/(origin_url)/visits/`. """ return _origin_visits_browse(request, origin_url) @browse_route(r"origin/", view_name="browse-origin") def origin_browse(request): """Django view that redirects to the display of the latest archived snapshot for a given software origin. """ last_snapshot_url = reverse("browse-origin-directory", query_params=request.GET,) return redirect(last_snapshot_url) @browse_route(r"origin/(?P.+)/", view_name="browse-origin-legacy") def origin_browse_legacy(request, origin_url): """Django view that redirects to the display of the latest archived snapshot for a given software origin. """ last_snapshot_url = reverse( "browse-origin-directory", query_params={"origin_url": origin_url, **request.GET}, ) return redirect(last_snapshot_url) diff --git a/swh/web/browse/views/snapshot.py b/swh/web/browse/views/snapshot.py index 09e0dfb7..a1ec5ef2 100644 --- a/swh/web/browse/views/snapshot.py +++ b/swh/web/browse/views/snapshot.py @@ -1,189 +1,209 @@ # Copyright (C) 2018-2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from django.shortcuts import redirect from swh.web.browse.browseurls import browse_route from swh.web.browse.snapshot_context import ( browse_snapshot_branches, browse_snapshot_content, browse_snapshot_directory, browse_snapshot_log, browse_snapshot_releases, get_snapshot_context, ) from swh.web.common.exc import BadInputExc from swh.web.common.utils import reverse def get_snapshot_from_request(request): snapshot = request.GET.get("snapshot") if snapshot: return snapshot if request.GET.get("origin_url") is None: raise BadInputExc("An origin URL must be provided as a query parameter.") return get_snapshot_context( origin_url=request.GET.get("origin_url"), timestamp=request.GET.get("timestamp") )["snapshot_id"] @browse_route( r"snapshot/(?P[0-9a-f]+)/", view_name="browse-snapshot", checksum_args=["snapshot_id"], ) def snapshot_browse(request, snapshot_id): """Django view for browsing the content of a snapshot. The url that points to it is :http:get:`/browse/snapshot/(snapshot_id)/` """ browse_snapshot_url = reverse( "browse-snapshot-directory", url_args={"snapshot_id": snapshot_id}, query_params=request.GET, ) return redirect(browse_snapshot_url) @browse_route( r"snapshot/(?P[0-9a-f]+)/directory/", view_name="browse-snapshot-directory", checksum_args=["snapshot_id"], ) def snapshot_directory_browse(request, snapshot_id): """Django view for browsing the content of a directory collected in a snapshot. The URL that points to it is :http:get:`/browse/snapshot/(snapshot_id)/directory/` """ return browse_snapshot_directory( request, snapshot_id=snapshot_id, path=request.GET.get("path"), origin_url=request.GET.get("origin_url"), ) @browse_route( r"snapshot/(?P[0-9a-f]+)/directory/(?P.+)/", view_name="browse-snapshot-directory-legacy", checksum_args=["snapshot_id"], ) def snapshot_directory_browse_legacy(request, snapshot_id, path=None): """Django view for browsing the content of a directory collected in a snapshot. The URL that points to it is :http:get:`/browse/snapshot/(snapshot_id)/directory/(path)/` """ origin_url = request.GET.get("origin_url", None) if not origin_url: origin_url = request.GET.get("origin", None) return browse_snapshot_directory( request, snapshot_id=snapshot_id, path=path, origin_url=origin_url ) @browse_route( r"snapshot/(?P[0-9a-f]+)/content/", view_name="browse-snapshot-content", checksum_args=["snapshot_id"], ) def snapshot_content_browse(request, snapshot_id): """Django view that produces an HTML display of a content collected in a snapshot. The url that points to it is :http:get:`/browse/snapshot/(snapshot_id)/content/` """ return browse_snapshot_content( request, snapshot_id=snapshot_id, path=request.GET.get("path"), selected_language=request.GET.get("language"), ) @browse_route( r"snapshot/(?P[0-9a-f]+)/content/(?P.+)/", view_name="browse-snapshot-content-legacy", checksum_args=["snapshot_id"], ) def snapshot_content_browse_legacy(request, snapshot_id, path): """Django view that produces an HTML display of a content collected in a snapshot. The url that points to it is :http:get:`/browse/snapshot/(snapshot_id)/content/(path)/` """ return browse_snapshot_content( request, snapshot_id=snapshot_id, path=path, selected_language=request.GET.get("language"), ) @browse_route( r"snapshot/(?P[0-9a-f]+)/log/", r"snapshot/log/", view_name="browse-snapshot-log", checksum_args=["snapshot_id"], ) def snapshot_log_browse(request, snapshot_id=None): """Django view that produces an HTML display of revisions history (aka the commit log) collected in a snapshot. The URLs that point to it are :http:get:`/browse/snapshot/(snapshot_id)/log/` and :http:get:`/browse/snapshot/log/` """ if snapshot_id is None: # This case happens when redirected from /origin/log snapshot_id = get_snapshot_from_request(request) # Redirect to the same route with snapshot_id return redirect( reverse( "browse-snapshot-log", url_args={"snapshot_id": snapshot_id}, query_params=request.GET, ), ) return browse_snapshot_log( request, snapshot_id=snapshot_id, origin_url=request.GET.get("origin_url"), timestamp=request.GET.get("timestamp"), ) @browse_route( r"snapshot/(?P[0-9a-f]+)/branches/", + r"snapshot/branches/", view_name="browse-snapshot-branches", checksum_args=["snapshot_id"], ) -def snapshot_branches_browse(request, snapshot_id): - """Django view that produces an HTML display of the list of releases +def snapshot_branches_browse(request, snapshot_id=None): + """Django view that produces an HTML display of the list of branches collected in a snapshot. - The url that points to it is - :http:get:`/browse/snapshot/(snapshot_id)/branches/` + The URLs that point to it are + :http:get:`/browse/snapshot/(snapshot_id)/branches/` and + :http:get:`/browse/snapshot/branches/` """ - return browse_snapshot_branches(request, snapshot_id=snapshot_id) + if snapshot_id is None: + # This case happens when redirected from /origin/branches + snapshot_id = get_snapshot_from_request(request) + # Redirect to the same route with the newest snapshot_id + # for the given origin + return redirect( + reverse( + "browse-snapshot-branches", + url_args={"snapshot_id": snapshot_id}, + query_params=request.GET, + ), + ) + return browse_snapshot_branches( + request, + snapshot_id=snapshot_id, + origin_url=request.GET.get("origin_url"), + timestamp=request.GET.get("timestamp"), + branch_name_include=request.GET.get("name_include"), + ) @browse_route( r"snapshot/(?P[0-9a-f]+)/releases/", view_name="browse-snapshot-releases", checksum_args=["snapshot_id"], ) def snapshot_releases_browse(request, snapshot_id): """Django view that produces an HTML display of the list of releases collected in a snapshot. The url that points to it is :http:get:`/browse/snapshot/(snapshot_id)/releases/` """ return browse_snapshot_releases(request, snapshot_id=snapshot_id) diff --git a/swh/web/tests/browse/views/test_origin.py b/swh/web/tests/browse/views/test_origin.py index 589d95b0..17143945 100644 --- a/swh/web/tests/browse/views/test_origin.py +++ b/swh/web/tests/browse/views/test_origin.py @@ -1,1341 +1,1212 @@ # Copyright (C) 2017-2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import random import re -import string from hypothesis import given import pytest from django.utils.html import escape from swh.model.hashutil import hash_to_bytes from swh.model.model import ( OriginVisit, OriginVisitStatus, Snapshot, SnapshotBranch, TargetType, ) from swh.model.swhids import ObjectType from swh.storage.utils import now from swh.web.browse.snapshot_context import process_snapshot_branches from swh.web.common.exc import NotFoundExc from swh.web.common.identifiers import gen_swhid from swh.web.common.utils import ( format_utc_iso_date, gen_path_info, parse_iso8601_date_to_utc, reverse, ) -from swh.web.tests.data import get_content, random_sha1 +from swh.web.tests.data import get_content from swh.web.tests.django_asserts import assert_contains, assert_not_contains from swh.web.tests.strategies import new_origin, new_snapshot, visit_dates from swh.web.tests.utils import check_html_get_response def test_origin_visits_browse(client, archive_data, origin_with_multiple_visits): origin_url = origin_with_multiple_visits["url"] url = reverse("browse-origin-visits", query_params={"origin_url": origin_url}) resp = check_html_get_response( client, url, status_code=200, template_used="browse/origin-visits.html" ) visits = archive_data.origin_visit_get(origin_url) for v in visits: vdate = format_utc_iso_date(v["date"], "%Y-%m-%dT%H:%M:%SZ") browse_dir_url = reverse( "browse-origin-directory", query_params={"origin_url": origin_url, "timestamp": vdate}, ) assert_contains(resp, browse_dir_url) _check_origin_link(resp, origin_url) def test_origin_content_view( client, archive_data, swh_scheduler, origin_with_multiple_visits ): origin_visits = archive_data.origin_visit_get(origin_with_multiple_visits["url"]) def _get_archive_data(visit_idx): snapshot = archive_data.snapshot_get(origin_visits[visit_idx]["snapshot"]) head_rev_id = archive_data.snapshot_get_head(snapshot) head_rev = archive_data.revision_get(head_rev_id) dir_content = archive_data.directory_ls(head_rev["directory"]) dir_files = [e for e in dir_content if e["type"] == "file"] dir_file = random.choice(dir_files) branches, releases, _ = process_snapshot_branches(snapshot) return { "branches": branches, "releases": releases, "root_dir_sha1": head_rev["directory"], "content": get_content(dir_file["checksums"]["sha1"]), "visit": origin_visits[visit_idx], "snapshot_sizes": archive_data.snapshot_count_branches(snapshot["id"]), } tdata = _get_archive_data(-1) _origin_content_view_test_helper( client, archive_data, origin_with_multiple_visits, origin_visits[-1], tdata["snapshot_sizes"], tdata["branches"], tdata["releases"], tdata["root_dir_sha1"], tdata["content"], ) _origin_content_view_test_helper( client, archive_data, origin_with_multiple_visits, origin_visits[-1], tdata["snapshot_sizes"], tdata["branches"], tdata["releases"], tdata["root_dir_sha1"], tdata["content"], timestamp=tdata["visit"]["date"], ) _origin_content_view_test_helper( client, archive_data, origin_with_multiple_visits, origin_visits[-1], tdata["snapshot_sizes"], tdata["branches"], tdata["releases"], tdata["root_dir_sha1"], tdata["content"], snapshot_id=tdata["visit"]["snapshot"], ) tdata = _get_archive_data(0) _origin_content_view_test_helper( client, archive_data, origin_with_multiple_visits, origin_visits[0], tdata["snapshot_sizes"], tdata["branches"], tdata["releases"], tdata["root_dir_sha1"], tdata["content"], visit_id=tdata["visit"]["visit"], ) _origin_content_view_test_helper( client, archive_data, origin_with_multiple_visits, origin_visits[0], tdata["snapshot_sizes"], tdata["branches"], tdata["releases"], tdata["root_dir_sha1"], tdata["content"], snapshot_id=tdata["visit"]["snapshot"], ) def test_origin_root_directory_view(client, archive_data, swh_scheduler, origin): origin_visits = archive_data.origin_visit_get(origin["url"]) visit = origin_visits[-1] snapshot = archive_data.snapshot_get(visit["snapshot"]) snapshot_sizes = archive_data.snapshot_count_branches(snapshot["id"]) head_rev_id = archive_data.snapshot_get_head(snapshot) head_rev = archive_data.revision_get(head_rev_id) root_dir_sha1 = head_rev["directory"] dir_content = archive_data.directory_ls(root_dir_sha1) branches, releases, _ = process_snapshot_branches(snapshot) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, visit_id=visit["visit"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, timestamp=visit["date"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, snapshot_id=visit["snapshot"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, visit_id=visit["visit"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, timestamp=visit["date"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, dir_content, snapshot_id=visit["snapshot"], ) def test_origin_sub_directory_view(client, archive_data, swh_scheduler, origin): origin_visits = archive_data.origin_visit_get(origin["url"]) visit = origin_visits[-1] snapshot = archive_data.snapshot_get(visit["snapshot"]) snapshot_sizes = archive_data.snapshot_count_branches(snapshot["id"]) head_rev_id = archive_data.snapshot_get_head(snapshot) head_rev = archive_data.revision_get(head_rev_id) root_dir_sha1 = head_rev["directory"] subdirs = [ e for e in archive_data.directory_ls(root_dir_sha1) if e["type"] == "dir" ] branches, releases, _ = process_snapshot_branches(snapshot) if len(subdirs) == 0: return subdir = random.choice(subdirs) subdir_content = archive_data.directory_ls(subdir["target"]) subdir_path = subdir["name"] _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, visit_id=visit["visit"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, timestamp=visit["date"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, snapshot_id=visit["snapshot"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, visit_id=visit["visit"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, timestamp=visit["date"], ) _origin_directory_view_test_helper( client, archive_data, origin, visit, snapshot_sizes, branches, releases, root_dir_sha1, subdir_content, path=subdir_path, snapshot_id=visit["snapshot"], ) -def test_origin_branches(client, archive_data, origin): - origin_visits = archive_data.origin_visit_get(origin["url"]) - - visit = origin_visits[-1] - snapshot = archive_data.snapshot_get(visit["snapshot"]) - snapshot_sizes = archive_data.snapshot_count_branches(snapshot["id"]) - snapshot_content = process_snapshot_branches(snapshot) - - _origin_branches_test_helper(client, origin, snapshot_content, snapshot_sizes) - - _origin_branches_test_helper( - client, origin, snapshot_content, snapshot_sizes, snapshot_id=visit["snapshot"] - ) - - def test_origin_releases(client, archive_data, origin): origin_visits = archive_data.origin_visit_get(origin["url"]) visit = origin_visits[-1] snapshot = archive_data.snapshot_get(visit["snapshot"]) snapshot_sizes = archive_data.snapshot_count_branches(snapshot["id"]) snapshot_content = process_snapshot_branches(snapshot) _origin_releases_test_helper(client, origin, snapshot_content, snapshot_sizes) _origin_releases_test_helper( client, origin, snapshot_content, snapshot_sizes, snapshot_id=visit["snapshot"] ) @given( new_origin(), new_snapshot(min_size=4, max_size=4), visit_dates(), ) def test_origin_snapshot_null_branch( client, archive_data, revisions_list, new_origin, new_snapshot, visit_dates, ): revisions = revisions_list(size=4) snp_dict = new_snapshot.to_dict() archive_data.origin_add([new_origin]) for i, branch in enumerate(snp_dict["branches"].keys()): if i == 0: snp_dict["branches"][branch] = None else: snp_dict["branches"][branch] = { "target_type": "revision", "target": hash_to_bytes(revisions[i - 1]), } archive_data.snapshot_add([Snapshot.from_dict(snp_dict)]) visit = archive_data.origin_visit_add( [OriginVisit(origin=new_origin.url, date=visit_dates[0], type="git",)] )[0] visit_status = OriginVisitStatus( origin=new_origin.url, visit=visit.visit, date=now(), status="partial", snapshot=snp_dict["id"], ) archive_data.origin_visit_status_add([visit_status]) url = reverse( "browse-origin-directory", query_params={"origin_url": new_origin.url} ) check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) @given( new_origin(), new_snapshot(min_size=4, max_size=4), visit_dates(), ) def test_origin_snapshot_invalid_branch( client, archive_data, revisions_list, new_origin, new_snapshot, visit_dates, ): revisions = revisions_list(size=4) snp_dict = new_snapshot.to_dict() archive_data.origin_add([new_origin]) for i, branch in enumerate(snp_dict["branches"].keys()): snp_dict["branches"][branch] = { "target_type": "revision", "target": hash_to_bytes(revisions[i]), } archive_data.snapshot_add([Snapshot.from_dict(snp_dict)]) visit = archive_data.origin_visit_add( [OriginVisit(origin=new_origin.url, date=visit_dates[0], type="git",)] )[0] visit_status = OriginVisitStatus( origin=new_origin.url, visit=visit.visit, date=now(), status="full", snapshot=snp_dict["id"], ) archive_data.origin_visit_status_add([visit_status]) url = reverse( "browse-origin-directory", query_params={"origin_url": new_origin.url, "branch": "invalid_branch"}, ) check_html_get_response(client, url, status_code=404, template_used="error.html") @given(new_origin()) def test_browse_visits_origin_not_found(client, new_origin): url = reverse("browse-origin-visits", query_params={"origin_url": new_origin.url}) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert_contains( resp, f"Origin with url {new_origin.url} not found", status_code=404 ) def test_browse_origin_directory_no_visit(client, mocker, origin): mock_get_origin_visits = mocker.patch( "swh.web.common.origin_visits.get_origin_visits" ) mock_get_origin_visits.return_value = [] mock_archive = mocker.patch("swh.web.common.origin_visits.archive") mock_archive.lookup_origin_visit_latest.return_value = None url = reverse("browse-origin-directory", query_params={"origin_url": origin["url"]}) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert_contains(resp, "No valid visit", status_code=404) assert not mock_get_origin_visits.called def test_browse_origin_directory_unknown_visit(client, mocker, origin): mock_get_origin_visits = mocker.patch( "swh.web.common.origin_visits.get_origin_visits" ) mock_get_origin_visits.return_value = [{"visit": 1}] url = reverse( "browse-origin-directory", query_params={"origin_url": origin["url"], "visit_id": 2}, ) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert re.search("Visit.*not found", resp.content.decode("utf-8")) assert mock_get_origin_visits.called def test_browse_origin_directory_not_found(client, origin): url = reverse( "browse-origin-directory", query_params={"origin_url": origin["url"], "path": "/invalid/dir/path/"}, ) resp = check_html_get_response( client, url, status_code=404, template_used="browse/directory.html" ) assert re.search("Directory.*not found", resp.content.decode("utf-8")) def test_browse_origin_content_no_visit(client, mocker, origin): mock_get_origin_visits = mocker.patch( "swh.web.common.origin_visits.get_origin_visits" ) mock_get_origin_visits.return_value = [] mock_archive = mocker.patch("swh.web.common.origin_visits.archive") mock_archive.lookup_origin_visit_latest.return_value = None url = reverse( "browse-origin-content", query_params={"origin_url": origin["url"], "path": "foo"}, ) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert_contains(resp, "No valid visit", status_code=404) assert not mock_get_origin_visits.called def test_browse_origin_content_unknown_visit(client, mocker, origin): mock_get_origin_visits = mocker.patch( "swh.web.common.origin_visits.get_origin_visits" ) mock_get_origin_visits.return_value = [{"visit": 1}] url = reverse( "browse-origin-content", query_params={"origin_url": origin["url"], "path": "foo", "visit_id": 2}, ) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert re.search("Visit.*not found", resp.content.decode("utf-8")) assert mock_get_origin_visits.called def _add_empty_snapshot_origin(new_origin, archive_data): snapshot = Snapshot(branches={}) archive_data.origin_add([new_origin]) archive_data.snapshot_add([snapshot]) visit = archive_data.origin_visit_add( [OriginVisit(origin=new_origin.url, date=now(), type="git",)] )[0] visit_status = OriginVisitStatus( origin=new_origin.url, visit=visit.visit, date=now(), status="full", snapshot=snapshot.id, ) archive_data.origin_visit_status_add([visit_status]) @pytest.mark.django_db @pytest.mark.parametrize("object_type", ["content", "directory"]) @given(new_origin()) def test_browse_origin_content_directory_empty_snapshot( client, staff_user, archive_data, object_type, new_origin ): _add_empty_snapshot_origin(new_origin, archive_data) # to check proper generation of raw extrinsic metadata api links client.force_login(staff_user) url = reverse( f"browse-origin-{object_type}", query_params={"origin_url": new_origin.url, "path": "baz"}, ) resp = check_html_get_response( client, url, status_code=200, template_used=f"browse/{object_type}.html" ) assert re.search("snapshot.*is empty", resp.content.decode("utf-8")) def test_browse_origin_content_not_found(client, origin): url = reverse( "browse-origin-content", query_params={"origin_url": origin["url"], "path": "/invalid/file/path"}, ) resp = check_html_get_response( client, url, status_code=404, template_used="browse/content.html" ) assert re.search("Directory entry.*not found", resp.content.decode("utf-8")) def test_browse_directory_snapshot_not_found(client, mocker, origin): mock_get_snapshot_context = mocker.patch( "swh.web.browse.snapshot_context.get_snapshot_context" ) mock_get_snapshot_context.side_effect = NotFoundExc("Snapshot not found") url = reverse("browse-origin-directory", query_params={"origin_url": origin["url"]}) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert_contains(resp, "Snapshot not found", status_code=404) assert mock_get_snapshot_context.called @given(new_origin()) def test_origin_empty_snapshot(client, archive_data, new_origin): _add_empty_snapshot_origin(new_origin, archive_data) url = reverse( "browse-origin-directory", query_params={"origin_url": new_origin.url} ) resp = check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) resp_content = resp.content.decode("utf-8") assert re.search("snapshot.*is empty", resp_content) assert not re.search("swh-tr-link", resp_content) @given(new_origin()) def test_origin_empty_snapshot_null_revision(client, archive_data, new_origin): snapshot = Snapshot( branches={ b"HEAD": SnapshotBranch( target="refs/head/master".encode(), target_type=TargetType.ALIAS, ), b"refs/head/master": None, } ) archive_data.origin_add([new_origin]) archive_data.snapshot_add([snapshot]) visit = archive_data.origin_visit_add( [OriginVisit(origin=new_origin.url, date=now(), type="git",)] )[0] visit_status = OriginVisitStatus( origin=new_origin.url, visit=visit.visit, date=now(), status="partial", snapshot=snapshot.id, ) archive_data.origin_visit_status_add([visit_status]) url = reverse( "browse-origin-directory", query_params={"origin_url": new_origin.url}, ) resp = check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) resp_content = resp.content.decode("utf-8") assert re.search("snapshot.*is empty", resp_content) assert not re.search("swh-tr-link", resp_content) def test_origin_release_browse(client, archive_data, origin_with_releases): origin_url = origin_with_releases["url"] snapshot = archive_data.snapshot_get_latest(origin_url) release = [ b for b in snapshot["branches"].values() if b["target_type"] == "release" ][-1] release_data = archive_data.release_get(release["target"]) revision_data = archive_data.revision_get(release_data["target"]) url = reverse( "browse-origin-directory", query_params={"origin_url": origin_url, "release": release_data["name"]}, ) resp = check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) assert_contains(resp, release_data["name"]) assert_contains(resp, release["target"]) swhid_context = { "origin": origin_url, "visit": gen_swhid(ObjectType.SNAPSHOT, snapshot["id"]), "anchor": gen_swhid(ObjectType.RELEASE, release_data["id"]), } swh_dir_id = gen_swhid( ObjectType.DIRECTORY, revision_data["directory"], metadata=swhid_context ) swh_dir_id_url = reverse("browse-swhid", url_args={"swhid": swh_dir_id}) assert_contains(resp, swh_dir_id) assert_contains(resp, swh_dir_id_url) def test_origin_release_browse_not_found(client, origin_with_releases): invalid_release_name = "swh-foo-bar" url = reverse( "browse-origin-directory", query_params={ "origin_url": origin_with_releases["url"], "release": invalid_release_name, }, ) resp = check_html_get_response( client, url, status_code=404, template_used="error.html" ) assert re.search( f"Release {invalid_release_name}.*not found", resp.content.decode("utf-8") ) @given(new_origin()) def test_origin_browse_directory_branch_with_non_resolvable_revision( client, archive_data, unknown_revision, new_origin, ): branch_name = "master" snapshot = Snapshot( branches={ branch_name.encode(): SnapshotBranch( target=hash_to_bytes(unknown_revision), target_type=TargetType.REVISION, ) } ) archive_data.origin_add([new_origin]) archive_data.snapshot_add([snapshot]) visit = archive_data.origin_visit_add( [OriginVisit(origin=new_origin.url, date=now(), type="git",)] )[0] visit_status = OriginVisitStatus( origin=new_origin.url, visit=visit.visit, date=now(), status="partial", snapshot=snapshot.id, ) archive_data.origin_visit_status_add([visit_status]) url = reverse( "browse-origin-directory", query_params={"origin_url": new_origin.url, "branch": branch_name}, ) resp = check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) assert_contains( resp, f"Revision {unknown_revision } could not be found in the archive." ) # no revision card assert_not_contains(resp, "swh-tip-revision") # no Download dropdown assert_not_contains(resp, "swh-vault-download") # no History link assert_not_contains(resp, "swh-tr-link") # no SWHIDs for directory and revision assert_not_contains(resp, "swh:1:dir:") assert_not_contains(resp, "swh:1:rev:") def test_origin_content_no_path(client, origin): url = reverse("browse-origin-content", query_params={"origin_url": origin["url"]}) resp = check_html_get_response( client, url, status_code=400, template_used="error.html" ) assert_contains( resp, "The path of a content must be given as query parameter.", status_code=400 ) def test_origin_views_no_url_query_parameter(client): for browse_context in ( "content", "directory", - "branches", "releases", "visits", ): url = reverse(f"browse-origin-{browse_context}") resp = check_html_get_response( client, url, status_code=400, template_used="error.html" ) assert_contains( resp, "An origin URL must be provided as query parameter.", status_code=400, ) @given(new_origin()) -@pytest.mark.parametrize("browse_context", ["log"]) +@pytest.mark.parametrize("browse_context", ["log", "branches"]) def test_origin_view_redirects(client, browse_context, new_origin): query_params = {"origin_url": new_origin.url} url = reverse(f"browse-origin-{browse_context}", query_params=query_params) resp = check_html_get_response(client, url, status_code=301) assert resp["location"] == reverse( f"browse-snapshot-{browse_context}", query_params=query_params ) @given(new_origin()) -@pytest.mark.parametrize("browse_context", ["log"]) +@pytest.mark.parametrize("browse_context", ["log", "branches"]) def test_origin_view_legacy_redirects(client, browse_context, new_origin): params = {"origin_url": new_origin.url, "timestamp": "2021-01-23T22:24:10Z"} url = reverse( f"browse-origin-{browse_context}-legacy", url_args=params, query_params=params ) resp = check_html_get_response(client, url, status_code=301) assert resp["location"] == reverse( f"browse-snapshot-{browse_context}", query_params=params ) def _origin_content_view_test_helper( client, archive_data, origin_info, origin_visit, snapshot_sizes, origin_branches, origin_releases, root_dir_sha1, content, visit_id=None, timestamp=None, snapshot_id=None, ): content_path = "/".join(content["path"].split("/")[1:]) if not visit_id and not snapshot_id: visit_id = origin_visit["visit"] query_params = {"origin_url": origin_info["url"], "path": content_path} if timestamp: query_params["timestamp"] = timestamp if visit_id: query_params["visit_id"] = visit_id elif snapshot_id: query_params["snapshot"] = snapshot_id url = reverse("browse-origin-content", query_params=query_params) resp = check_html_get_response( client, url, status_code=200, template_used="browse/content.html" ) assert type(content["data"]) == str assert_contains(resp, '' % content["hljs_language"]) assert_contains(resp, escape(content["data"])) split_path = content_path.split("/") filename = split_path[-1] path = content_path.replace(filename, "")[:-1] path_info = gen_path_info(path) del query_params["path"] if timestamp: query_params["timestamp"] = format_utc_iso_date( parse_iso8601_date_to_utc(timestamp).isoformat(), "%Y-%m-%dT%H:%M:%SZ" ) root_dir_url = reverse("browse-origin-directory", query_params=query_params) assert_contains(resp, '
  • ', count=len(path_info) + 1) assert_contains(resp, '%s' % (root_dir_url, root_dir_sha1[:7])) for p in path_info: query_params["path"] = p["path"] dir_url = reverse("browse-origin-directory", query_params=query_params) assert_contains(resp, '%s' % (dir_url, p["name"])) assert_contains(resp, "
  • %s
  • " % filename) query_string = "sha1_git:" + content["sha1_git"] url_raw = reverse( "browse-content-raw", url_args={"query_string": query_string}, query_params={"filename": filename}, ) assert_contains(resp, url_raw) if "path" in query_params: del query_params["path"] origin_branches_url = reverse("browse-origin-branches", query_params=query_params) assert_contains(resp, f'href="{escape(origin_branches_url)}"') assert_contains(resp, f"Branches ({snapshot_sizes['revision']})") origin_releases_url = reverse("browse-origin-releases", query_params=query_params) assert_contains(resp, f'href="{escape(origin_releases_url)}">') assert_contains(resp, f"Releases ({snapshot_sizes['release']})") assert_contains(resp, '
  • ', count=len(origin_branches)) query_params["path"] = content_path for branch in origin_branches: root_dir_branch_url = reverse( "browse-origin-content", query_params={"branch": branch["name"], **query_params}, ) assert_contains(resp, '' % root_dir_branch_url) assert_contains(resp, '
  • ', count=len(origin_releases)) query_params["branch"] = None for release in origin_releases: root_dir_release_url = reverse( "browse-origin-content", query_params={"release": release["name"], **query_params}, ) assert_contains(resp, '' % root_dir_release_url) url = reverse("browse-origin-content", query_params=query_params) resp = check_html_get_response( client, url, status_code=200, template_used="browse/content.html" ) snapshot = archive_data.snapshot_get(origin_visit["snapshot"]) head_rev_id = archive_data.snapshot_get_head(snapshot) swhid_context = { "origin": origin_info["url"], "visit": gen_swhid(ObjectType.SNAPSHOT, snapshot["id"]), "anchor": gen_swhid(ObjectType.REVISION, head_rev_id), "path": f"/{content_path}", } swh_cnt_id = gen_swhid( ObjectType.CONTENT, content["sha1_git"], metadata=swhid_context ) swh_cnt_id_url = reverse("browse-swhid", url_args={"swhid": swh_cnt_id}) assert_contains(resp, swh_cnt_id) assert_contains(resp, swh_cnt_id_url) assert_contains(resp, "swh-take-new-snapshot") _check_origin_link(resp, origin_info["url"]) assert_not_contains(resp, "swh-metadata-popover") def _origin_directory_view_test_helper( client, archive_data, origin_info, origin_visit, snapshot_sizes, origin_branches, origin_releases, root_directory_sha1, directory_entries, visit_id=None, timestamp=None, snapshot_id=None, path=None, ): dirs = [e for e in directory_entries if e["type"] in ("dir", "rev")] files = [e for e in directory_entries if e["type"] == "file"] if not visit_id and not snapshot_id: visit_id = origin_visit["visit"] query_params = {"origin_url": origin_info["url"]} if timestamp: query_params["timestamp"] = timestamp elif visit_id: query_params["visit_id"] = visit_id else: query_params["snapshot"] = snapshot_id if path: query_params["path"] = path url = reverse("browse-origin-directory", query_params=query_params) resp = check_html_get_response( client, url, status_code=200, template_used="browse/directory.html" ) assert_contains(resp, '', count=len(dirs)) assert_contains(resp, '', count=len(files)) if timestamp: query_params["timestamp"] = format_utc_iso_date( parse_iso8601_date_to_utc(timestamp).isoformat(), "%Y-%m-%dT%H:%M:%SZ" ) for d in dirs: if d["type"] == "rev": dir_url = reverse("browse-revision", url_args={"sha1_git": d["target"]}) else: dir_path = d["name"] if path: dir_path = "%s/%s" % (path, d["name"]) query_params["path"] = dir_path dir_url = reverse("browse-origin-directory", query_params=query_params,) assert_contains(resp, dir_url) for f in files: file_path = f["name"] if path: file_path = "%s/%s" % (path, f["name"]) query_params["path"] = file_path file_url = reverse("browse-origin-content", query_params=query_params) assert_contains(resp, file_url) if "path" in query_params: del query_params["path"] root_dir_branch_url = reverse("browse-origin-directory", query_params=query_params) nb_bc_paths = 1 if path: nb_bc_paths = len(path.split("/")) + 1 assert_contains(resp, '
  • ', count=nb_bc_paths) assert_contains( resp, '%s' % (root_dir_branch_url, root_directory_sha1[:7]) ) origin_branches_url = reverse("browse-origin-branches", query_params=query_params) assert_contains(resp, f'href="{escape(origin_branches_url)}"') assert_contains(resp, f"Branches ({snapshot_sizes['revision']})") origin_releases_url = reverse("browse-origin-releases", query_params=query_params) nb_releases = len(origin_releases) if nb_releases > 0: assert_contains(resp, f'href="{escape(origin_releases_url)}"') assert_contains(resp, f"Releases ({snapshot_sizes['release']})") if path: query_params["path"] = path assert_contains(resp, '
  • ', count=len(origin_branches)) for branch in origin_branches: query_params["branch"] = branch["name"] root_dir_branch_url = reverse( "browse-origin-directory", query_params=query_params ) assert_contains(resp, '' % root_dir_branch_url) assert_contains(resp, '
  • ', count=len(origin_releases)) query_params["branch"] = None for release in origin_releases: query_params["release"] = release["name"] root_dir_release_url = reverse( "browse-origin-directory", query_params=query_params ) assert_contains(resp, 'href="%s"' % root_dir_release_url) assert_contains(resp, "vault-cook-directory") assert_contains(resp, "vault-cook-revision") snapshot = archive_data.snapshot_get(origin_visit["snapshot"]) head_rev_id = archive_data.snapshot_get_head(snapshot) swhid_context = { "origin": origin_info["url"], "visit": gen_swhid(ObjectType.SNAPSHOT, snapshot["id"]), "anchor": gen_swhid(ObjectType.REVISION, head_rev_id), "path": f"/{path}" if path else None, } swh_dir_id = gen_swhid( ObjectType.DIRECTORY, directory_entries[0]["dir_id"], metadata=swhid_context ) swh_dir_id_url = reverse("browse-swhid", url_args={"swhid": swh_dir_id}) assert_contains(resp, swh_dir_id) assert_contains(resp, swh_dir_id_url) assert_contains(resp, "swh-take-new-snapshot") _check_origin_link(resp, origin_info["url"]) assert_not_contains(resp, "swh-metadata-popover") -def _origin_branches_test_helper( - client, origin_info, origin_snapshot, snapshot_sizes, snapshot_id=None -): - query_params = {"origin_url": origin_info["url"], "snapshot": snapshot_id} - - url = reverse("browse-origin-branches", query_params=query_params) - - resp = check_html_get_response( - client, url, status_code=200, template_used="browse/branches.html" - ) - - origin_branches = origin_snapshot[0] - origin_releases = origin_snapshot[1] - - origin_branches_url = reverse("browse-origin-branches", query_params=query_params) - - assert_contains(resp, f'href="{escape(origin_branches_url)}"') - assert_contains(resp, f"Branches ({snapshot_sizes['revision']})") - - origin_releases_url = reverse("browse-origin-releases", query_params=query_params) - - nb_releases = len(origin_releases) - if nb_releases > 0: - assert_contains(resp, f'href="{escape(origin_releases_url)}">') - assert_contains(resp, f"Releases ({snapshot_sizes['release']})") - - assert_contains(resp, '' % escape(browse_branch_url)) - - browse_revision_url = reverse( - "browse-revision", - url_args={"sha1_git": branch["revision"]}, - query_params=query_params, - ) - assert_contains(resp, '' % escape(browse_revision_url)) - - _check_origin_link(resp, origin_info["url"]) - - def _origin_releases_test_helper( client, origin_info, origin_snapshot, snapshot_sizes, snapshot_id=None ): query_params = {"origin_url": origin_info["url"], "snapshot": snapshot_id} url = reverse("browse-origin-releases", query_params=query_params) resp = check_html_get_response( client, url, status_code=200, template_used="browse/releases.html" ) origin_releases = origin_snapshot[1] origin_branches_url = reverse("browse-origin-branches", query_params=query_params) assert_contains(resp, f'href="{escape(origin_branches_url)}"') assert_contains(resp, f"Branches ({snapshot_sizes['revision']})") origin_releases_url = reverse("browse-origin-releases", query_params=query_params) nb_releases = len(origin_releases) if nb_releases > 0: assert_contains(resp, f'href="{escape(origin_releases_url)}"') assert_contains(resp, f"Releases ({snapshot_sizes['release']}") assert_contains(resp, '' % escape(browse_release_url)) assert_contains(resp, '' % escape(browse_revision_url)) _check_origin_link(resp, origin_info["url"]) -@given( - new_origin(), visit_dates(), -) -def test_origin_branches_pagination_with_alias( - client, archive_data, mocker, release, revisions_list, new_origin, visit_dates, -): - """ - When a snapshot contains a branch or a release alias, pagination links - in the branches / releases view should be displayed. - """ - revisions = revisions_list(size=10) - mocker.patch("swh.web.browse.snapshot_context.PER_PAGE", len(revisions) / 2) - snp_dict = {"branches": {}, "id": hash_to_bytes(random_sha1())} - for i in range(len(revisions)): - branch = "".join(random.choices(string.ascii_lowercase, k=8)) - snp_dict["branches"][branch.encode()] = { - "target_type": "revision", - "target": hash_to_bytes(revisions[i]), - } - release_name = "".join(random.choices(string.ascii_lowercase, k=8)) - snp_dict["branches"][b"RELEASE_ALIAS"] = { - "target_type": "alias", - "target": release_name.encode(), - } - snp_dict["branches"][release_name.encode()] = { - "target_type": "release", - "target": hash_to_bytes(release), - } - archive_data.origin_add([new_origin]) - archive_data.snapshot_add([Snapshot.from_dict(snp_dict)]) - visit = archive_data.origin_visit_add( - [OriginVisit(origin=new_origin.url, date=visit_dates[0], type="git",)] - )[0] - visit_status = OriginVisitStatus( - origin=new_origin.url, - visit=visit.visit, - date=now(), - status="full", - snapshot=snp_dict["id"], - ) - archive_data.origin_visit_status_add([visit_status]) - - url = reverse("browse-origin-branches", query_params={"origin_url": new_origin.url}) - - resp = check_html_get_response( - client, url, status_code=200, template_used="browse/branches.html" - ) - assert_contains(resp, '