duplicity 3.0.7.dev0__tar.gz → 3.0.8.dev3__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.
Files changed (143) hide show
  1. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/CHANGELOG.md +26 -4
  2. {duplicity-3.0.7.dev0/duplicity.egg-info → duplicity-3.0.8.dev3}/PKG-INFO +1 -1
  3. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/__init__.py +2 -2
  4. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/s3_boto3_backend.py +19 -13
  5. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/webdavbackend.py +7 -3
  6. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/cli_data.py +2 -2
  7. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/cli_util.py +1 -1
  8. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/diffdir.py +2 -22
  9. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/dup_main.py +38 -30
  10. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/log.py +25 -11
  11. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/path.py +1 -22
  12. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/util.py +2 -24
  13. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3/duplicity.egg-info}/PKG-INFO +1 -1
  14. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/man/duplicity.1 +33 -33
  15. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/pyproject.toml +1 -4
  16. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/setup.py +1 -1
  17. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/AUTHORS.md +0 -0
  18. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/COPYING +0 -0
  19. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/README-LOG.md +0 -0
  20. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/README-REPO.md +0 -0
  21. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/README-TESTING.md +0 -0
  22. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/README.md +0 -0
  23. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/__main__.py +0 -0
  24. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/_librsyncmodule.c +0 -0
  25. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/argparse311.py +0 -0
  26. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backend.py +0 -0
  27. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backend_pool.py +0 -0
  28. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/__init__.py +0 -0
  29. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/_cf_cloudfiles.py +0 -0
  30. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/_cf_pyrax.py +0 -0
  31. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/_testbackend.py +0 -0
  32. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/adbackend.py +0 -0
  33. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/azurebackend.py +0 -0
  34. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/b2backend.py +0 -0
  35. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/boxbackend.py +0 -0
  36. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/cfbackend.py +0 -0
  37. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/dpbxbackend.py +0 -0
  38. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/gdocsbackend.py +0 -0
  39. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/gdrivebackend.py +0 -0
  40. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/giobackend.py +0 -0
  41. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/hsibackend.py +0 -0
  42. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/hubicbackend.py +0 -0
  43. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/idrivedbackend.py +0 -0
  44. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/imapbackend.py +0 -0
  45. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/jottacloudbackend.py +0 -0
  46. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/lftpbackend.py +0 -0
  47. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/localbackend.py +0 -0
  48. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/mediafirebackend.py +0 -0
  49. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/megabackend.py +0 -0
  50. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/megav2backend.py +0 -0
  51. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/megav3backend.py +0 -0
  52. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/multibackend.py +0 -0
  53. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/ncftpbackend.py +0 -0
  54. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/onedrivebackend.py +0 -0
  55. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/par2backend.py +0 -0
  56. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/pcabackend.py +0 -0
  57. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/pydrivebackend.py +0 -0
  58. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/pyrax_identity/__init__.py +0 -0
  59. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/pyrax_identity/hubic.py +0 -0
  60. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/rclonebackend.py +0 -0
  61. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/rsyncbackend.py +0 -0
  62. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/slatebackend.py +0 -0
  63. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/ssh_paramiko_backend.py +0 -0
  64. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/ssh_pexpect_backend.py +0 -0
  65. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/swiftbackend.py +0 -0
  66. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/sxbackend.py +0 -0
  67. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/tahoebackend.py +0 -0
  68. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/backends/xorrisobackend.py +0 -0
  69. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/cached_ops.py +0 -0
  70. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/cli_main.py +0 -0
  71. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/config.py +0 -0
  72. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/dup_collections.py +0 -0
  73. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/dup_tarfile.py +0 -0
  74. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/dup_temp.py +0 -0
  75. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/dup_time.py +0 -0
  76. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/errors.py +0 -0
  77. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/file_naming.py +0 -0
  78. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/filechunkio.py +0 -0
  79. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/globmatch.py +0 -0
  80. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/gpg.py +0 -0
  81. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/gpginterface.py +0 -0
  82. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/lazy.py +0 -0
  83. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/librsync.py +0 -0
  84. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/manifest.py +0 -0
  85. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/patchdir.py +0 -0
  86. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/progress.py +0 -0
  87. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/robust.py +0 -0
  88. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/selection.py +0 -0
  89. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/statistics.py +0 -0
  90. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity/tempdir.py +0 -0
  91. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity.egg-info/SOURCES.txt +0 -0
  92. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity.egg-info/dependency_links.txt +0 -0
  93. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity.egg-info/entry_points.txt +0 -0
  94. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity.egg-info/requires.txt +0 -0
  95. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/duplicity.egg-info/top_level.txt +0 -0
  96. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/af_ZA/duplicity.mo +0 -0
  97. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ar_SA/duplicity.mo +0 -0
  98. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ca_ES/duplicity.mo +0 -0
  99. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/cs_CZ/duplicity.mo +0 -0
  100. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/da_DK/duplicity.mo +0 -0
  101. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/de_AT/duplicity.mo +0 -0
  102. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/de_DE/duplicity.mo +0 -0
  103. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/el_GR/duplicity.mo +0 -0
  104. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/en_AU/duplicity.mo +0 -0
  105. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/en_GB/duplicity.mo +0 -0
  106. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/en_PR/duplicity.mo +0 -0
  107. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/en_US/duplicity.mo +0 -0
  108. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/es_EM/duplicity.mo +0 -0
  109. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/es_ES/duplicity.mo +0 -0
  110. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/es_MX/duplicity.mo +0 -0
  111. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/es_PR/duplicity.mo +0 -0
  112. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/es_US/duplicity.mo +0 -0
  113. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/fi_FI/duplicity.mo +0 -0
  114. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/fr_FR/duplicity.mo +0 -0
  115. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/he_IL/duplicity.mo +0 -0
  116. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/hu_HU/duplicity.mo +0 -0
  117. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/it_IT/duplicity.mo +0 -0
  118. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ja_JP/duplicity.mo +0 -0
  119. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ko_KR/duplicity.mo +0 -0
  120. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/nl_BE/duplicity.mo +0 -0
  121. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/nl_NL/duplicity.mo +0 -0
  122. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/nl_SR/duplicity.mo +0 -0
  123. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/no_NO/duplicity.mo +0 -0
  124. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/pl_PL/duplicity.mo +0 -0
  125. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/pt_BR/duplicity.mo +0 -0
  126. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/pt_PT/duplicity.mo +0 -0
  127. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ro_RO/duplicity.mo +0 -0
  128. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ru_BY/duplicity.mo +0 -0
  129. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ru_MD/duplicity.mo +0 -0
  130. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ru_RU/duplicity.mo +0 -0
  131. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/ru_UA/duplicity.mo +0 -0
  132. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/sr_SP/duplicity.mo +0 -0
  133. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/sv_SE/duplicity.mo +0 -0
  134. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/tr_TR/duplicity.mo +0 -0
  135. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/uk_UA/duplicity.mo +0 -0
  136. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/vi_VN/duplicity.mo +0 -0
  137. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/zh_CN/duplicity.mo +0 -0
  138. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/zh_HK/duplicity.mo +0 -0
  139. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/zh_MO/duplicity.mo +0 -0
  140. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/zh_SG/duplicity.mo +0 -0
  141. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/po/zh_TW/duplicity.mo +0 -0
  142. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/requirements.txt +0 -0
  143. {duplicity-3.0.7.dev0 → duplicity-3.0.8.dev3}/setup.cfg +0 -0
@@ -1,10 +1,28 @@
1
1
 
2
- (Unreleased) / 2025-11-26
2
+ (Unreleased) / 2025-12-31
3
3
  =========================
4
4
 
5
- * 29e319fa:chg: Add missing ':' between tags and subject.
6
- * 41e08e43:chg: Use chg|fix|new, not target part.
7
- * 3418970f:chg: Use `git changelog` instead of `gitchangelog`.
5
+
6
+
7
+ rel.3.0.7 / 2025-12-31
8
+ ======================
9
+
10
+ * ad175c22:fix: replace custom deltree with built-in shutil.rmtree.
11
+ * 101be7fd:fix: delete duplicate code in DirDelta.
12
+ * c0d3e72d:fix: webdavs with "--concurrency 1" failed because of missing auth header
13
+ * a08f8bef:fix: disable s3 checksum workaround, warn only...
14
+ * f7055a7a:fix: --log-timestamp no longer working.
15
+ * ac163d5e:fix: Combined fix to related issues 912 and 914
16
+
17
+ rel.3.0.6.3 / 2025-12-05
18
+ ========================
19
+
20
+ * dd45a92d:chg: Update check_tags to use current branch.
21
+ * 5bbb5813:fix: Change log level from Info to Notice in get_passphrase().
22
+ * a9c4fbad:chg: Add key_needs_passphrase(key).
23
+ * abaf8485:chg: Better error message from get_remote_file().
24
+ * b083fca8:fix: 'duplicity --no-check-remote inc' prints spurious warning "found missing difftar(s) in backup sets"
25
+ * 9400fc37:chg: Add check_tags minor fix to setversion.
8
26
  * 29227571:fix: delete unused diffdir.DirSig, diffdir.SigTarBlockIter, librsync.SigFile.
9
27
  * 29205145:fix: Delete unused IndexedTuple.
10
28
  * b0653566:fix: Delete unused Patch, patch_diff_tarfile, PathPatcher.
@@ -444,6 +462,10 @@ rel.2.0.1 / 2023-08-08
444
462
  * fc8da777:fix: Restore pre-parser. Fixes #727.
445
463
  * 295e641b:fix: Add missing import to cli_util.py. Fixes #730.
446
464
  * 97fd0957:fix: Add missing import to b2backend.py. Fixes #729.
465
+
466
+ rel.2.0.0 / 2023-08-07
467
+ ======================
468
+
447
469
  * 5cb97560:fix: Adjust version to build under LP.
448
470
  * 598352ba:fix: Adjust to build under LP Mantic.
449
471
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duplicity
3
- Version: 3.0.7.dev0
3
+ Version: 3.0.8.dev3
4
4
  Summary: Encrypted backup using rsync algorithm
5
5
  Author-email: Kenneth Loafman <kenneth@loafman.com>
6
6
  Maintainer: Edgar Soldin, Thomas Laubrock
@@ -21,7 +21,7 @@
21
21
 
22
22
  import gettext
23
23
 
24
- __version__: str = "3.0.7.dev0"
25
- __reldate__: str = "November 20, 2025"
24
+ __version__: str = "3.0.8.dev3"
25
+ __reldate__: str = "January 14, 2026"
26
26
 
27
27
  gettext.install("duplicity", names=["ngettext"])
@@ -74,19 +74,6 @@ class S3Boto3Backend(duplicity.backend.Backend):
74
74
  from boto3.s3.transfer import S3UploadFailedError, TransferConfig
75
75
  from botocore.exceptions import ClientError
76
76
 
77
- if not (boto3.__version__ < "1.36.0" and botocore.__version__ < "1.36.0"):
78
- # TODO: remove this workaround when issue #870 is fixed.
79
- # https://github.com/boto/boto3/issues/2913
80
- log.Warn(
81
- "WARNING: Using boto3 >= 1,36.0 may result in errors, so we qre applying\n"
82
- "the workaround for https://gitlab.com/duplicity/duplicity/-/issues/870\n"
83
- " export AWS_REQUEST_CHECKSUM_CALCULATION=when_required\n"
84
- " export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required\n"
85
- "NOTE: This workaround is temporary and will be removed when issue is fixed.\n."
86
- )
87
- os.environ["AWS_REQUEST_CHECKSUM_CALCULATION"] = "when_required"
88
- os.environ["AWS_RESPONSE_CHECKSUM_VALIDATION"] = "when_required"
89
-
90
77
  duplicity.backend.Backend.__init__(self, parsed_url)
91
78
 
92
79
  # This folds the null prefix and all null parts, which means that:
@@ -109,6 +96,25 @@ class S3Boto3Backend(duplicity.backend.Backend):
109
96
  self.bucket = None
110
97
  self.tracker = UploadProgressTracker()
111
98
 
99
+ if not (boto3.__version__ < "1.36.0" and botocore.__version__ < "1.36.0"):
100
+ # this is an issue with 3rd party s3 implementations only
101
+ # likely when an endpoint is given that resides not under amazonaws.com
102
+ # in time that workaround will probably not be needed anymore
103
+ # https://github.com/boto/boto3/issues/2913
104
+ import re
105
+
106
+ if config.s3_endpoint_url and not re.match(
107
+ pattern="(?i).*\\.amazonaws\\.com(/+)?$", string=config.s3_endpoint_url
108
+ ):
109
+ log.Warn(
110
+ "WARNING: Using boto3 >= 1,36.0 with non-amazon s3 services"
111
+ " may result in checksum errors."
112
+ " a workaround is to set the following env vars\n\n"
113
+ " export AWS_REQUEST_CHECKSUM_CALCULATION=when_required\n"
114
+ " export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required\n\n"
115
+ "see https://gitlab.com/duplicity/duplicity/-/issues/870 for details."
116
+ )
117
+
112
118
  def reset_connection(self):
113
119
  self.bucket = None
114
120
  self.s3 = boto3.resource(
@@ -194,9 +194,10 @@ class WebDAVBackend(duplicity.backend.Backend):
194
194
  if self.username or self.password:
195
195
  # Workaround cpython http.client issue
196
196
  # https://github.com/python/cpython/issues/70107
197
- self.conn.request("OPTIONS", self.directory, None)
198
- response = self.conn.getresponse()
199
- response.read()
197
+ # PUT may not return 401 when ran without basic-auth but throw SSL-EOF-Error or hang
198
+ # as a workaround we run an OPTIONS request that adds auth if needed and creates
199
+ # an authenticated connection to (re)use
200
+ response = self.request("OPTIONS", self.directory, None)
200
201
  response.close()
201
202
 
202
203
  def _close(self):
@@ -226,6 +227,8 @@ class WebDAVBackend(duplicity.backend.Backend):
226
227
 
227
228
  if self.digest_challenge is not None:
228
229
  self.headers["Authorization"] = self.get_digest_authorization(path)
230
+ elif self.username or self.password:
231
+ self.headers["Authorization"] = self.get_basic_authorization()
229
232
 
230
233
  log.Debug(_("WebDAV %s %s request with headers: %s ") % (method, quoted_path, munge_headers(self.headers)))
231
234
  log.Debug(_("WebDAV data length: %s ") % sys.getsizeof(data))
@@ -245,6 +248,7 @@ class WebDAVBackend(duplicity.backend.Backend):
245
248
  return self.request(method, self.directory, data, redirected + 1)
246
249
  else:
247
250
  raise FatalBackendException(_("WebDAV missing location header in redirect response."))
251
+ # mainly for digest-auth to recalculate with response values
248
252
  elif response.status == 401:
249
253
  response.read()
250
254
  response.close()
@@ -446,9 +446,9 @@ OptionKwargs = dict(
446
446
  type=set_log_file,
447
447
  help="Logging filename to use",
448
448
  ),
449
- # log_timestamp is directly applied in SetLogTimestampAction(), not saved in config
449
+ # log_timestamp is directly applied in set_log_timestamp(), not saved in config
450
450
  log_timestamp=dict(
451
- dest="",
451
+ nargs=0,
452
452
  action=SetLogTimestampAction,
453
453
  help="Whether to include timestamp and level in log",
454
454
  default=dflt(False),
@@ -160,7 +160,7 @@ class SetLogTimestampAction(argparse._StoreConstAction):
160
160
  super().__init__(option_strings, dest, **kwargs)
161
161
 
162
162
  def __call__(self, parser, namespace, values, option_string=None):
163
- log._log_timestamp = True
163
+ log.add_timestamp()
164
164
 
165
165
 
166
166
  def _check_int(val):
@@ -37,7 +37,7 @@ from duplicity import dup_tarfile
37
37
  from duplicity import util
38
38
  from duplicity.path import * # pylint: disable=unused-wildcard-import,redefined-builtin
39
39
 
40
- # A StatsObj will be written to this from DirDelta and DirDelta_WriteSig.
40
+ # A StatsObj will be written to this from DirDelta_WriteSig.
41
41
  stats = None
42
42
  tracker = None
43
43
 
@@ -55,7 +55,7 @@ def DirFull(path_iter):
55
55
  will be easy to split up the tar and make the volumes the same
56
56
  sizes).
57
57
  """
58
- return DirDelta(path_iter, io.StringIO(""))
58
+ return DirDelta_WriteSig(path_iter, io.StringIO(""), None)
59
59
 
60
60
 
61
61
  def DirFull_WriteSig(path_iter, sig_outfp):
@@ -65,26 +65,6 @@ def DirFull_WriteSig(path_iter, sig_outfp):
65
65
  return DirDelta_WriteSig(path_iter, io.StringIO(""), sig_outfp)
66
66
 
67
67
 
68
- def DirDelta(path_iter, dirsig_fileobj_list):
69
- """
70
- Produce tarblock diff given dirsig_fileobj_list and pathiter
71
-
72
- dirsig_fileobj_list should either be a tar fileobj or a list of
73
- those, sorted so the most recent is last.
74
- """
75
- global stats
76
- stats = statistics.StatsDeltaProcess()
77
- if isinstance(dirsig_fileobj_list, list):
78
- sig_iter = combine_path_iters([sigtar2path_iter(x) for x in dirsig_fileobj_list])
79
- else:
80
- sig_iter = sigtar2path_iter(dirsig_fileobj_list)
81
- delta_iter = get_delta_iter(path_iter, sig_iter)
82
- if config.dry_run or (config.progress and not progress.tracker.has_collected_evidence()):
83
- return DummyBlockIter(delta_iter)
84
- else:
85
- return DeltaTarBlockIter(delta_iter)
86
-
87
-
88
68
  def delta_iter_error_handler(exc, new_path, sig_path, sig_tar=None): # pylint: disable=unused-argument
89
69
  """
90
70
  Called by get_delta_iter, report error in getting delta
@@ -132,39 +132,47 @@ def get_passphrase(n, action, for_signing=False):
132
132
  log.Notice(_("Reuse configured SIGN_PASSPHRASE as PASSPHRASE"))
133
133
  return os.environ["SIGN_PASSPHRASE"]
134
134
 
135
- # Not in the environment, check if encryption passphrase is needed
136
- asymmetric = False
137
- need_passphrase = False
138
- profile = config.gpg_profile
139
- encrypt_keys = profile.recipients + profile.hidden_recipients
140
- if profile.sign_key:
141
- encrypt_keys.append(profile.sign_key)
142
- if encrypt_keys:
143
- asymmetric = True
144
- for key in encrypt_keys:
145
- if util.key_needs_passphrase(key):
146
- log.Notice(f"Key {key} needs passphrase.")
147
- need_passphrase = True
148
- break
149
- else:
150
- log.Notice("No encryption keys need passphrase.")
151
- else:
152
- symmetric = True
153
- need_passphrase = True
154
- log.Notice("No encryption keys configured.")
155
-
156
- skips = copy.copy(skips_sync_archive)
157
- skips.remove("full")
158
- if (action == "full" and asymmetric) or config.restart or action in skips:
159
- log.Notice(f"Skipping passphrase request for action {action}")
135
+ # Next, verify we need to ask the user
136
+
137
+ # Assumptions:
138
+ # - encrypt-key has no passphrase
139
+ # - sign-key requires passphrase
140
+ # - gpg-agent supplies all, no user interaction
141
+
142
+ # no passphrase if --no-encryption or --use-agent
143
+ if not config.encryption or config.use_agent:
160
144
  return ""
161
145
 
162
- elif asymmetric and not need_passphrase:
163
- log.Notice(_("Skipping because no encryption key passphrase is needed."))
146
+ # these commands don't need a password
147
+ elif action in [
148
+ "collection-status",
149
+ "list-current-files",
150
+ "remove-all-but-n-full",
151
+ "remove-all-inc-of-but-n-full",
152
+ "remove-older-than",
153
+ ]:
154
+ return ""
155
+
156
+ # for a full, inc, verify, we don't need a password if
157
+ # there is no sign_key and there are recipients
158
+ elif (
159
+ action in ("full", "inc", "verify")
160
+ and (config.gpg_profile.recipients or config.gpg_profile.hidden_recipients)
161
+ and (not config.gpg_profile.sign_key or (not config.restart and not for_signing))
162
+ ):
163
+ return ""
164
+
165
+ elif (
166
+ (config.gpg_profile.recipients or config.gpg_profile.hidden_recipients)
167
+ and config.metadata_sync_mode == "partial"
168
+ and action in ["full"]
169
+ ):
170
+ log.Info(_("Skipping passphrase input for full backup with encryption keys."))
164
171
  return ""
165
172
 
173
+ # Finally, ask the user for the passphrase
166
174
  else:
167
- log.Notice(_("No environment variables are set, asking user."))
175
+ log.Info(_("PASSPHRASE variable not set, asking user."))
168
176
  use_cache = True
169
177
  while True:
170
178
  # ask the user to enter a new passphrase to avoid an infinite loop
@@ -758,7 +766,7 @@ def incremental_backup(sig_chain, col_stats=None):
758
766
  if config.progress:
759
767
  progress.tracker = progress.ProgressTracker()
760
768
  # Fake a backup to compute total of moving bytes
761
- tarblock_iter = diffdir.DirDelta(config.select, sig_chain.get_fileobjs())
769
+ tarblock_iter = diffdir.DirDelta_WriteSig(config.select, sig_chain.get_fileobjs(), None)
762
770
  dummy_backup(tarblock_iter)
763
771
  # Store computed stats to compute progress later
764
772
  progress.tracker.set_evidence(diffdir.stats, False)
@@ -768,7 +776,7 @@ def incremental_backup(sig_chain, col_stats=None):
768
776
  progress.progress_thread = progress.LogProgressThread()
769
777
 
770
778
  if config.dry_run:
771
- tarblock_iter = diffdir.DirDelta(config.select, sig_chain.get_fileobjs())
779
+ tarblock_iter = diffdir.DirDelta_WriteSig(config.select, sig_chain.get_fileobjs(), None)
772
780
  bytes_written = dummy_backup(tarblock_iter)
773
781
  else:
774
782
  new_sig_outfp = get_sig_fileobj("new-sig")
@@ -40,7 +40,6 @@ MAX = 9
40
40
  PREFIX = ""
41
41
 
42
42
  _logger = None
43
- _log_timestamp = False
44
43
 
45
44
 
46
45
  def DupToLoggerLevel(verb):
@@ -321,7 +320,6 @@ def setup():
321
320
  Initialize logging
322
321
  """
323
322
  global _logger
324
- global _log_timestamp
325
323
  if _logger:
326
324
  return
327
325
 
@@ -333,18 +331,12 @@ def setup():
333
331
 
334
332
  # stdout and stderr are for different logging levels
335
333
  outHandler = logging.StreamHandler(sys.stdout)
336
- if _log_timestamp:
337
- outHandler.setFormatter(DetailFormatter())
338
- else:
339
- outHandler.setFormatter(PrettyProgressFormatter())
334
+ outHandler.setFormatter(PrettyProgressFormatter())
340
335
  outHandler.addFilter(OutFilter())
341
336
  _logger.addHandler(outHandler)
342
337
 
343
338
  errHandler = logging.StreamHandler(sys.stderr)
344
- if _log_timestamp:
345
- errHandler.setFormatter(DetailFormatter())
346
- else:
347
- errHandler.setFormatter(PrettyProgressFormatter())
339
+ errHandler.setFormatter(PrettyProgressFormatter())
348
340
  errHandler.addFilter(ErrFilter())
349
341
  _logger.addHandler(errHandler)
350
342
 
@@ -388,7 +380,7 @@ class DetailFormatter(logging.Formatter):
388
380
  # standard 'levelname'. This is because the standard 'levelname' can
389
381
  # be adjusted by any library anywhere in our stack without us knowing.
390
382
  # But we control 'levelName'.
391
- logging.Formatter.__init__(self, "%(asctime)s %(levelName)s %(message)s")
383
+ logging.Formatter.__init__(self, "%(asctime)s %(levelName)-6s %(message)s")
392
384
 
393
385
  def format(self, record):
394
386
  s = logging.Formatter.format(self, record)
@@ -452,6 +444,28 @@ def add_file(filename):
452
444
  _logger.addHandler(handler)
453
445
 
454
446
 
447
+ def add_timestamp():
448
+ """
449
+ Add timestamp to logs written
450
+ """
451
+ global _logger
452
+
453
+ # remove all handlers
454
+ for handler in _logger.handlers[:]:
455
+ _logger.removeHandler(handler)
456
+
457
+ # stdout and stderr are for different logging levels
458
+ outHandler = logging.StreamHandler(sys.stdout)
459
+ outHandler.setFormatter(DetailFormatter())
460
+ outHandler.addFilter(OutFilter())
461
+ _logger.addHandler(outHandler)
462
+
463
+ errHandler = logging.StreamHandler(sys.stderr)
464
+ errHandler.setFormatter(DetailFormatter())
465
+ errHandler.addFilter(ErrFilter())
466
+ _logger.addHandler(errHandler)
467
+
468
+
455
469
  def setverbosity(verb):
456
470
  """
457
471
  Set the verbosity level.
@@ -623,13 +623,8 @@ class Path(ROPath):
623
623
 
624
624
  def deltree(self):
625
625
  """Remove self by recursively deleting files under it"""
626
- from duplicity import selection # TODO: avoid circ. dep. issue
627
-
628
626
  log.Debug(_("Deleting tree %s") % self.uc_name)
629
- itr = IterTreeReducer(PathDeleter, [])
630
- for path in selection.Select(self).set_iter():
631
- itr(path.index, path)
632
- itr.Finish()
627
+ shutil.rmtree(self.name)
633
628
  self.setdata()
634
629
 
635
630
  def get_parent_dir(self):
@@ -807,19 +802,3 @@ class DupPath(Path):
807
802
  return gpg.GPGFile(True, self, gpg_profile)
808
803
  else:
809
804
  return self.open(mode)
810
-
811
-
812
- class PathDeleter(ITRBranch):
813
- """Delete a directory. Called by Path.deltree"""
814
-
815
- def start_process(self, index, path): # pylint: disable=unused-argument
816
- self.path = path
817
-
818
- def end_process(self):
819
- self.path.delete()
820
-
821
- def can_fast_process(self, index, path): # pylint: disable=unused-argument
822
- return not path.isdir()
823
-
824
- def fast_process(self, index, path): # pylint: disable=unused-argument
825
- path.delete()
@@ -27,11 +27,13 @@ import atexit
27
27
  import csv
28
28
  import errno
29
29
  import json
30
+ import locale
30
31
  import multiprocessing
31
32
  import os
32
33
  import socket
33
34
  import sys
34
35
  import traceback
36
+ from contextlib import contextmanager
35
37
  from io import StringIO
36
38
 
37
39
  import fasteners
@@ -203,30 +205,6 @@ def release_lockfile():
203
205
  pass
204
206
 
205
207
 
206
- def key_needs_passphrase(key):
207
- """
208
- Check if a key needs a passphrase.
209
- """
210
- try:
211
- child = pexpect.spawn("gpg", f"--pinentry-mode=loopback --dry-run --passwd {key}".split())
212
- except Exception:
213
- log.FatalError(f"Exception spawning gpg while checking if passphrase needed for key: {key}")
214
-
215
- try:
216
- got = child.expect(["passphrase.*:", pexpect.EOF])
217
- except Exception:
218
- log.FatalError(f"Exception while checking if passphrase needed for key: {key}: {str(child)}")
219
-
220
- if got == 0:
221
- log.Debug(f"Key {key} needs passphrase")
222
- child.close()
223
- return True
224
- elif got == 1:
225
- log.Debug(f"Key {key} does not need passphrase")
226
- return False
227
- return None
228
-
229
-
230
208
  def copyfileobj(infp, outfp, byte_count=-1):
231
209
  """
232
210
  Copy byte_count bytes from infp to outfp, or all if byte_count < 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duplicity
3
- Version: 3.0.7.dev0
3
+ Version: 3.0.8.dev3
4
4
  Summary: Encrypted backup using rsync algorithm
5
5
  Author-email: Kenneth Loafman <kenneth@loafman.com>
6
6
  Maintainer: Edgar Soldin, Thomas Laubrock
@@ -1,4 +1,4 @@
1
- .TH DUPLICITY 1 "November 20, 2025" "Version 3.0.7.dev0" "User Manuals" \" -*- nroff -*-
1
+ .TH DUPLICITY 1 "January 14, 2026" "Version 3.0.8.dev3" "User Manuals" \" -*- nroff -*-
2
2
  .\" disable justification (adjust text to left margin only)
3
3
  .\" command line examples stay readable through that
4
4
  .ad l
@@ -497,23 +497,6 @@ See the
497
497
  .B FILE SELECTION
498
498
  section for more information.
499
499
 
500
- .TP
501
- .BI "--files-from " filename
502
- Read a list of files to backup from filename rather than searching the entire
503
- backup source directory. Operation is otherwise normal, just on the specified
504
- subset of the backup source directory.
505
-
506
- Files must be specified one per line and relative to the backup source
507
- directory. Any absolute paths will raise an error. All characters per line are
508
- significant and treated as part of the path, including leading and trailing
509
- whitespace. Lines are separated by newlines or nulls, depending on whether the
510
- .B "--null-separator"
511
- switch was given.
512
-
513
- It is not necessary to include the parent directory of listed files, their
514
- inclusion is implied. However, the content of any explicitly listed directories
515
- is not implied. All required files must be listed when this option is used.
516
-
517
500
  .TP
518
501
  .BI "--file-prefix " prefix
519
502
  .PD 0
@@ -535,12 +518,21 @@ See also
535
518
  .B "A NOTE ON FILENAME PREFIXES"
536
519
 
537
520
  .TP
538
- .BI "--path-to-restore " path
539
- This option may be given in restore mode, causing only
540
- .I path
541
- to be restored instead of the entire contents of the backup archive.
542
- .I path
543
- should be given relative to the root of the directory backed up.
521
+ .BI "--files-from " filename
522
+ Read a list of files to backup from filename rather than searching the entire
523
+ backup source directory. Operation is otherwise normal, just on the specified
524
+ subset of the backup source directory.
525
+
526
+ Files must be specified one per line and relative to the backup source
527
+ directory. Any absolute paths will raise an error. All characters per line are
528
+ significant and treated as part of the path, including leading and trailing
529
+ whitespace. Lines are separated by newlines or nulls, depending on whether the
530
+ .B "--null-separator"
531
+ switch was given.
532
+
533
+ It is not necessary to include the parent directory of listed files, their
534
+ inclusion is implied. However, the content of any explicitly listed directories
535
+ is not implied. All required files must be listed when this option is used.
544
536
 
545
537
  .TP
546
538
  .BI --filter-globbing
@@ -572,15 +564,6 @@ See the
572
564
  .B FILE SELECTION
573
565
  section for more information.
574
566
 
575
- .TP
576
- .BI "--full-if-older-than " time
577
- Perform a full backup if an incremental backup is requested, but the
578
- latest full backup in the collection is older than the given
579
- .IR time .
580
- See the
581
- .B TIME FORMATS
582
- section for more information.
583
-
584
567
  .TP
585
568
  .BI --force
586
569
  Proceed even if data loss might result. Duplicity will let the user
@@ -596,6 +579,15 @@ out.
596
579
  .BI --ftp-regular
597
580
  Use regular (PORT) data connections.
598
581
 
582
+ .TP
583
+ .BI "--full-if-older-than " time
584
+ Perform a full backup if an incremental backup is requested, but the
585
+ latest full backup in the collection is older than the given
586
+ .IR time .
587
+ See the
588
+ .B TIME FORMATS
589
+ section for more information.
590
+
599
591
  .TP
600
592
  .BI --gio
601
593
  Use the GIO backend and interpret any URLs as GIO would.
@@ -862,6 +854,14 @@ for Par2 recovery files (default 10%).
862
854
  .BI "--par2-volumes " number
863
855
  Number of Par2 volumes to create (default 1).
864
856
 
857
+ .TP
858
+ .BI "--path-to-restore " path
859
+ This option may be given in restore mode, causing only
860
+ .I path
861
+ to be restored instead of the entire contents of the backup archive.
862
+ .I path
863
+ should be given relative to the root of the directory backed up.
864
+
865
865
  .TP
866
866
  .BI --progress
867
867
  When selected, duplicity will output the current upload progress and estimated
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "duplicity"
3
- version = "3.0.7.dev0"
3
+ version = "3.0.8.dev3"
4
4
  dynamic = ["dependencies"]
5
5
  description = "Encrypted backup using rsync algorithm"
6
6
  authors = [
@@ -126,9 +126,6 @@ addopts = [
126
126
  "-p no:randomly",
127
127
  "-p no:xdist",
128
128
  ]
129
- markers = [
130
- "slow: test runs >= 10 secs",
131
- ]
132
129
  testpaths = [
133
130
  "testing/test_code.py",
134
131
  "testing/unit",
@@ -42,7 +42,7 @@ elif not ((3, 9) <= sys.version_info[:2] <= (3, 14)):
42
42
  print("Sorry, duplicity requires version 3.9 thru 3.14 of Python.", file=sys.stderr)
43
43
  sys.exit(1)
44
44
 
45
- Version: str = "3.0.7.dev0"
45
+ Version: str = "3.0.8.dev3"
46
46
 
47
47
  # READTHEDOCS uses setup.py sdist but can't handle extensions
48
48
  ext_modules = list()
File without changes
File without changes
File without changes