devpi-server 6.10.0__tar.gz → 6.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devpi-server-6.10.0 → devpi_server-6.12.0}/CHANGELOG +41 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/CHANGELOG.short.rst +38 -34
- {devpi-server-6.10.0 → devpi_server-6.12.0}/PKG-INFO +30 -34
- devpi_server-6.12.0/devpi_server/__init__.py +1 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth.py +4 -2
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/config.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/filestore.py +39 -21
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/filestore_fs.py +1 -6
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/fsck.py +7 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/hookspecs.py +43 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/importexport.py +19 -6
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs.py +45 -57
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_sqlite.py +5 -6
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_sqlite_fs.py +1 -6
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/main.py +4 -1
- devpi_server-6.12.0/devpi_server/markers.py +14 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/middleware.py +0 -2
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/mirror.py +46 -10
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/model.py +40 -22
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/replica.py +71 -81
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/views.py +187 -157
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/PKG-INFO +30 -34
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/SOURCES.txt +1 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/setup.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/functional.py +23 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +1 -1
- devpi_server-6.12.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/plugin.py +11 -22
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/reqmock.py +16 -9
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/simpypi.py +4 -4
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_auth.py +3 -3
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_config.py +9 -7
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_filestore.py +30 -28
- devpi_server-6.12.0/test_devpi_server/test_fsck.py +41 -0
- devpi_server-6.12.0/test_devpi_server/test_genconfig.py +50 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_importexport.py +88 -68
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_keyfs.py +11 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_main.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mirror.py +32 -4
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_model.py +29 -45
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_nginx.py +4 -1
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_permissions.py +8 -6
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_replica.py +45 -33
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_stage_customizer.py +8 -8
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming.py +1 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_view_auth.py +10 -8
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views.py +77 -24
- {devpi-server-6.10.0 → devpi_server-6.12.0}/tox.ini +7 -2
- devpi-server-6.10.0/devpi_server/__init__.py +0 -1
- devpi-server-6.10.0/devpi_server/markers.py +0 -6
- devpi-server-6.10.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- devpi-server-6.10.0/test_devpi_server/test_genconfig.py +0 -19
- {devpi-server-6.10.0 → devpi_server-6.12.0}/.flake8 +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/AUTHORS +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/LICENSE +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/MANIFEST.in +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/README.rst +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/__main__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth_basic.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth_devpi.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/crontab.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/exceptions.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/fileutil.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/genconfig.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/init.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/interfaces.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_types.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/log.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/mythread.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/passwd.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/readonly.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/sizeof.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/vendor/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/vendor/_pip.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/view_auth.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/requires.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/pyproject.toml +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/pytest_devpi_server/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/setup.cfg +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/conftest.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/example.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz → /devpi_server-6.12.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz → /devpi_server-6.12.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz → /devpi_server-6.12.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_authcheck.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_conftest.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_fileutil.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_log.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mythread.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_readonly.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_reqmock.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_patch.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_status.py +0 -0
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.12.0 (2024-06-25)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
- Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
|
|
12
|
+
|
|
13
|
+
- Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Bug Fixes
|
|
18
|
+
---------
|
|
19
|
+
|
|
20
|
+
- Return json data if toxresult upload is forbidden.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
6.11.0 (2024-04-20)
|
|
25
|
+
===================
|
|
26
|
+
|
|
27
|
+
Features
|
|
28
|
+
--------
|
|
29
|
+
|
|
30
|
+
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
31
|
+
|
|
32
|
+
- Fix #983: Add plugin hook for mirror authentication header.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Bug Fixes
|
|
37
|
+
---------
|
|
38
|
+
|
|
39
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
40
|
+
|
|
41
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
5
45
|
6.10.0 (2023-12-19)
|
|
6
46
|
===================
|
|
7
47
|
|
|
@@ -10,7 +50,7 @@ Features
|
|
|
10
50
|
|
|
11
51
|
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
12
52
|
|
|
13
|
-
- Fix #
|
|
53
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
14
54
|
|
|
15
55
|
|
|
16
56
|
|
|
@@ -9,85 +9,89 @@ Changelog
|
|
|
9
9
|
|
|
10
10
|
.. towncrier release notes start
|
|
11
11
|
|
|
12
|
-
6.
|
|
13
|
-
|
|
12
|
+
6.11.0 (2024-04-20)
|
|
13
|
+
===================
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Features
|
|
16
|
+
--------
|
|
17
17
|
|
|
18
|
-
-
|
|
18
|
+
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
19
|
+
|
|
20
|
+
- Fix #983: Add plugin hook for mirror authentication header.
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
6.9.1 (2023-07-02)
|
|
22
|
-
==================
|
|
23
23
|
|
|
24
24
|
Bug Fixes
|
|
25
25
|
---------
|
|
26
26
|
|
|
27
|
-
-
|
|
27
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
28
28
|
|
|
29
|
-
- Fix #
|
|
29
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
6.10.0 (2023-12-19)
|
|
34
|
+
===================
|
|
34
35
|
|
|
35
36
|
Features
|
|
36
37
|
--------
|
|
37
38
|
|
|
38
|
-
-
|
|
39
|
+
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
40
|
+
|
|
41
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
39
42
|
|
|
40
|
-
- Fix #931: Add ``mirror_no_project_list`` setting for mirror indexes that have no full project list like google cloud artifacts or if you want to prevent downloading the full list for huge indexes like PyPI.
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
Bug Fixes
|
|
44
46
|
---------
|
|
45
47
|
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
49
|
-
|
|
50
|
-
- Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
|
|
48
|
+
- Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
|
|
51
49
|
|
|
52
|
-
-
|
|
50
|
+
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
53
51
|
|
|
54
|
-
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
55
52
|
|
|
56
53
|
|
|
57
|
-
6.
|
|
54
|
+
6.9.2 (2023-08-06)
|
|
58
55
|
==================
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
Bug Fixes
|
|
58
|
+
---------
|
|
62
59
|
|
|
63
|
-
-
|
|
60
|
+
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
64
61
|
|
|
65
62
|
|
|
63
|
+
6.9.1 (2023-07-02)
|
|
64
|
+
==================
|
|
65
|
+
|
|
66
66
|
Bug Fixes
|
|
67
67
|
---------
|
|
68
68
|
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
- Catch exceptions in async_httpget analog to httpget.
|
|
69
|
+
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
72
70
|
|
|
73
|
-
-
|
|
71
|
+
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
74
72
|
|
|
75
73
|
|
|
76
|
-
6.
|
|
74
|
+
6.9.0 (2023-05-23)
|
|
77
75
|
==================
|
|
78
76
|
|
|
79
77
|
Features
|
|
80
78
|
--------
|
|
81
79
|
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
- Automatically check for ``+files`` when using ``--replica-file-search-path``.
|
|
80
|
+
- Support export directory layout for ``--replica-file-search-path`` option.
|
|
85
81
|
|
|
86
|
-
-
|
|
82
|
+
- Fix #931: Add ``mirror_no_project_list`` setting for mirror indexes that have no full project list like google cloud artifacts or if you want to prevent downloading the full list for huge indexes like PyPI.
|
|
87
83
|
|
|
88
84
|
|
|
89
85
|
Bug Fixes
|
|
90
86
|
---------
|
|
91
87
|
|
|
92
|
-
-
|
|
88
|
+
- Keep a reference to async tasks to avoid their removal mid execution.
|
|
89
|
+
|
|
90
|
+
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
91
|
+
|
|
92
|
+
- Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
|
|
93
|
+
|
|
94
|
+
- Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
|
|
95
|
+
|
|
96
|
+
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
93
97
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.12.0
|
|
4
4
|
Summary: devpi-server: reliable private and pypi.org caching server
|
|
5
5
|
Home-page: https://devpi.net
|
|
6
6
|
Maintainer: Florian Schulze
|
|
@@ -119,87 +119,83 @@ Changelog
|
|
|
119
119
|
|
|
120
120
|
.. towncrier release notes start
|
|
121
121
|
|
|
122
|
-
6.
|
|
122
|
+
6.12.0 (2024-06-25)
|
|
123
123
|
===================
|
|
124
124
|
|
|
125
125
|
Features
|
|
126
126
|
--------
|
|
127
127
|
|
|
128
|
-
-
|
|
128
|
+
- Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
|
|
129
129
|
|
|
130
|
-
-
|
|
130
|
+
- Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
Bug Fixes
|
|
135
135
|
---------
|
|
136
136
|
|
|
137
|
-
-
|
|
137
|
+
- Return json data if toxresult upload is forbidden.
|
|
138
138
|
|
|
139
|
-
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
140
139
|
|
|
141
140
|
|
|
141
|
+
6.11.0 (2024-04-20)
|
|
142
|
+
===================
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
Features
|
|
145
|
+
--------
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
---------
|
|
147
|
+
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
148
148
|
|
|
149
|
-
-
|
|
149
|
+
- Fix #983: Add plugin hook for mirror authentication header.
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
6.9.1 (2023-07-02)
|
|
153
|
-
==================
|
|
154
152
|
|
|
155
153
|
Bug Fixes
|
|
156
154
|
---------
|
|
157
155
|
|
|
158
|
-
-
|
|
156
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
159
157
|
|
|
160
|
-
- Fix #
|
|
158
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
161
159
|
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
|
|
162
|
+
6.10.0 (2023-12-19)
|
|
163
|
+
===================
|
|
165
164
|
|
|
166
165
|
Features
|
|
167
166
|
--------
|
|
168
167
|
|
|
169
|
-
-
|
|
168
|
+
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
169
|
+
|
|
170
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
170
171
|
|
|
171
|
-
- Fix #931: Add ``mirror_no_project_list`` setting for mirror indexes that have no full project list like google cloud artifacts or if you want to prevent downloading the full list for huge indexes like PyPI.
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
Bug Fixes
|
|
175
175
|
---------
|
|
176
176
|
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
180
|
-
|
|
181
|
-
- Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
|
|
177
|
+
- Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
|
|
182
178
|
|
|
183
|
-
-
|
|
179
|
+
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
184
180
|
|
|
185
|
-
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
186
181
|
|
|
187
182
|
|
|
188
|
-
6.
|
|
183
|
+
6.9.2 (2023-08-06)
|
|
189
184
|
==================
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
Bug Fixes
|
|
187
|
+
---------
|
|
193
188
|
|
|
194
|
-
-
|
|
189
|
+
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
195
190
|
|
|
196
191
|
|
|
192
|
+
6.9.1 (2023-07-02)
|
|
193
|
+
==================
|
|
194
|
+
|
|
197
195
|
Bug Fixes
|
|
198
196
|
---------
|
|
199
197
|
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
- Catch exceptions in async_httpget analog to httpget.
|
|
198
|
+
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
203
199
|
|
|
204
|
-
-
|
|
200
|
+
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
205
201
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.12.0'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import hashlib
|
|
3
|
+
import itertools
|
|
3
4
|
import itsdangerous
|
|
4
5
|
import secrets
|
|
5
6
|
from .log import threadlog
|
|
@@ -53,7 +54,8 @@ class Auth:
|
|
|
53
54
|
# one of the plugins returned valid userinfo
|
|
54
55
|
# return union of all groups which may be contained in that info
|
|
55
56
|
groups = (ui.get('groups', []) for ui in userinfo_list)
|
|
56
|
-
return dict(status="ok", groups=sorted(
|
|
57
|
+
return dict(status="ok", groups=sorted(
|
|
58
|
+
set(itertools.chain.from_iterable(groups))))
|
|
57
59
|
|
|
58
60
|
def _validate(self, authuser, authpassword, request=None):
|
|
59
61
|
""" Validates user credentials.
|
|
@@ -131,7 +133,7 @@ class Auth:
|
|
|
131
133
|
username,
|
|
132
134
|
result.get("groups", []),
|
|
133
135
|
result.get("from_user_object", False)])
|
|
134
|
-
assert not isinstance(pseudopass, str) or pseudopass.encode('ascii')
|
|
136
|
+
assert not isinstance(pseudopass, str) or pseudopass.encode('ascii') # type: ignore[attr-defined]
|
|
135
137
|
return {"password": pseudopass,
|
|
136
138
|
"expiration": self.LOGIN_EXPIRATION}
|
|
137
139
|
|
|
@@ -179,7 +179,7 @@ def add_web_options(parser, pluginmanager):
|
|
|
179
179
|
|
|
180
180
|
def add_mirror_options(parser, pluginmanager):
|
|
181
181
|
parser.addoption(
|
|
182
|
-
"--mirror-cache-expiry", type=
|
|
182
|
+
"--mirror-cache-expiry", type=int, metavar="SECS",
|
|
183
183
|
default=DEFAULT_MIRROR_CACHE_EXPIRY,
|
|
184
184
|
help="(experimental) time after which projects in mirror indexes "
|
|
185
185
|
"are checked for new releases.")
|
|
@@ -10,9 +10,14 @@ import re
|
|
|
10
10
|
from devpi_common.metadata import splitbasename
|
|
11
11
|
from devpi_common.types import parse_hash_spec
|
|
12
12
|
from devpi_server.log import threadlog
|
|
13
|
+
from inspect import currentframe
|
|
13
14
|
from urllib.parse import unquote
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
class ChecksumError(ValueError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
16
21
|
_nodefault = object()
|
|
17
22
|
|
|
18
23
|
|
|
@@ -38,23 +43,37 @@ def get_file_hash(fp, hash_type):
|
|
|
38
43
|
return running_hash.hexdigest()
|
|
39
44
|
|
|
40
45
|
|
|
46
|
+
def get_seekable_content_or_file(content_or_file):
|
|
47
|
+
if isinstance(content_or_file, bytes):
|
|
48
|
+
return content_or_file
|
|
49
|
+
seekable_method = getattr(content_or_file, "seekable", None)
|
|
50
|
+
seekable = seekable_method() if callable(seekable_method) else False
|
|
51
|
+
if not seekable:
|
|
52
|
+
content_or_file = content_or_file.read()
|
|
53
|
+
if len(content_or_file) > 1048576:
|
|
54
|
+
frame = currentframe()
|
|
55
|
+
if frame is not None and frame.f_back is not None:
|
|
56
|
+
frame = frame.f_back
|
|
57
|
+
if frame is None:
|
|
58
|
+
f_name = "get_seekable_content_or_file"
|
|
59
|
+
else:
|
|
60
|
+
f_name = frame.f_code.co_name
|
|
61
|
+
threadlog.warn(
|
|
62
|
+
"Read %.1f megabytes into memory in %s",
|
|
63
|
+
len(content_or_file) / 1048576, f_name)
|
|
64
|
+
return content_or_file
|
|
65
|
+
|
|
66
|
+
|
|
41
67
|
def get_hash_spec(content_or_file, hash_type):
|
|
42
68
|
if not isinstance(content_or_file, bytes):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if len(content_or_file) > 1048576:
|
|
52
|
-
threadlog.warn(
|
|
53
|
-
"Read %.1f megabytes into memory in get_default_hash_spec",
|
|
54
|
-
len(content_or_file) / 1048576)
|
|
55
|
-
if isinstance(content_or_file, bytes):
|
|
56
|
-
running_hash = getattr(hashlib, hash_type)(content_or_file)
|
|
57
|
-
return f"{running_hash.name}={running_hash.hexdigest()}"
|
|
69
|
+
assert content_or_file.seekable()
|
|
70
|
+
content_or_file.seek(0)
|
|
71
|
+
hexdigest = get_file_hash(
|
|
72
|
+
content_or_file, hash_type)
|
|
73
|
+
content_or_file.seek(0)
|
|
74
|
+
return f"{hash_type}={hexdigest}"
|
|
75
|
+
running_hash = getattr(hashlib, hash_type)(content_or_file)
|
|
76
|
+
return f"{running_hash.name}={running_hash.hexdigest()}"
|
|
58
77
|
|
|
59
78
|
|
|
60
79
|
def make_splitdir(hash_spec):
|
|
@@ -150,8 +169,7 @@ class FileStore:
|
|
|
150
169
|
|
|
151
170
|
def metaprop(name):
|
|
152
171
|
def fget(self):
|
|
153
|
-
if self.meta is
|
|
154
|
-
return self.meta.get(name)
|
|
172
|
+
return None if self.meta is None else self.meta.get(name)
|
|
155
173
|
|
|
156
174
|
def fset(self, val):
|
|
157
175
|
val = unicode_if_bytes(val)
|
|
@@ -251,7 +269,7 @@ class FileEntry(object):
|
|
|
251
269
|
def file_os_path(self):
|
|
252
270
|
return self.tx.conn.io_file_os_path(self._storepath)
|
|
253
271
|
|
|
254
|
-
def file_set_content(self, content_or_file, last_modified=None, hash_spec=None):
|
|
272
|
+
def file_set_content(self, content_or_file, *, last_modified=None, hash_spec=None):
|
|
255
273
|
if last_modified != -1:
|
|
256
274
|
if last_modified is None:
|
|
257
275
|
last_modified = unicode_if_bytes(format_date_time(None))
|
|
@@ -294,7 +312,7 @@ class FileEntry(object):
|
|
|
294
312
|
self.file_delete()
|
|
295
313
|
|
|
296
314
|
def has_existing_metadata(self):
|
|
297
|
-
return self.hash_spec and self.last_modified
|
|
315
|
+
return bool(self.hash_spec and self.last_modified)
|
|
298
316
|
|
|
299
317
|
|
|
300
318
|
def get_checksum_error(content_or_hash, relpath, hash_spec):
|
|
@@ -306,12 +324,12 @@ def get_checksum_error(content_or_hash, relpath, hash_spec):
|
|
|
306
324
|
if callable(hexdigest):
|
|
307
325
|
hexdigest = hexdigest()
|
|
308
326
|
if content_or_hash.name != hash_type:
|
|
309
|
-
return
|
|
327
|
+
return ChecksumError(
|
|
310
328
|
f"{relpath}: hash type mismatch, "
|
|
311
329
|
f"got {content_or_hash.name}, expected {hash_type}")
|
|
312
330
|
else:
|
|
313
331
|
hexdigest = hash_algo(content_or_hash).hexdigest()
|
|
314
332
|
if hexdigest != hash_value:
|
|
315
|
-
return
|
|
333
|
+
return ChecksumError(
|
|
316
334
|
f"{relpath}: {hash_type} mismatch, "
|
|
317
335
|
f"got {hexdigest}, expected {hash_value}")
|
|
@@ -44,15 +44,10 @@ class DirtyFile:
|
|
|
44
44
|
os.link(content_or_file.devpi_srcpath, self.tmppath)
|
|
45
45
|
else:
|
|
46
46
|
with get_write_file_ensure_dir(self.tmppath) as f:
|
|
47
|
-
if not isinstance(content_or_file, bytes) and not callable(getattr(content_or_file, "seekable", None)):
|
|
48
|
-
content_or_file = content_or_file.read()
|
|
49
|
-
if len(content_or_file) > 1048576:
|
|
50
|
-
threadlog.warn(
|
|
51
|
-
"Read %.1f megabytes into memory in keyfs_sqlite_fs from_content for %s, because of unseekable file",
|
|
52
|
-
len(content_or_file) / 1048576, path)
|
|
53
47
|
if isinstance(content_or_file, bytes):
|
|
54
48
|
f.write(content_or_file)
|
|
55
49
|
else:
|
|
50
|
+
assert content_or_file.seekable()
|
|
56
51
|
content_or_file.seek(0)
|
|
57
52
|
shutil.copyfileobj(content_or_file, f)
|
|
58
53
|
return self
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .filestore import FileEntry
|
|
2
2
|
from .log import configure_cli_logging
|
|
3
3
|
from .main import CommandRunner
|
|
4
|
+
from .main import Fatal
|
|
4
5
|
from .main import xom_from_config
|
|
5
6
|
import sys
|
|
6
7
|
import time
|
|
@@ -38,6 +39,7 @@ def fsck():
|
|
|
38
39
|
last_time = time.time()
|
|
39
40
|
processed = 0
|
|
40
41
|
missing_files = 0
|
|
42
|
+
got_errors = False
|
|
41
43
|
with xom.keyfs.read_transaction() as tx:
|
|
42
44
|
log.info("Checking at serial %s" % tx.at_serial)
|
|
43
45
|
relpaths = tx.iter_relpaths_at(keys, tx.at_serial)
|
|
@@ -57,6 +59,7 @@ def fsck():
|
|
|
57
59
|
if not entry.file_exists():
|
|
58
60
|
missing_files += 1
|
|
59
61
|
if missing_files < 10:
|
|
62
|
+
got_errors = True
|
|
60
63
|
log.error("Missing file %s" % entry.relpath)
|
|
61
64
|
elif missing_files == 10:
|
|
62
65
|
log.error("Further missing files will be omitted.")
|
|
@@ -65,6 +68,7 @@ def fsck():
|
|
|
65
68
|
continue
|
|
66
69
|
checksum = entry.file_get_checksum(entry.hash_type)
|
|
67
70
|
if entry.hash_value != checksum:
|
|
71
|
+
got_errors = True
|
|
68
72
|
log.error(
|
|
69
73
|
"%s - %s mismatch, got %s, expected %s"
|
|
70
74
|
% (entry.relpath, entry.hash_type, checksum, entry.hash_value))
|
|
@@ -75,4 +79,7 @@ def fsck():
|
|
|
75
79
|
log.error(
|
|
76
80
|
"A total of %s files are missing."
|
|
77
81
|
% missing_files)
|
|
82
|
+
if got_errors:
|
|
83
|
+
msg = "There have been errors during consistency check."
|
|
84
|
+
raise Fatal(msg)
|
|
78
85
|
return runner.return_code
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
from pluggy import HookspecMarker
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from devpi_server.views import ToxResultHandling
|
|
9
|
+
from pyramid.request import Request
|
|
10
|
+
|
|
3
11
|
|
|
4
12
|
hookspec = HookspecMarker("devpiserver")
|
|
5
13
|
|
|
@@ -200,6 +208,18 @@ def devpiserver_auth_denials(request, acl, user, stage):
|
|
|
200
208
|
"""
|
|
201
209
|
|
|
202
210
|
|
|
211
|
+
@hookspec
|
|
212
|
+
def devpiserver_get_mirror_auth(mirror_url, www_authenticate_header):
|
|
213
|
+
"""Provide an http "Authorization" header to access a mirror.
|
|
214
|
+
|
|
215
|
+
Return a string which will be set as the authorization header for all http
|
|
216
|
+
requests to this mirror.
|
|
217
|
+
|
|
218
|
+
Return None or an empty string if no credentials can be determined for the
|
|
219
|
+
supplied url.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
|
|
203
223
|
@hookspec
|
|
204
224
|
def devpiserver_get_stage_customizer_classes():
|
|
205
225
|
"""EXPERIMENTAL!
|
|
@@ -243,6 +263,28 @@ def devpiserver_on_replicated_file(stage, project, version, link, serial, back_s
|
|
|
243
263
|
"""Called when a file was downloaded from master on replica."""
|
|
244
264
|
|
|
245
265
|
|
|
266
|
+
@hookspec(firstresult=True)
|
|
267
|
+
def devpiserver_on_toxresult_store(request: Request, tox_result_handling: ToxResultHandling) -> Optional[ToxResultHandling]:
|
|
268
|
+
"""Called when a toxresult is about to be stored.
|
|
269
|
+
|
|
270
|
+
Stops at first non-None result.
|
|
271
|
+
|
|
272
|
+
:returns: A ToxResultHandling object which determines how the upload is processed.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@hookspec(firstresult=True)
|
|
277
|
+
def devpiserver_on_toxresult_upload_forbidden(request: Request, tox_result_handling: ToxResultHandling) -> Optional[str]:
|
|
278
|
+
"""Called when the permission check for toxresult upload failed.
|
|
279
|
+
|
|
280
|
+
Stops at first non-None result.
|
|
281
|
+
|
|
282
|
+
:returns: A ToxResultHandling object which determines whether an error
|
|
283
|
+
with message (default using ``block``) or a success with a message
|
|
284
|
+
(using ``skip``) is returned.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
|
|
246
288
|
@hookspec
|
|
247
289
|
def devpiserver_metrics(request):
|
|
248
290
|
""" called for status view.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import itertools
|
|
1
2
|
import sys
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
@@ -279,6 +280,7 @@ class IndexDump:
|
|
|
279
280
|
project=linkstore.project,
|
|
280
281
|
relpath=relpath,
|
|
281
282
|
version=linkstore.version,
|
|
283
|
+
entrymapping=tox_link.entry.meta,
|
|
282
284
|
for_entrypath=reflink.entrypath,
|
|
283
285
|
log=tox_link.get_logs())
|
|
284
286
|
|
|
@@ -297,7 +299,9 @@ class IndexDump:
|
|
|
297
299
|
relpath = self.exporter.copy_file(
|
|
298
300
|
entry,
|
|
299
301
|
self.basedir.join("%s-%s.doc.zip" % (project, version)))
|
|
300
|
-
self.add_filedesc(
|
|
302
|
+
self.add_filedesc(
|
|
303
|
+
"doczip", project, relpath,
|
|
304
|
+
version=version, entrymapping=entry.meta)
|
|
301
305
|
|
|
302
306
|
|
|
303
307
|
class Importer:
|
|
@@ -535,8 +539,11 @@ class Importer:
|
|
|
535
539
|
if self.xom.config.hard_links:
|
|
536
540
|
# additional attribute for hard links
|
|
537
541
|
f.devpi_srcpath = p.strpath
|
|
542
|
+
|
|
543
|
+
# docs and toxresults didn't always have entrymapping in export dump
|
|
544
|
+
mapping = filedesc.get("entrymapping", {})
|
|
545
|
+
|
|
538
546
|
if filedesc["type"] == "releasefile":
|
|
539
|
-
mapping = filedesc["entrymapping"]
|
|
540
547
|
if self.dumpversion == "1":
|
|
541
548
|
# previous versions would not add a version attribute
|
|
542
549
|
version = BasenameMeta(p.basename).version
|
|
@@ -554,7 +561,7 @@ class Importer:
|
|
|
554
561
|
url = URL(mapping['url']).replace(fragment=mapping['hash_spec'])
|
|
555
562
|
entry = self.xom.filestore.maplink(
|
|
556
563
|
url, stage.username, stage.index, project)
|
|
557
|
-
entry.file_set_content(f, mapping["last_modified"])
|
|
564
|
+
entry.file_set_content(f, last_modified=mapping["last_modified"])
|
|
558
565
|
(_, links_with_data, serial) = stage._load_cache_links(project)
|
|
559
566
|
if links_with_data is None:
|
|
560
567
|
links_with_data = []
|
|
@@ -579,16 +586,22 @@ class Importer:
|
|
|
579
586
|
# determined here but in store_releasefile/store_doczip/store_toxresult etc
|
|
580
587
|
elif filedesc["type"] == "doczip":
|
|
581
588
|
version = filedesc["version"]
|
|
582
|
-
|
|
589
|
+
# docs didn't always have entrymapping in export dump
|
|
590
|
+
last_modified = mapping.get("last_modified")
|
|
591
|
+
link = stage.store_doczip(
|
|
592
|
+
project, version, f, last_modified=last_modified)
|
|
583
593
|
elif filedesc["type"] == "toxresult":
|
|
584
594
|
linkstore = stage.get_linkstore_perstage(
|
|
585
595
|
filedesc["projectname"], filedesc["version"])
|
|
586
596
|
# we can not search for the full relative path because
|
|
587
597
|
# it might use a different checksum
|
|
588
598
|
basename = posixpath.basename(filedesc["for_entrypath"])
|
|
599
|
+
# toxresults didn't always have entrymapping in export dump
|
|
600
|
+
last_modified = mapping.get("last_modified")
|
|
589
601
|
link, = linkstore.get_links(basename=basename)
|
|
590
602
|
link = stage.store_toxresult(
|
|
591
|
-
link, f, filename=posixpath.basename(filedesc["relpath"])
|
|
603
|
+
link, f, filename=posixpath.basename(filedesc["relpath"]),
|
|
604
|
+
last_modified=last_modified)
|
|
592
605
|
else:
|
|
593
606
|
msg = f"unknown file type: {type}"
|
|
594
607
|
raise Fatal(msg)
|
|
@@ -622,7 +635,7 @@ class IndexTree:
|
|
|
622
635
|
children.append(name)
|
|
623
636
|
|
|
624
637
|
def validate(self):
|
|
625
|
-
all_bases = set(
|
|
638
|
+
all_bases = set(itertools.chain.from_iterable(self.name2bases.values()))
|
|
626
639
|
all_indexes = set(self.name2bases)
|
|
627
640
|
missing = all_bases - all_indexes
|
|
628
641
|
if missing:
|