Hello community,

here is the log from the commit of package python-devpi-server for 
openSUSE:Factory checked in at 2020-04-02 17:45:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-devpi-server (Old)
 and      /work/SRC/openSUSE:Factory/.python-devpi-server.new.3248 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-devpi-server"

Thu Apr  2 17:45:00 2020 rev:4 rq:790871 version:5.4.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-devpi-server/python-devpi-server.changes  
2020-01-12 23:26:26.094861635 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-devpi-server.new.3248/python-devpi-server.changes
        2020-04-02 17:45:22.469530734 +0200
@@ -1,0 +2,18 @@
+Thu Apr  2 11:26:56 UTC 2020 - Marketa Calabkova <[email protected]>
+
+- Update to 5.4.1
+  * This is the last feature release with Python 2.7 support!
+  * Import won't abort anymore when a base index was removed. 
+    The bases setting will be imported as is.
+  * The ``requires_python`` metadata is now included in version 
+    data on mirror indexes.
+  * Downloaded files from mirrors can be included in exports with 
+    the ``--include-mirrored-files`` option.
+  * On import files for mirror indexes are now imported when they 
+    were included in the dump (see ``--include-mirrored-files``).
+  * Fix ``--no-root-pypi`` option when importing devpi data.
+  * Fix pushing from mirror to an index when the file was removed 
+    and ``mirror_use_external_urls`` is active.
+- Drop Python2 anyway because of pyramid dropping Python2
+
+-------------------------------------------------------------------

Old:
----
  devpi-server-5.3.1.tar.gz

New:
----
  devpi-server-5.4.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-devpi-server.spec ++++++
--- /var/tmp/diff_new_pack.sZ0Wnj/_old  2020-04-02 17:45:23.381531836 +0200
+++ /var/tmp/diff_new_pack.sZ0Wnj/_new  2020-04-02 17:45:23.381531836 +0200
@@ -17,8 +17,9 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define skip_python2 1
 Name:           python-devpi-server
-Version:        5.3.1
+Version:        5.4.1
 Release:        0
 Summary:        Private PyPI caching server
 License:        MIT

++++++ devpi-server-5.3.1.tar.gz -> devpi-server-5.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/CHANGELOG 
new/devpi-server-5.4.1/CHANGELOG
--- old/devpi-server-5.3.1/CHANGELOG    2019-12-05 14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/CHANGELOG    2020-03-26 09:54:54.000000000 +0100
@@ -2,6 +2,50 @@
 
 .. towncrier release notes start
 
+5.4.1 (2020-03-26)
+==================
+
+Bug Fixes
+---------
+
+- Import won't abort anymore when a base index was removed. The bases setting 
will be imported as is.
+
+
+5.4.0 (2020-01-31)
+==================
+
+.. note::
+  This is the last feature release with Python 2.7 support!
+
+  We will only make export related bugfix releases of 5.4.x.
+
+Features
+--------
+
+- The ``requires_python`` metadata is now included in version data on mirror 
indexes.
+
+- Downloaded files from mirrors can be included in exports with the 
``--include-mirrored-files`` option.
+
+- On import files for mirror indexes are now imported when they were included 
in the dump (see ``--include-mirrored-files``).
+
+
+Bug Fixes
+---------
+
+- Fix ``--no-root-pypi`` option when importing devpi data.
+
+- Fix pushing from mirror to an index when the file was removed and 
``mirror_use_external_urls`` is active.
+
+
+5.3.1 (2019-12-05)
+==================
+
+Bug Fixes
+---------
+
+- fix #688: on file upload existing metadata is only updated, not replaced.
+
+
 5.3.0 (2019-12-03)
 ==================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/PKG-INFO 
new/devpi-server-5.4.1/PKG-INFO
--- old/devpi-server-5.3.1/PKG-INFO     2019-12-05 14:39:07.000000000 +0100
+++ new/devpi-server-5.4.1/PKG-INFO     2020-03-26 09:54:56.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: devpi-server
-Version: 5.3.1
+Version: 5.4.1
 Summary: devpi-server: reliable private and pypi.org caching server
 Home-page: http://doc.devpi.net
 Maintainer: Holger Krekel, Florian Schulze
@@ -79,6 +79,50 @@
         
         .. towncrier release notes start
         
+        5.4.1 (2020-03-26)
+        ==================
+        
+        Bug Fixes
+        ---------
+        
+        - Import won't abort anymore when a base index was removed. The bases 
setting will be imported as is.
+        
+        
+        5.4.0 (2020-01-31)
+        ==================
+        
+        .. note::
+          This is the last feature release with Python 2.7 support!
+        
+          We will only make export related bugfix releases of 5.4.x.
+        
+        Features
+        --------
+        
+        - The ``requires_python`` metadata is now included in version data on 
mirror indexes.
+        
+        - Downloaded files from mirrors can be included in exports with the 
``--include-mirrored-files`` option.
+        
+        - On import files for mirror indexes are now imported when they were 
included in the dump (see ``--include-mirrored-files``).
+        
+        
+        Bug Fixes
+        ---------
+        
+        - Fix ``--no-root-pypi`` option when importing devpi data.
+        
+        - Fix pushing from mirror to an index when the file was removed and 
``mirror_use_external_urls`` is active.
+        
+        
+        5.3.1 (2019-12-05)
+        ==================
+        
+        Bug Fixes
+        ---------
+        
+        - fix #688: on file upload existing metadata is only updated, not 
replaced.
+        
+        
         5.3.0 (2019-12-03)
         ==================
         
@@ -155,103 +199,6 @@
         - The timeout when fetching the list of remote projects for a mirror 
index is set to a minimum of 30s by default and to 60s when running as replica. 
Other fetches of mirrors still use the timeout specified via 
``--request-timeout``.
         
         
-        5.1.0 (2019-08-05)
-        ==================
-        
-        Features
-        --------
-        
-        - Allow stage customizer plugins to filter projects and versions.
-        
-        - Replicas will use the multiple changelog endpoint added in 
devpi-server 4.9.0 to reduce the number of requests necessary to synchronize 
state.
-        
-        
-        5.0.0 (2019-06-28)
-        ==================
-        
-        Deprecations and Removals
-        -------------------------
-        
-        - fix #518: There are no URLs on PyPI anymore that need to be scraped 
or crawled, so the code for that was removed.
-        
-        - removed support for long deprecated ``acl_upload`` and ``bases`` 
mirror index option. They were only kept for compatibility with devpi-client <= 
2.4.1.
-        
-        - the ``--start``, ``--stop``, ``--status`` and ``--log`` options are 
deprecated. Use ``--gen-config`` to create example configuration files for 
various process managers.
-        
-        - removed long deprecated ``pypi_whitelist`` index option. It was only 
kept for compatibility with devpi-client <= 2.4.1.
-        
-        - deprecated Python 2.7 support. This is the last major version 
supporting Python 2.7. For upgrading to Python 3.x you have to export your data 
using your current setup with Python 2.7 and import it in a new installation 
with Python 3.x.
-        
-        
-        Features
-        --------
-        
-        - fix #249: unknown keys for index configuration now result in an 
error instead of being silently ignored.
-        
-        - fix #625: project registration is now optional. A file upload with 
twine or setuptools will automatically register the project.
-        
-        - fix #636: support ignore_bases argument for project listings.
-        
-        - support ``:AUTHENTICATED:`` for permissions. This resolves to any 
user which is logged in, regardless of username or groups.
-        
-        - added experimental support for stage customizers to let plugins add 
index types with customized behaviour. See ``BaseStageCustomizer`` in 
``model.py`` for the API and ``devpiserver_get_stage_customizer_classes`` for 
the registration.
-        
-        - support no_projects argument for index json requests. The list of 
projects will not be added to the result.
-        
-        - when credentials for the user are rejected, the error message now 
says so instead of claiming the user could not be found.
-        
-        
-        Other Changes
-        -------------
-        
-        - boolean values can now only be set via the following values: 
'false', 'no', 'true', 'yes' and actual booleans in the REST API. Before any 
string not matching 'false' and 'no' was converted into boolean true.
-        
-        - the default logging configuration now outputs to stdout instead of 
stderr.
-        
-        - major releases don't require an export/import cycle anymore except 
when explicitly announced. You should always make a backup though! When 
upgrading to devpi-server 5.0.0 you can keep the state as is and even downgrade 
to the last 4.9.x release if necessary. Don't forget to backup before upgrades!
-        
-        - the server secret isn't automatically persisted for new 
installations. A server restart invalidates login tokens. An existing 
installation will still use it's stored secret, but log a warning. Use 
``--secretfile`` to explicitly specify a persistent secret file.
-        
-        - the ``--storage`` option is now required when a storage plugin like 
devpi-postgresql is in use. It's recommended to use a configuration file for 
devpi-server to have everything in one place (see ``--configfile``).
-        
-        - for the ``--logger-cfg`` yaml loading we now use ``safe_load`` of 
``ruamel.yaml`` instead of ``load`` from ``pyyaml``.
-        
-        
-        4.9.0 (2019-04-26)
-        ==================
-        
-        Features
-        --------
-        
-        - implement #93: When creating a user, the password hash can be set 
directly with ``pwhash``. Upon database initialization allow setting root user 
password with ``--root-passwd`` and the password hash with 
``--root-passwd-hash`` options. Thanks to Andreas Palsson.
-        
-        - decouple devpi server version from database version to enable major 
releases that do not require export import of data
-        
-        - support ``--hard-links`` option during import for releases and doc 
zips.
-        
-        - added new endpoint to download multiple changelog entries at once. 
This will be used for faster replication in the future.
-        
-        - add option ``--replica-file-search-path`` to point to existing 
files. If a match is found it will be copied locally instead of fetched from 
the master. These files could be from a previous replication attempt or 
separately copied/restored.
-        
-        - add ``--hard-links`` support for replicas together with the 
``--replica-file-search-path`` option. When a matching file is found it's hard 
linked instead of writing a copy.
-        
-        
-        Bug Fixes
-        ---------
-        
-        - fix multiple triggering of mirror project names initialization.
-        
-        - fix updating time stamp of mirror project name cache when no project 
names have changed. This makes subsequent fetches actually use the cache 
instead of always fetching the data again from the mirror.
-        
-        - use timeout when waiting for data from master in replica on mirror 
simple pages.
-        
-        
-        Other Changes
-        -------------
-        
-        - slightly improved replica performance by removing unnecessary DB 
read and using fewer transactions.
-        
-        
 Keywords: pypi realtime cache server
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/__init__.py 
new/devpi-server-5.4.1/devpi_server/__init__.py
--- old/devpi-server-5.3.1/devpi_server/__init__.py     2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/__init__.py     2020-03-26 
09:54:54.000000000 +0100
@@ -1 +1 @@
-__version__ = '5.3.1'
+__version__ = '5.4.1'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/config.py 
new/devpi-server-5.4.1/devpi_server/config.py
--- old/devpi-server-5.3.1/devpi_server/config.py       2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/config.py       2020-03-26 
09:54:54.000000000 +0100
@@ -256,6 +256,10 @@
                  "This will export all users, indices, release files "
                  "(except for mirrors), test results and documentation.")
 
+    parser.addoption(
+        "--include-mirrored-files", action="store_true",
+        help="include downloaded files from mirror indexes in dump.")
+
 
 def add_import_options(parser, pluginmanager, standalone=True):
     if not standalone:
@@ -645,6 +649,14 @@
         return getattr(self.args, 'offline_mode', False)
 
     @property
+    def replica_cert(self):
+        return getattr(self.args, 'replica_cert', None)
+
+    @property
+    def replica_max_retries(self):
+        return getattr(self.args, 'replica_max_retries', None)
+
+    @property
     def requests_only(self):
         return getattr(self.args, 'requests_only', False)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/extpypi.py 
new/devpi-server-5.4.1/devpi_server/extpypi.py
--- old/devpi-server-5.3.1/devpi_server/extpypi.py      2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/extpypi.py      2020-03-26 
09:54:54.000000000 +0100
@@ -373,7 +373,7 @@
         if self.offline and links is None:
             raise self.UpstreamError("offline mode")
         if self.offline or not is_expired:
-            if project not in self._offline_logging:
+            if self.offline and project not in self._offline_logging:
                 threadlog.debug(
                     "using stale links for %r due to offline mode", project)
                 self._offline_logging.add(project)
@@ -531,6 +531,8 @@
                 if not verdata:
                     verdata['name'] = project
                     verdata['version'] = version
+                if sm.require_python is not None:
+                    verdata['requires_python'] = sm.require_python
                 elinks = verdata.setdefault("+elinks", [])
                 entrypath = sm._url.path
                 elinks.append({"rel": "releasefile", "entrypath": entrypath})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/importexport.py 
new/devpi-server-5.4.1/devpi_server/importexport.py
--- old/devpi-server-5.3.1/devpi_server/importexport.py 2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/importexport.py 2020-03-26 
09:54:54.000000000 +0100
@@ -9,11 +9,13 @@
 from devpi_common.validation import normalize_name
 from devpi_common.metadata import BasenameMeta
 from devpi_common.types import parse_hash_spec
+from devpi_common.url import URL
 from devpi_server import __version__ as server_version
 from devpi_server.model import is_valid_name
 from devpi_server.model import get_stage_customizer_classes
 from .config import MyArgumentParser
 from .config import add_configfile_option
+from .config import add_export_options
 from .config import add_hard_links_option
 from .config import add_help_option
 from .config import add_import_options
@@ -81,6 +83,7 @@
         add_help_option(parser, pluginmanager)
         add_configfile_option(parser, pluginmanager)
         add_storage_options(parser, pluginmanager)
+        add_export_options(parser, pluginmanager)
         add_hard_links_option(parser, pluginmanager)
         parser.add_argument("directory")
         config = parseoptions(pluginmanager, argv, parser=parser)
@@ -238,10 +241,16 @@
         self.indexmeta = exporter.export_indexes[stage.name] = {}
         self.indexmeta["indexconfig"] = stage.ixconfig
 
-    def dump(self):
+    def should_dump(self):
         if self.stage.ixconfig["type"] == "mirror":
-            projects = []
-        else:
+            if not self.exporter.config.args.include_mirrored_files:
+                return False
+        return True
+
+    def dump(self):
+        projects = []
+        if self.should_dump():
+            self.stage.offline = True
             self.indexmeta["projects"] = {}
             self.indexmeta["files"] = []
             projects = self.stage.list_projects_perstage()
@@ -263,7 +272,9 @@
                 self.basedir.ensure(dir=1)
                 self.dump_releasefiles(linkstore)
                 self.dump_toxresults(linkstore)
-                entry = self.stage.get_doczip_entry(vername, version)
+                entry = None
+                if hasattr(self.stage, 'get_doczip_entry'):
+                    entry = self.stage.get_doczip_entry(vername, version)
                 if entry:
                     self.dump_docfile(vername, version, entry)
         self.exporter.completed("index %r" % self.stage.name)
@@ -271,6 +282,8 @@
     def dump_releasefiles(self, linkstore):
         for link in linkstore.get_links(rel="releasefile"):
             entry = self.exporter.filestore.get_file_entry(link.entrypath)
+            if not entry.last_modified:
+                continue
             assert entry.file_exists(), entry.relpath
             relpath = self.exporter.copy_file(
                 entry,
@@ -334,10 +347,8 @@
         total_num_projects = 0
         total_num_files = 0
         for idx_name, idx in self.import_indexes.items():
-            if idx['indexconfig']['type'] == 'mirror':
-                continue
-            num_projects = len(idx['projects'])
-            num_files = len(idx['files'])
+            num_projects = len(idx.get('projects', {}))
+            num_files = len(idx.get('files', []))
             self.tw.line(
                 'Index %s has %d projects and %d files'
                 % (idx_name, num_projects, num_files))
@@ -409,30 +420,42 @@
         # first create all users
         with self.xom.keyfs.transaction(write=True):
             for username, userconfig in self.import_users.items():
+                user = None
                 if username == "root":
                     user = self.xom.model.get_user(username)
-                else:
+                if user is None:
                     user = self.xom.model.create_user(username, password="")
                 user._set(userconfig)
 
         # memorize index inheritance structure
         tree = IndexTree()
+        indexes = set(self.import_indexes)
         with self.xom.keyfs.transaction(write=False):
             stage = self.xom.model.getstage("root/pypi")
         if stage is not None:
+            indexes.add("root/pypi")
             tree.add("root/pypi")
+        missing_bases = set()
         for stagename, import_index in self.import_indexes.items():
             bases = import_index["indexconfig"].get("bases")
-            tree.add(stagename, bases)
+            if bases is None:
+                tree.add(stagename)
+            else:
+                existing_bases = set(bases).intersection(indexes)
+                missing_bases.update(set(bases) - existing_bases)
+                tree.add(stagename, existing_bases)
+
+        if missing_bases:
+            self.warn(
+                "The following indexes are in bases, but don't exist "
+                "in the import data: %s" % ", ".join(sorted(missing_bases)))
 
         # create stages in inheritance/root-first order
         stages = []
         with self.xom.keyfs.transaction(write=True):
             for stagename in tree.iternames():
-                if stagename == "root/pypi":
-                    stage = self.xom.model.getstage(stagename)
-                    if stage is not None:
-                        continue
+                if stagename == "root/pypi" and stagename not in 
self.import_indexes:
+                    continue
                 import_index = self.import_indexes[stagename]
                 indexconfig = dict(import_index["indexconfig"])
                 if indexconfig['type'] in self.types_to_skip:
@@ -454,22 +477,30 @@
                 # newer versions don't. To support exports from both we
                 # have the default None value
                 bases = indexconfig.pop('bases', None)
-                stage = user.create_stage(index, **indexconfig)
+                stage = None
+                if stagename == "root/pypi":
+                    stage = self.xom.model.getstage(stagename)
+                    if stage is not None:
+                        stage.modify(**indexconfig)
+                    elif self.xom.config.args.no_root_pypi:
+                        continue
+                if stage is None:
+                    stage = user.create_stage(index, **indexconfig)
                 if "bases" in import_index["indexconfig"]:
-                    indexconfig = stage.ixconfig
-                    indexconfig["bases"] = bases
-                    stage.modify(**indexconfig)
+                    # we are changing bases directly to allow import with
+                    # removed bases without changing the data from the export
+                    with stage.user.key.update() as userconfig:
+                        indexconfig = userconfig['indexes'][stage.index]
+                        indexconfig["bases"] = tuple(bases)
                 stages.append(stage)
         del tree
 
         # create projects and releasefiles for each index
         for stage in stages:
-            if stage.ixconfig["type"] == "mirror":
-                continue
             imported_files = set()
             import_index = self.import_indexes[stage.name]
-            projects = import_index["projects"]
-            files = import_index["files"]
+            projects = import_index.get("projects", {})
+            files = import_index.get("files", [])
             for project, versions in self.iter_projects_normalized(projects):
                 with self.xom.keyfs.transaction(write=True):
                     for version, versiondata in versions.items():
@@ -483,13 +514,16 @@
                                       "version, setting derived %r" %
                                       (name, version))
                             versiondata["version"] = version
-                        stage.set_versiondata(versiondata)
+                        if hasattr(stage, 'set_versiondata'):
+                            stage.set_versiondata(versiondata)
+                        else:
+                            stage.add_project_name(versiondata["name"])
 
                     # import release files
                     for filedesc in files:
                         if normalize_name(filedesc["projectname"]) == 
normalize_name(project):
                             imported_files.add(filedesc["relpath"])
-                            self.import_filedesc(stage, filedesc)
+                            self.import_filedesc(stage, filedesc, versions)
             missing = set(x["relpath"] for x in files) - imported_files
             if missing:
                 fatal(
@@ -511,8 +545,7 @@
         self.tw.line("wait_for_events: importing finished"
                      "; latest_serial = %s" % latest_serial)
 
-    def import_filedesc(self, stage, filedesc):
-        assert stage.ixconfig["type"] != "mirror"
+    def import_filedesc(self, stage, filedesc, versions):
         rel = filedesc["relpath"]
         project = filedesc["projectname"]
         p = self.import_rootdir.join(rel)
@@ -530,15 +563,33 @@
             else:
                 version = filedesc["version"]
 
-            link = stage.store_releasefile(project, version,
-                                           p.basename, data,
-                                           
last_modified=mapping["last_modified"])
+            if hasattr(stage, 'store_releasefile'):
+                link = stage.store_releasefile(
+                    project, version,
+                    p.basename, data,
+                    last_modified=mapping["last_modified"])
+                entry = link.entry
+            else:
+                link = None
+                url = 
URL(mapping['url']).replace(fragment=mapping['hash_spec'])
+                entry = self.xom.filestore.maplink(
+                    url, stage.username, stage.index, project)
+                entry.file_set_content(data, mapping["last_modified"])
+                (_, links_with_require_python, serial) = 
stage._load_cache_links(project)
+                if links_with_require_python is None:
+                    links_with_require_python = []
+                links = [(url.basename, entry.relpath)]
+                requires_python = [versions[version].get('requires_python')]
+                for key, href, require_python in links_with_require_python:
+                    links.append((key, href))
+                    requires_python.append(require_python)
+                stage._save_cache_links(project, links, requires_python, 
serial)
             # devpi-server-2.1 exported with md5 checksums
             if "md5" in mapping:
                 assert "hash_spec" not in mapping
                 mapping["hash_spec"] = "md5=" + mapping["md5"]
             hash_algo, hash_value = parse_hash_spec(mapping["hash_spec"])
-            digest = hash_algo(link.entry.file_get_content()).hexdigest()
+            digest = hash_algo(entry.file_get_content()).hexdigest()
             if digest != hash_value:
                 fatal("File %s has bad checksum %s, expected %s" % (
                       p, digest, hash_value))
@@ -557,11 +608,12 @@
             link = stage.store_toxresult(link, json.loads(data.decode("utf8")))
         else:
             fatal("unknown file type: %s" % (type,))
-        history_log = filedesc.get('log')
-        if history_log is None:
-            link.add_log('upload', '<import>', dst=stage.name)
-        else:
-            link.add_logs(history_log)
+        if link is not None:
+            history_log = filedesc.get('log')
+            if history_log is None:
+                link.add_log('upload', '<import>', dst=stage.name)
+            else:
+                link.add_logs(history_log)
 
 
 class IndexTree:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/keyfs.py 
new/devpi-server-5.4.1/devpi_server/keyfs.py
--- old/devpi-server-5.3.1/devpi_server/keyfs.py        2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/keyfs.py        2020-03-26 
09:54:54.000000000 +0100
@@ -3,7 +3,7 @@
 basic python types based on parametrizable keys.  Multiple
 read Transactions can execute concurrently while at most one
 write Transaction is ongoing.  Each Transaction will see a consistent
-view of key/values refering to the point in time it was started,
+view of key/values referring to the point in time it was started,
 independent from any future changes.
 """
 from __future__ import unicode_literals
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/main.py 
new/devpi-server-5.4.1/devpi_server/main.py
--- old/devpi-server-5.3.1/devpi_server/main.py 2019-12-05 14:39:00.000000000 
+0100
+++ new/devpi-server-5.4.1/devpi_server/main.py 2020-03-26 09:54:54.000000000 
+0100
@@ -154,7 +154,8 @@
 
     xom = xom_from_config(config)
 
-    init_default_indexes(xom)
+    if args.init:
+        init_default_indexes(xom)
 
     if args.start or args.stop or args.log or args.status:
         xprocdir = config.serverdir.join(".xproc")
@@ -358,15 +359,16 @@
 
     def new_http_session(self, component_name, max_retries=None):
         session = new_requests_session(agent=(component_name, server_version), 
max_retries=max_retries)
-        session.cert = self.config.args.replica_cert
+        session.cert = self.config.replica_cert
         return session
 
     @cached_property
     def _httpsession(self):
-        return self.new_http_session("server", 
max_retries=self.config.args.replica_max_retries)
+        max_retries = self.config.replica_max_retries
+        return self.new_http_session("server", max_retries=max_retries)
 
     def httpget(self, url, allow_redirects, timeout=None, extra_headers=None):
-        if self.config.args.offline_mode:
+        if self.config.offline_mode:
             resp = Response()
             resp.status_code = 503  # service unavailable
             return resp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/model.py 
new/devpi-server-5.4.1/devpi_server/model.py
--- old/devpi-server-5.3.1/devpi_server/model.py        2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/model.py        2020-03-26 
09:54:54.000000000 +0100
@@ -834,13 +834,12 @@
             return newconfig
 
     def modify(self, index=None, **kw):
+        if self.customizer.readonly:
+            raise ReadonlyIndex("index is marked read only")
         newconfig = self._modify(**kw)
         threadlog.info("modified index %s: %s", self.name, newconfig)
         return newconfig
 
-        if self.customizer.readonly:
-            raise ReadonlyIndex("index is marked read only")
-
     def op_sro(self, opname, **kw):
         if "project" in kw:
             project = normalize_name(kw["project"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server/views.py 
new/devpi-server-5.4.1/devpi_server/views.py
--- old/devpi-server-5.3.1/devpi_server/views.py        2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server/views.py        2020-03-26 
09:54:54.000000000 +0100
@@ -1397,11 +1397,16 @@
         # the file was downloaded before but locally removed, so put
         # it back in place without creating a new serial
         # we need a direct write connection to use the io_file_* methods
-        with xom.keyfs._storage.get_connection(write=True) as conn:
-            conn.io_file_set(entry._storepath, content)
+        if tx is not None:
+            tx.conn.io_file_set(entry._storepath, content)
             threadlog.debug(
                 "put missing file back into place: %s", entry._storepath)
-            conn.commit_files_without_increasing_serial()
+        else:
+            with xom.keyfs._storage.get_connection(write=True) as conn:
+                conn.io_file_set(entry._storepath, content)
+                threadlog.debug(
+                    "put missing file back into place: %s", entry._storepath)
+                conn.commit_files_without_increasing_serial()
 
 
 def iter_remote_file_replica(xom, entry):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server.egg-info/PKG-INFO 
new/devpi-server-5.4.1/devpi_server.egg-info/PKG-INFO
--- old/devpi-server-5.3.1/devpi_server.egg-info/PKG-INFO       2019-12-05 
14:39:07.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server.egg-info/PKG-INFO       2020-03-26 
09:54:55.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: devpi-server
-Version: 5.3.1
+Version: 5.4.1
 Summary: devpi-server: reliable private and pypi.org caching server
 Home-page: http://doc.devpi.net
 Maintainer: Holger Krekel, Florian Schulze
@@ -79,6 +79,50 @@
         
         .. towncrier release notes start
         
+        5.4.1 (2020-03-26)
+        ==================
+        
+        Bug Fixes
+        ---------
+        
+        - Import won't abort anymore when a base index was removed. The bases 
setting will be imported as is.
+        
+        
+        5.4.0 (2020-01-31)
+        ==================
+        
+        .. note::
+          This is the last feature release with Python 2.7 support!
+        
+          We will only make export related bugfix releases of 5.4.x.
+        
+        Features
+        --------
+        
+        - The ``requires_python`` metadata is now included in version data on 
mirror indexes.
+        
+        - Downloaded files from mirrors can be included in exports with the 
``--include-mirrored-files`` option.
+        
+        - On import files for mirror indexes are now imported when they were 
included in the dump (see ``--include-mirrored-files``).
+        
+        
+        Bug Fixes
+        ---------
+        
+        - Fix ``--no-root-pypi`` option when importing devpi data.
+        
+        - Fix pushing from mirror to an index when the file was removed and 
``mirror_use_external_urls`` is active.
+        
+        
+        5.3.1 (2019-12-05)
+        ==================
+        
+        Bug Fixes
+        ---------
+        
+        - fix #688: on file upload existing metadata is only updated, not 
replaced.
+        
+        
         5.3.0 (2019-12-03)
         ==================
         
@@ -155,103 +199,6 @@
         - The timeout when fetching the list of remote projects for a mirror 
index is set to a minimum of 30s by default and to 60s when running as replica. 
Other fetches of mirrors still use the timeout specified via 
``--request-timeout``.
         
         
-        5.1.0 (2019-08-05)
-        ==================
-        
-        Features
-        --------
-        
-        - Allow stage customizer plugins to filter projects and versions.
-        
-        - Replicas will use the multiple changelog endpoint added in 
devpi-server 4.9.0 to reduce the number of requests necessary to synchronize 
state.
-        
-        
-        5.0.0 (2019-06-28)
-        ==================
-        
-        Deprecations and Removals
-        -------------------------
-        
-        - fix #518: There are no URLs on PyPI anymore that need to be scraped 
or crawled, so the code for that was removed.
-        
-        - removed support for long deprecated ``acl_upload`` and ``bases`` 
mirror index option. They were only kept for compatibility with devpi-client <= 
2.4.1.
-        
-        - the ``--start``, ``--stop``, ``--status`` and ``--log`` options are 
deprecated. Use ``--gen-config`` to create example configuration files for 
various process managers.
-        
-        - removed long deprecated ``pypi_whitelist`` index option. It was only 
kept for compatibility with devpi-client <= 2.4.1.
-        
-        - deprecated Python 2.7 support. This is the last major version 
supporting Python 2.7. For upgrading to Python 3.x you have to export your data 
using your current setup with Python 2.7 and import it in a new installation 
with Python 3.x.
-        
-        
-        Features
-        --------
-        
-        - fix #249: unknown keys for index configuration now result in an 
error instead of being silently ignored.
-        
-        - fix #625: project registration is now optional. A file upload with 
twine or setuptools will automatically register the project.
-        
-        - fix #636: support ignore_bases argument for project listings.
-        
-        - support ``:AUTHENTICATED:`` for permissions. This resolves to any 
user which is logged in, regardless of username or groups.
-        
-        - added experimental support for stage customizers to let plugins add 
index types with customized behaviour. See ``BaseStageCustomizer`` in 
``model.py`` for the API and ``devpiserver_get_stage_customizer_classes`` for 
the registration.
-        
-        - support no_projects argument for index json requests. The list of 
projects will not be added to the result.
-        
-        - when credentials for the user are rejected, the error message now 
says so instead of claiming the user could not be found.
-        
-        
-        Other Changes
-        -------------
-        
-        - boolean values can now only be set via the following values: 
'false', 'no', 'true', 'yes' and actual booleans in the REST API. Before any 
string not matching 'false' and 'no' was converted into boolean true.
-        
-        - the default logging configuration now outputs to stdout instead of 
stderr.
-        
-        - major releases don't require an export/import cycle anymore except 
when explicitly announced. You should always make a backup though! When 
upgrading to devpi-server 5.0.0 you can keep the state as is and even downgrade 
to the last 4.9.x release if necessary. Don't forget to backup before upgrades!
-        
-        - the server secret isn't automatically persisted for new 
installations. A server restart invalidates login tokens. An existing 
installation will still use it's stored secret, but log a warning. Use 
``--secretfile`` to explicitly specify a persistent secret file.
-        
-        - the ``--storage`` option is now required when a storage plugin like 
devpi-postgresql is in use. It's recommended to use a configuration file for 
devpi-server to have everything in one place (see ``--configfile``).
-        
-        - for the ``--logger-cfg`` yaml loading we now use ``safe_load`` of 
``ruamel.yaml`` instead of ``load`` from ``pyyaml``.
-        
-        
-        4.9.0 (2019-04-26)
-        ==================
-        
-        Features
-        --------
-        
-        - implement #93: When creating a user, the password hash can be set 
directly with ``pwhash``. Upon database initialization allow setting root user 
password with ``--root-passwd`` and the password hash with 
``--root-passwd-hash`` options. Thanks to Andreas Palsson.
-        
-        - decouple devpi server version from database version to enable major 
releases that do not require export import of data
-        
-        - support ``--hard-links`` option during import for releases and doc 
zips.
-        
-        - added new endpoint to download multiple changelog entries at once. 
This will be used for faster replication in the future.
-        
-        - add option ``--replica-file-search-path`` to point to existing 
files. If a match is found it will be copied locally instead of fetched from 
the master. These files could be from a previous replication attempt or 
separately copied/restored.
-        
-        - add ``--hard-links`` support for replicas together with the 
``--replica-file-search-path`` option. When a matching file is found it's hard 
linked instead of writing a copy.
-        
-        
-        Bug Fixes
-        ---------
-        
-        - fix multiple triggering of mirror project names initialization.
-        
-        - fix updating time stamp of mirror project name cache when no project 
names have changed. This makes subsequent fetches actually use the cache 
instead of always fetching the data again from the mirror.
-        
-        - use timeout when waiting for data from master in replica on mirror 
simple pages.
-        
-        
-        Other Changes
-        -------------
-        
-        - slightly improved replica performance by removing unnecessary DB 
read and using fewer transactions.
-        
-        
 Keywords: pypi realtime cache server
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/devpi_server.egg-info/SOURCES.txt 
new/devpi-server-5.4.1/devpi_server.egg-info/SOURCES.txt
--- old/devpi-server-5.3.1/devpi_server.egg-info/SOURCES.txt    2019-12-05 
14:39:07.000000000 +0100
+++ new/devpi-server-5.4.1/devpi_server.egg-info/SOURCES.txt    2020-03-26 
09:54:55.000000000 +0100
@@ -2,6 +2,7 @@
 LICENSE
 MANIFEST.in
 README.rst
+pyproject.toml
 setup.cfg
 setup.py
 tox.ini
@@ -89,11 +90,17 @@
 test_devpi_server/importexportdata/badindexname/dataindex.json
 test_devpi_server/importexportdata/badusername/dataindex.json
 test_devpi_server/importexportdata/basescycle/dataindex.json
+test_devpi_server/importexportdata/deletedbase/dataindex.json
+test_devpi_server/importexportdata/mirrordata/dataindex.json
+test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz
+test_devpi_server/importexportdata/modifiedpypi/dataindex.json
 test_devpi_server/importexportdata/normalization/dataindex.json
 
test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz
 test_devpi_server/importexportdata/normalization_merge/dataindex.json
 
test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz
 
test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz
+test_devpi_server/importexportdata/norootpypi/dataindex.json
+test_devpi_server/importexportdata/nouser/dataindex.json
 test_devpi_server/importexportdata/removedindexplugin/dataindex.json
 
test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz
 test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/pyproject.toml 
new/devpi-server-5.4.1/pyproject.toml
--- old/devpi-server-5.3.1/pyproject.toml       1970-01-01 01:00:00.000000000 
+0100
+++ new/devpi-server-5.4.1/pyproject.toml       2020-03-26 09:54:54.000000000 
+0100
@@ -0,0 +1,26 @@
+[tool.towncrier]
+package = "devpi_server"
+filename = "CHANGELOG"
+directory = "news/"
+title_format = "{version} ({project_date})"
+template = "news/_template.rst"
+
+  [[tool.towncrier.type]]
+  directory = "removal"
+  name = "Deprecations and Removals"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "feature"
+  name = "Features"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "bugfix"
+  name = "Bug Fixes"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "other"
+  name = "Other Changes"
+  showcontent = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/setup.py 
new/devpi-server-5.4.1/setup.py
--- old/devpi-server-5.3.1/setup.py     2019-12-05 14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/setup.py     2020-03-26 09:54:54.000000000 +0100
@@ -4,7 +4,7 @@
 import re
 import io
 import sys
-from setuptools import setup, find_packages
+from setuptools import setup
 
 
 def get_changelog():
@@ -48,10 +48,15 @@
       keywords="pypi realtime cache server",
       long_description="\n\n".join([README, CHANGELOG]),
       url="http://doc.devpi.net";,
-      version='5.3.1',
+      version='5.4.1',
       maintainer="Holger Krekel, Florian Schulze",
       maintainer_email="[email protected]",
-      packages=find_packages(),
+      packages=[
+        'devpi_server',
+        'devpi_server.cfg',
+        'devpi_server.vendor',
+        'pytest_devpi_server',
+        'test_devpi_server'],
       include_package_data=True,
       zip_safe=False,
       license="MIT",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/test_devpi_server/conftest.py 
new/devpi-server-5.4.1/test_devpi_server/conftest.py
--- old/devpi-server-5.3.1/test_devpi_server/conftest.py        2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/test_devpi_server/conftest.py        2020-03-26 
09:54:54.000000000 +0100
@@ -96,13 +96,12 @@
     return caplog
 
 @pytest.fixture
-def gentmp(request):
-    tmpdirhandler = request.config._tmpdirhandler
+def gentmp(request, tmpdir_factory):
     cache = []
     def gentmp(name=None):
         if not cache:
             prefix = re.sub(r"[\W]", "_", request.node.name)
-            basedir = tmpdirhandler.mktemp(prefix, numbered=True)
+            basedir = tmpdir_factory.mktemp(prefix, numbered=True)
             cache.append(basedir)
         else:
             basedir = cache[0]
@@ -234,10 +233,9 @@
                     lambda self: set())
             add_pypistage_mocks(monkeypatch, httpget)
         # initialize default indexes
-        from devpi_server.main import set_default_indexes
+        from devpi_server.main import init_default_indexes
         if not xom.config.args.master_url:
-            with xom.keyfs.transaction(write=True):
-                set_default_indexes(xom.model)
+            init_default_indexes(xom)
         if request.node.get_closest_marker("with_replica_thread"):
             from devpi_server.replica import ReplicaThread
             rt = ReplicaThread(xom)
@@ -912,7 +910,7 @@
         "The port %s on host %s didn't become accessible" % (port, host))
 
 
[email protected]_fixture(scope="module")
[email protected]_fixture(scope="class")
 def server_directory():
     import tempfile
     srvdir = py.path.local(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/deletedbase/dataindex.json
 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/deletedbase/dataindex.json
--- 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/deletedbase/dataindex.json
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/deletedbase/dataindex.json
        2020-03-26 09:54:54.000000000 +0100
@@ -0,0 +1,67 @@
+{
+  "users": {
+    "root": {
+      "username": "root",
+      "pwsalt": "uBLR5DKWtF6ro4H7h3FObQ==",
+      "pwhash": 
"667a21361c2d700d6451ecc77c837a595329a74fa5b49c396c6aa6c49600bad6"
+    }
+  },
+  "secret": "ItYAZLP23wgW4/F2uFMNJ+ffjYhIiZg3T3HJlIzVTB8=",
+  "indexes": {
+    "root/dev1": {
+      "files": [],
+      "indexconfig": {
+        "bases": ["root/removed"], "pypi_whitelist": [],
+        "acl_upload": ["root"],
+        "volatile": true, "type": "stage"
+      },
+      "projects": []
+    },
+    "root/dev2": {
+      "files": [],
+      "indexconfig": {
+        "bases": ["root/removed"], "pypi_whitelist": [],
+        "acl_upload": ["root"],
+        "volatile": true, "type": "stage"
+      },
+      "projects": []
+    },
+    "root/dev3": {
+      "files": [],
+      "indexconfig": {
+        "bases": ["root/dev2"], "pypi_whitelist": [],
+        "acl_upload": ["root"],
+        "volatile": true, "type": "stage"
+      },
+      "projects": []
+    },
+    "root/dev4": {
+      "files": [],
+      "indexconfig": {
+        "bases": ["root/removed", "root/pypi"], "pypi_whitelist": [],
+        "acl_upload": ["root"],
+        "volatile": true, "type": "stage"
+      },
+      "projects": []
+    },
+    "root/dev5": {
+      "files": [],
+      "indexconfig": {
+        "bases": ["root/removed", "root/dev2"], "pypi_whitelist": [],
+        "acl_upload": ["root"],
+        "volatile": true, "type": "stage"
+      },
+      "projects": []
+    }
+  },
+  "pythonversion": [
+    3,
+    4,
+    3,
+    "final",
+    0
+  ],
+  "dumpversion": "2",
+  "devpi_server": "3.0.0b1",
+  "uuid": "3885c05ceae34ed4b7574da2a1bd1e0f"
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/mirrordata/dataindex.json
 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/mirrordata/dataindex.json
--- 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/mirrordata/dataindex.json
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/mirrordata/dataindex.json
 2020-03-26 09:54:54.000000000 +0100
@@ -0,0 +1,53 @@
+{
+  "users": {
+    "root": {
+      "pwhash": 
"$argon2id$v=19$m=102400,t=2,p=8$rzUGQKiVsrZ2DkEIIQTA+A$tX1MOZ8fl1T4QrfgEwXj9Q",
+      "username": "root"
+    }
+  },
+  "indexes": {
+    "root/pypi": {
+      "indexconfig": {
+        "type": "mirror",
+        "volatile": false,
+        "title": "PyPI",
+        "mirror_url": "https://pypi.org/simple/";,
+        "mirror_web_url_fmt": "https://pypi.org/project/{name}/";
+      },
+      "projects": {
+        "dddttt": {
+          "0.1.dev1": {
+            "name": "dddttt",
+            "version": "0.1.dev1"
+          }
+        }
+      },
+      "files": [
+        {
+          "version": "0.1.dev1",
+          "entrymapping": {
+            "url": 
"https://files.pythonhosted.org/packages/d0/c9/64c9fae84124fec0c08fa1b2db6259fb1ee894d4f41b5d0a79242c7709b7/dddttt-0.1.dev1.tar.gz";,
+            "hash_spec": 
"sha256=1007f0cd10aaa290eb84f092e786639bbe930b7f2169f51f755a0f50a6aba489",
+            "project": "dddttt",
+            "version": "0.1.dev1",
+            "last_modified": "Sat, 23 Apr 2016 07:01:31 GMT"
+          },
+          "log": [],
+          "type": "releasefile",
+          "projectname": "dddttt",
+          "relpath": "root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz"
+        }
+      ]
+    }
+  },
+  "dumpversion": "2",
+  "pythonversion": [
+    3,
+    6,
+    8,
+    "final",
+    0
+  ],
+  "devpi_server": "5.3.1",
+  "uuid": "0846290854e347f782fd65dd529cde40"
+}
\ No newline at end of file
Binary files 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz
 and 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/modifiedpypi/dataindex.json
 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/modifiedpypi/dataindex.json
--- 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/modifiedpypi/dataindex.json
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/modifiedpypi/dataindex.json
       2020-03-26 09:54:54.000000000 +0100
@@ -0,0 +1,31 @@
+{
+  "users": {
+    "root": {
+      "pwhash": 
"$argon2id$v=19$m=102400,t=2,p=8$rzUGQKiVsrZ2DkEIIQTA+A$tX1MOZ8fl1T4QrfgEwXj9Q",
+      "username": "root"
+    }
+  },
+  "indexes": {
+    "root/pypi": {
+      "indexconfig": {
+        "type": "mirror",
+        "volatile": false,
+        "title": "Modified PyPI",
+        "mirror_url": "https://example.com/simple/";,
+        "mirror_web_url_fmt": "https://example.com/project/{name}/";
+      },
+      "projects": {},
+      "files": []
+    }
+  },
+  "dumpversion": "2",
+  "pythonversion": [
+    3,
+    6,
+    8,
+    "final",
+    0
+  ],
+  "devpi_server": "5.3.1",
+  "uuid": "0846290854e347f782fd65dd529cde40"
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/norootpypi/dataindex.json
 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/norootpypi/dataindex.json
--- 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/norootpypi/dataindex.json
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/norootpypi/dataindex.json
 2020-03-26 09:54:54.000000000 +0100
@@ -0,0 +1,19 @@
+{
+  "users": {
+    "root": {
+      "pwhash": 
"$argon2id$v=19$m=102400,t=2,p=8$0noPYaw1Zqy1traW0nrvvQ$Xle8OjVpx6ZNHsNgAoEhww",
+      "username": "root"
+    }
+  },
+  "indexes": {},
+  "dumpversion": "2",
+  "pythonversion": [
+    3,
+    6,
+    8,
+    "final",
+    0
+  ],
+  "devpi_server": "5.3.1",
+  "uuid": "b63921e918684b23ae62b9a66095e382"
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/nouser/dataindex.json 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/nouser/dataindex.json
--- 
old/devpi-server-5.3.1/test_devpi_server/importexportdata/nouser/dataindex.json 
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/devpi-server-5.4.1/test_devpi_server/importexportdata/nouser/dataindex.json 
    2020-03-26 09:54:54.000000000 +0100
@@ -0,0 +1,15 @@
+{
+  "users": {
+  },
+  "indexes": {},
+  "dumpversion": "2",
+  "pythonversion": [
+    3,
+    6,
+    8,
+    "final",
+    0
+  ],
+  "devpi_server": "5.3.1",
+  "uuid": "b63921e918684b23ae62b9a66095e382"
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/test_importexport.py 
new/devpi-server-5.4.1/test_devpi_server/test_importexport.py
--- old/devpi-server-5.3.1/test_devpi_server/test_importexport.py       
2019-12-05 14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/test_devpi_server/test_importexport.py       
2020-03-26 09:54:54.000000000 +0100
@@ -11,8 +11,10 @@
 from devpi_server.importexport import IndexTree
 from devpi_server.importexport import do_export, do_import
 from devpi_server.main import Fatal
+from devpi_server.readonly import get_mutable_deepcopy
 from devpi_common.archive import Archive, zip_dict
 from devpi_common.metadata import Version
+from devpi_common.url import URL
 
 import devpi_server
 
@@ -210,6 +212,7 @@
     with pytest.raises(Fatal):
         do_import(tmpdir, xom)
 
+
 class TestIndexTree:
     def test_basic(self):
         tree = IndexTree()
@@ -398,6 +401,21 @@
             stage = mapp.xom.model.getstage('root/dev')
             assert stage.ixconfig['bases'] == ('root/dev',)
 
+    def test_deleted_base(self, caplog, impexp):
+        mapp = impexp.import_testdata('deletedbase')
+        with mapp.xom.keyfs.transaction(write=False):
+            assert mapp.xom.model.getstage('root/removed') is None
+            stage = mapp.xom.model.getstage('root/dev1')
+            assert stage.ixconfig['bases'] == ('root/removed',)
+            stage = mapp.xom.model.getstage('root/dev2')
+            assert stage.ixconfig['bases'] == ('root/removed',)
+            stage = mapp.xom.model.getstage('root/dev3')
+            assert stage.ixconfig['bases'] == ('root/dev2',)
+            stage = mapp.xom.model.getstage('root/dev4')
+            assert stage.ixconfig['bases'] == ('root/removed', 'root/pypi')
+            stage = mapp.xom.model.getstage('root/dev5')
+            assert stage.ixconfig['bases'] == ('root/removed', 'root/dev2')
+
     def test_bad_username(self, caplog, impexp):
         with pytest.raises(SystemExit):
             impexp.import_testdata('badusername')
@@ -414,6 +432,94 @@
         (record,) = caplog.getrecords('You could also try to edit')
         assert 'dataindex.json' in record.message
 
+    @pytest.mark.parametrize("norootpypi", [False, True])
+    def test_import_no_user(self, caplog, impexp, norootpypi):
+        from devpi_server.main import _pypi_ixconfig_default
+        options = ()
+        if norootpypi:
+            options = ('--no-root-pypi',)
+        mapp = impexp.import_testdata('nouser', options=options)
+        with mapp.xom.keyfs.transaction(write=False):
+            user = mapp.xom.model.get_user("root")
+            assert user is not None
+            stage = mapp.xom.model.getstage("root/pypi")
+            if norootpypi:
+                assert stage is None
+            else:
+                assert stage.ixconfig == _pypi_ixconfig_default
+
+    @pytest.mark.parametrize("norootpypi", [False, True])
+    def test_import_no_root_pypi(self, caplog, impexp, norootpypi):
+        from devpi_server.main import _pypi_ixconfig_default
+        options = ()
+        if norootpypi:
+            options = ('--no-root-pypi',)
+        mapp = impexp.import_testdata('nouser', options=options)
+        with mapp.xom.keyfs.transaction(write=False):
+            user = mapp.xom.model.get_user("root")
+            assert user is not None
+            stage = mapp.xom.model.getstage("root/pypi")
+            if norootpypi:
+                assert stage is None
+            else:
+                assert stage.ixconfig == _pypi_ixconfig_default
+
+    def test_include_mirrordata(self, caplog, makeimpexp, maketestapp, 
pypistage):
+        impexp = makeimpexp(options=('--include-mirrored-files',))
+        mapp1 = impexp.mapp1
+        testapp = maketestapp(mapp1.xom)
+        api = mapp1.use('root/pypi')
+        pypistage.mock_simple(
+            "package",
+            '<a href="/package-1.0.zip" />\n'
+            '<a 
href="/package-1.1.zip#sha256=a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"
 />\n'
+            + '<a 
href="/package-2.0.zip#sha256=b3a8e0e1f9ab1bfe3a36f231f676f78bb30a519d2b21e6c530c0eee8ebb4a5d0"
 data-requires-python="&gt;=3.5" />')
+        pypistage.mock_extfile("/package-1.1.zip", b"123")
+        pypistage.mock_extfile("/package-2.0.zip", b"456")
+        r = testapp.get(api.index + "/+simple/package/")
+        assert r.status_code == 200
+        # fetch some files, so they are included in the dump
+        (_, link1, link2) = sorted(x.attrs['href'] for x in r.html.select('a'))
+        baseurl = URL(r.request.url)
+        r = testapp.get(baseurl.joinpath(link1).url)
+        assert r.body == b"123"
+        r = testapp.get(baseurl.joinpath(link2).url)
+        assert r.body == b"456"
+        impexp.export()
+        mapp2 = impexp.new_import()
+        with mapp2.xom.keyfs.transaction(write=False):
+            stage = mapp2.xom.model.getstage(api.stagename)
+            stage.offline = True
+            projects = stage.list_projects_perstage()
+            assert projects == {'package'}
+            links = sorted(get_mutable_deepcopy(
+                stage.get_simplelinks_perstage("package")))
+            assert links == [
+                ('package-1.1.zip', 
'root/pypi/+f/a66/5a45920422f9d/package-1.1.zip', None),
+                ('package-2.0.zip', 
'root/pypi/+f/b3a/8e0e1f9ab1bfe/package-2.0.zip', '>=3.5')]
+
+    def test_mirrordata(self, caplog, impexp):
+        mapp = impexp.import_testdata('mirrordata')
+        with mapp.xom.keyfs.transaction(write=False):
+            stage = mapp.xom.model.getstage('root/pypi')
+            stage.offline = True
+            (link,) = stage.get_simplelinks_perstage("dddttt")
+            link = stage.get_link_from_entrypath(link[1])
+            assert link.project == "dddttt"
+            assert link.version == "0.1.dev1"
+            assert link.relpath == 
'root/pypi/+f/100/7f0cd10aaa290/dddttt-0.1.dev1.tar.gz'
+            assert link.entry.hash_spec == 
'sha256=1007f0cd10aaa290eb84f092e786639bbe930b7f2169f51f755a0f50a6aba489'
+
+    def test_modifiedpypi(self, caplog, impexp):
+        mapp = impexp.import_testdata('modifiedpypi')
+        with mapp.xom.keyfs.transaction(write=False):
+            stage = mapp.xom.model.getstage('root/pypi')
+            # test that we actually get the config from the import and not
+            # the default PyPI settings
+            assert stage.ixconfig['title'] == 'Modified PyPI'
+            assert stage.ixconfig['mirror_url'] == 
'https://example.com/simple/'
+            assert stage.ixconfig['mirror_web_url_fmt'] == 
'https://example.com/project/{name}/'
+
     def test_normalization(self, caplog, impexp):
         mapp = impexp.import_testdata('normalization')
         with mapp.xom.keyfs.transaction(write=False):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/devpi-server-5.3.1/test_devpi_server/test_stage_customizer.py 
new/devpi-server-5.4.1/test_devpi_server/test_stage_customizer.py
--- old/devpi-server-5.3.1/test_devpi_server/test_stage_customizer.py   
2019-12-05 14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/test_devpi_server/test_stage_customizer.py   
2020-03-26 09:54:54.000000000 +0100
@@ -17,6 +17,7 @@
 
 
 def test_permissions_for_unknown_index(mapp, xom):
+    from devpi_server.model import ReadonlyIndex
     api = mapp.create_and_use()
     mapp.upload_file_pypi("hello-1.0.tar.gz", b'content', "hello", "1.0")
     (path,) = mapp.get_release_paths("hello")
@@ -28,8 +29,26 @@
         stage = xom.model.getstage(api.stagename)
         with stage.user.key.update() as userconfig:
             userconfig["indexes"][stage.index]['type'] = 'unknown'
+    with xom.keyfs.transaction(write=True):
+        stage = xom.model.getstage(api.stagename)
+        # first check direct stage access
+        with pytest.raises(ReadonlyIndex):
+            stage.modify(**dict(stage.ixconfig, bases=[]))
+        with pytest.raises(ReadonlyIndex):
+            stage.set_versiondata(
+                dict(name="hello", version="1.0", requires_python=">=3.5"))
+        with pytest.raises(ReadonlyIndex):
+            stage.add_project_name("foo")
+        with pytest.raises(ReadonlyIndex):
+            stage.store_releasefile("foo", "2.0", "foo-2.0.zip", b'123')
+        with pytest.raises(ReadonlyIndex):
+            stage.store_doczip("foo", "2.0", b'456')
+        link_store = stage.get_linkstore_perstage("hello", "1.0")
+        (link,) = link_store.get_links()
+        with pytest.raises(ReadonlyIndex):
+            stage.store_toxresult(link, {})
     assert mapp.getjson(api.index)['result']['type'] == 'unknown'
-    # now check
+    # now check via views, which are protected by permissions most of the time
     mapp.modify_index(api.stagename, indexconfig=dict(bases=[]), code=403)
     mapp.testapp.xdel(403, path)
     mapp.delete_project('hello', code=403)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/test_devpi_server/test_views.py 
new/devpi-server-5.4.1/test_devpi_server/test_views.py
--- old/devpi-server-5.3.1/test_devpi_server/test_views.py      2019-12-05 
14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/test_devpi_server/test_views.py      2020-03-26 
09:54:54.000000000 +0100
@@ -1049,6 +1049,40 @@
     assert r.json["message"] == "error 502 getting 
https://pypi.org/simple/hello/hello-1.0.tar.gz";
 
 
+def test_push_from_pypi_mirror_switch_to_use_external_urls(httpget, mapp, 
pypistage, testapp):
+    pypistage.mock_simple("hello", text='<a href="hello-1.0.tar.gz"/>')
+    pypistage.mock_extfile("/simple/hello/hello-1.0.tar.gz", b"123")
+    mapp.create_and_login_user("foo")
+    mapp.create_index("newindex1", indexconfig=dict(bases=["root/pypi"]))
+    api = mapp.use("root/pypi")
+    assert not pypistage.use_external_url
+    # download the file to the mirror index
+    pkg_url = api.simpleindex + 'hello/'
+    (tag,) = testapp.get(pkg_url).html.select('a')
+    r = testapp.get(URL(pkg_url).joinpath(tag['href']).url)
+    assert r.body == b"123"
+    with mapp.xom.keyfs.transaction(write=True):
+        # switch to external URLs
+        pypistage.modify(mirror_use_external_urls=True)
+        # and remove the file to simulate a cleanup
+        linkstore = pypistage.get_linkstore_perstage("hello", "1.0")
+        (link,) = linkstore.get_links()
+        link.entry.file_delete()
+    assert pypistage.use_external_url
+    # we should now get a redirect when trying to get the file
+    r = testapp.xget(302, URL(pkg_url).joinpath(tag['href']).url)
+    # now we try to push the release to the new index
+    req = dict(name="hello", version="1.0", targetindex="foo/newindex1")
+    r = testapp.push("/root/pypi", json.dumps(req))
+    assert r.status_code == 200
+    assert r.json == {
+        'result': [
+            [200, 'register', 'hello', '1.0', '->', 'foo/newindex1'],
+            [200, 'store_releasefile',
+             'foo/newindex1/+f/a66/5a45920422f9d/hello-1.0.tar.gz']],
+        'type': 'actionlog'}
+
+
 def test_upload_docs_for_version_without_release(mapp, testapp, monkeypatch):
     mapp.create_and_use()
     mapp.upload_file_pypi("pkg1-2.6.tgz", b"123", "pkg1", "2.6")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/devpi-server-5.3.1/tox.ini 
new/devpi-server-5.4.1/tox.ini
--- old/devpi-server-5.3.1/tox.ini      2019-12-05 14:39:00.000000000 +0100
+++ new/devpi-server-5.4.1/tox.ini      2020-03-26 09:54:54.000000000 +0100
@@ -10,6 +10,7 @@
 commands=
     py.test --instafail --slow {posargs}
 deps=
+    py34: colorama<=0.4.1 ; sys_platform == 'win32'
     webtest
     py27,pypy: mock
     pytest>=3


Reply via email to