duplicity 2.2.2__tar.gz → 2.2.3__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 (250) hide show
  1. {duplicity-2.2.2 → duplicity-2.2.3}/CHANGELOG.md +83 -0
  2. {duplicity-2.2.2/duplicity.egg-info → duplicity-2.2.3}/PKG-INFO +1 -3
  3. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/__init__.py +2 -2
  4. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backend.py +38 -1
  5. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/README +10 -0
  6. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/_testbackend.py +74 -6
  7. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/b2backend.py +5 -3
  8. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/cli_data.py +1 -0
  9. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/cli_main.py +13 -0
  10. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_collections.py +1 -8
  11. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_main.py +9 -26
  12. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/log.py +1 -0
  13. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/path.py +4 -4
  14. {duplicity-2.2.2 → duplicity-2.2.3/duplicity.egg-info}/PKG-INFO +1 -3
  15. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity.egg-info/SOURCES.txt +5 -1
  16. {duplicity-2.2.2 → duplicity-2.2.3}/man/duplicity.1 +47 -200
  17. {duplicity-2.2.2 → duplicity-2.2.3}/po/af_ZA/duplicity.mo +0 -0
  18. {duplicity-2.2.2 → duplicity-2.2.3}/po/af_ZA.po +168 -165
  19. {duplicity-2.2.2 → duplicity-2.2.3}/po/ar_SA/duplicity.mo +0 -0
  20. {duplicity-2.2.2 → duplicity-2.2.3}/po/ar_SA.po +168 -169
  21. {duplicity-2.2.2 → duplicity-2.2.3}/po/ca_ES/duplicity.mo +0 -0
  22. {duplicity-2.2.2 → duplicity-2.2.3}/po/ca_ES.po +168 -165
  23. {duplicity-2.2.2 → duplicity-2.2.3}/po/cs_CZ/duplicity.mo +0 -0
  24. {duplicity-2.2.2 → duplicity-2.2.3}/po/cs_CZ.po +168 -167
  25. {duplicity-2.2.2 → duplicity-2.2.3}/po/da_DK/duplicity.mo +0 -0
  26. {duplicity-2.2.2 → duplicity-2.2.3}/po/da_DK.po +168 -165
  27. {duplicity-2.2.2 → duplicity-2.2.3}/po/de_AT/duplicity.mo +0 -0
  28. {duplicity-2.2.2 → duplicity-2.2.3}/po/de_AT.po +168 -165
  29. {duplicity-2.2.2 → duplicity-2.2.3}/po/de_DE/duplicity.mo +0 -0
  30. {duplicity-2.2.2 → duplicity-2.2.3}/po/de_DE.po +168 -165
  31. {duplicity-2.2.2 → duplicity-2.2.3}/po/duplicity.pot +167 -164
  32. {duplicity-2.2.2 → duplicity-2.2.3}/po/el_GR/duplicity.mo +0 -0
  33. {duplicity-2.2.2 → duplicity-2.2.3}/po/el_GR.po +168 -165
  34. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_AU/duplicity.mo +0 -0
  35. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_AU.po +168 -165
  36. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_GB/duplicity.mo +0 -0
  37. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_GB.po +168 -165
  38. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_PR/duplicity.mo +0 -0
  39. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_PR.po +168 -165
  40. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_US/duplicity.mo +0 -0
  41. {duplicity-2.2.2 → duplicity-2.2.3}/po/en_US.po +168 -165
  42. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_EM/duplicity.mo +0 -0
  43. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_EM.po +168 -165
  44. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_ES/duplicity.mo +0 -0
  45. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_ES.po +168 -165
  46. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_MX/duplicity.mo +0 -0
  47. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_MX.po +168 -165
  48. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_PR/duplicity.mo +0 -0
  49. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_PR.po +168 -165
  50. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_US/duplicity.mo +0 -0
  51. {duplicity-2.2.2 → duplicity-2.2.3}/po/es_US.po +168 -165
  52. {duplicity-2.2.2 → duplicity-2.2.3}/po/fi_FI/duplicity.mo +0 -0
  53. {duplicity-2.2.2 → duplicity-2.2.3}/po/fi_FI.po +168 -165
  54. {duplicity-2.2.2 → duplicity-2.2.3}/po/fr_FR/duplicity.mo +0 -0
  55. {duplicity-2.2.2 → duplicity-2.2.3}/po/fr_FR.po +168 -165
  56. {duplicity-2.2.2 → duplicity-2.2.3}/po/he_IL/duplicity.mo +0 -0
  57. {duplicity-2.2.2 → duplicity-2.2.3}/po/he_IL.po +168 -167
  58. {duplicity-2.2.2 → duplicity-2.2.3}/po/hu_HU/duplicity.mo +0 -0
  59. {duplicity-2.2.2 → duplicity-2.2.3}/po/hu_HU.po +168 -165
  60. {duplicity-2.2.2 → duplicity-2.2.3}/po/it_IT/duplicity.mo +0 -0
  61. {duplicity-2.2.2 → duplicity-2.2.3}/po/it_IT.po +168 -165
  62. {duplicity-2.2.2 → duplicity-2.2.3}/po/ja_JP/duplicity.mo +0 -0
  63. {duplicity-2.2.2 → duplicity-2.2.3}/po/ja_JP.po +168 -164
  64. {duplicity-2.2.2 → duplicity-2.2.3}/po/ko_KR/duplicity.mo +0 -0
  65. {duplicity-2.2.2 → duplicity-2.2.3}/po/ko_KR.po +168 -164
  66. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_BE/duplicity.mo +0 -0
  67. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_BE.po +168 -165
  68. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_NL/duplicity.mo +0 -0
  69. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_NL.po +168 -165
  70. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_SR/duplicity.mo +0 -0
  71. {duplicity-2.2.2 → duplicity-2.2.3}/po/nl_SR.po +168 -165
  72. {duplicity-2.2.2 → duplicity-2.2.3}/po/no_NO/duplicity.mo +0 -0
  73. {duplicity-2.2.2 → duplicity-2.2.3}/po/no_NO.po +168 -165
  74. {duplicity-2.2.2 → duplicity-2.2.3}/po/pl_PL/duplicity.mo +0 -0
  75. {duplicity-2.2.2 → duplicity-2.2.3}/po/pl_PL.po +168 -167
  76. {duplicity-2.2.2 → duplicity-2.2.3}/po/pt_BR/duplicity.mo +0 -0
  77. {duplicity-2.2.2 → duplicity-2.2.3}/po/pt_BR.po +168 -165
  78. {duplicity-2.2.2 → duplicity-2.2.3}/po/pt_PT/duplicity.mo +0 -0
  79. {duplicity-2.2.2 → duplicity-2.2.3}/po/pt_PT.po +168 -165
  80. {duplicity-2.2.2 → duplicity-2.2.3}/po/ro_RO/duplicity.mo +0 -0
  81. {duplicity-2.2.2 → duplicity-2.2.3}/po/ro_RO.po +168 -166
  82. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_BY/duplicity.mo +0 -0
  83. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_BY.po +168 -167
  84. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_MD/duplicity.mo +0 -0
  85. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_MD.po +168 -167
  86. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_RU/duplicity.mo +0 -0
  87. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_RU.po +168 -167
  88. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_UA/duplicity.mo +0 -0
  89. {duplicity-2.2.2 → duplicity-2.2.3}/po/ru_UA.po +168 -167
  90. {duplicity-2.2.2 → duplicity-2.2.3}/po/sr_SP/duplicity.mo +0 -0
  91. {duplicity-2.2.2 → duplicity-2.2.3}/po/sr_SP.po +168 -166
  92. {duplicity-2.2.2 → duplicity-2.2.3}/po/sv_SE/duplicity.mo +0 -0
  93. {duplicity-2.2.2 → duplicity-2.2.3}/po/sv_SE.po +168 -165
  94. {duplicity-2.2.2 → duplicity-2.2.3}/po/tr_TR/duplicity.mo +0 -0
  95. {duplicity-2.2.2 → duplicity-2.2.3}/po/tr_TR.po +168 -165
  96. {duplicity-2.2.2 → duplicity-2.2.3}/po/uk_UA/duplicity.mo +0 -0
  97. {duplicity-2.2.2 → duplicity-2.2.3}/po/uk_UA.po +168 -167
  98. {duplicity-2.2.2 → duplicity-2.2.3}/po/vi_VN/duplicity.mo +0 -0
  99. {duplicity-2.2.2 → duplicity-2.2.3}/po/vi_VN.po +168 -164
  100. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_CN/duplicity.mo +0 -0
  101. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_CN.po +168 -164
  102. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_HK/duplicity.mo +0 -0
  103. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_HK.po +168 -164
  104. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_MO/duplicity.mo +0 -0
  105. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_MO.po +168 -164
  106. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_SG/duplicity.mo +0 -0
  107. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_SG.po +168 -164
  108. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_TW/duplicity.mo +0 -0
  109. {duplicity-2.2.2 → duplicity-2.2.3}/po/zh_TW.po +168 -164
  110. {duplicity-2.2.2 → duplicity-2.2.3}/pyproject.toml +18 -10
  111. {duplicity-2.2.2 → duplicity-2.2.3}/requirements.txt +2 -1
  112. {duplicity-2.2.2 → duplicity-2.2.3}/setup.py +39 -70
  113. {duplicity-2.2.2 → duplicity-2.2.3}/snap/snapcraft.yaml +1 -1
  114. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/__init__.py +19 -16
  115. duplicity-2.2.3/testing/functional/test_badupload.py +110 -0
  116. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_log.py +6 -4
  117. {duplicity-2.2.2 → duplicity-2.2.3}/testing/test_code.py +2 -10
  118. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_backend.py +87 -2
  119. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_patchdir.py +1 -1
  120. {duplicity-2.2.2 → duplicity-2.2.3}/tox.ini +0 -2
  121. duplicity-2.2.2/crowdin.yml +0 -6
  122. duplicity-2.2.2/testing/functional/test_badupload.py +0 -53
  123. {duplicity-2.2.2 → duplicity-2.2.3}/AUTHORS.md +0 -0
  124. {duplicity-2.2.2 → duplicity-2.2.3}/COPYING +0 -0
  125. {duplicity-2.2.2 → duplicity-2.2.3}/README-LOG.md +0 -0
  126. {duplicity-2.2.2 → duplicity-2.2.3}/README-REPO.md +0 -0
  127. {duplicity-2.2.2 → duplicity-2.2.3}/README-SNAP.md +0 -0
  128. {duplicity-2.2.2 → duplicity-2.2.3}/README-TESTING.md +0 -0
  129. {duplicity-2.2.2 → duplicity-2.2.3}/README.md +0 -0
  130. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/__main__.py +0 -0
  131. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/_librsyncmodule.c +0 -0
  132. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/argparse311.py +0 -0
  133. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/asyncscheduler.py +0 -0
  134. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/__init__.py +0 -0
  135. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/_cf_cloudfiles.py +0 -0
  136. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/_cf_pyrax.py +0 -0
  137. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/adbackend.py +0 -0
  138. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/azurebackend.py +0 -0
  139. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/boxbackend.py +0 -0
  140. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/cfbackend.py +0 -0
  141. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/dpbxbackend.py +0 -0
  142. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/gdocsbackend.py +0 -0
  143. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/gdrivebackend.py +0 -0
  144. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/giobackend.py +0 -0
  145. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/hsibackend.py +0 -0
  146. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/hubicbackend.py +0 -0
  147. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/idrivedbackend.py +0 -0
  148. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/imapbackend.py +0 -0
  149. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/jottacloudbackend.py +0 -0
  150. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/lftpbackend.py +0 -0
  151. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/localbackend.py +0 -0
  152. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/mediafirebackend.py +0 -0
  153. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/megabackend.py +0 -0
  154. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/megav2backend.py +0 -0
  155. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/megav3backend.py +0 -0
  156. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/multibackend.py +0 -0
  157. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/ncftpbackend.py +0 -0
  158. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/onedrivebackend.py +0 -0
  159. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/par2backend.py +0 -0
  160. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/pcabackend.py +0 -0
  161. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/pydrivebackend.py +0 -0
  162. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/pyrax_identity/__init__.py +0 -0
  163. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/pyrax_identity/hubic.py +0 -0
  164. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/rclonebackend.py +0 -0
  165. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/rsyncbackend.py +0 -0
  166. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/s3_boto3_backend.py +0 -0
  167. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/slatebackend.py +0 -0
  168. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/ssh_paramiko_backend.py +0 -0
  169. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/ssh_pexpect_backend.py +0 -0
  170. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/swiftbackend.py +0 -0
  171. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/sxbackend.py +0 -0
  172. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/tahoebackend.py +0 -0
  173. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/webdavbackend.py +0 -0
  174. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/backends/xorrisobackend.py +0 -0
  175. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/cached_ops.py +0 -0
  176. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/cli_util.py +0 -0
  177. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/config.py +0 -0
  178. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/diffdir.py +0 -0
  179. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_tarfile.py +0 -0
  180. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_temp.py +0 -0
  181. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_threading.py +0 -0
  182. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/dup_time.py +0 -0
  183. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/errors.py +0 -0
  184. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/file_naming.py +0 -0
  185. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/filechunkio.py +0 -0
  186. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/globmatch.py +0 -0
  187. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/gpg.py +0 -0
  188. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/gpginterface.py +0 -0
  189. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/lazy.py +0 -0
  190. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/librsync.py +0 -0
  191. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/manifest.py +0 -0
  192. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/patchdir.py +0 -0
  193. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/progress.py +0 -0
  194. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/robust.py +0 -0
  195. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/selection.py +0 -0
  196. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/statistics.py +0 -0
  197. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/tempdir.py +0 -0
  198. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity/util.py +0 -0
  199. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity.egg-info/dependency_links.txt +0 -0
  200. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity.egg-info/entry_points.txt +0 -0
  201. {duplicity-2.2.2 → duplicity-2.2.3}/duplicity.egg-info/top_level.txt +0 -0
  202. {duplicity-2.2.2 → duplicity-2.2.3}/po/LINGUAS +0 -0
  203. {duplicity-2.2.2 → duplicity-2.2.3}/po/Makevars +0 -0
  204. {duplicity-2.2.2 → duplicity-2.2.3}/po/POTFILES.in +0 -0
  205. {duplicity-2.2.2 → duplicity-2.2.3}/po/POTFILES.skip +0 -0
  206. {duplicity-2.2.2 → duplicity-2.2.3}/po/update-pot +0 -0
  207. {duplicity-2.2.2 → duplicity-2.2.3}/setup.cfg +0 -0
  208. {duplicity-2.2.2 → duplicity-2.2.3}/snap/local/debug.sh +0 -0
  209. {duplicity-2.2.2 → duplicity-2.2.3}/snap/local/launcher.sh +0 -0
  210. {duplicity-2.2.2 → duplicity-2.2.3}/testing/__init__.py +0 -0
  211. {duplicity-2.2.2 → duplicity-2.2.3}/testing/conftest.py +0 -0
  212. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_cleanup.py +0 -0
  213. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_final.py +0 -0
  214. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_restart.py +0 -0
  215. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_selection.py +0 -0
  216. {duplicity-2.2.2 → duplicity-2.2.3}/testing/functional/test_verify.py +0 -0
  217. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/README +0 -0
  218. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/gpg-agent.conf +0 -0
  219. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/gpg.conf +0 -0
  220. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/pubring.gpg +0 -0
  221. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/secring.gpg +0 -0
  222. {duplicity-2.2.2 → duplicity-2.2.3}/testing/gnupg/trustdb.gpg +0 -0
  223. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/__init__.py +0 -0
  224. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/hsi +0 -0
  225. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/lftp +0 -0
  226. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/ncftpget +0 -0
  227. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/ncftpls +0 -0
  228. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/ncftpput +0 -0
  229. {duplicity-2.2.2 → duplicity-2.2.3}/testing/overrides/bin/tahoe +0 -0
  230. {duplicity-2.2.2 → duplicity-2.2.3}/testing/run-tests +0 -0
  231. {duplicity-2.2.2 → duplicity-2.2.3}/testing/testfiles.tar.gz +0 -0
  232. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/__init__.py +0 -0
  233. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_backend_instance.py +0 -0
  234. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_cli_main.py +0 -0
  235. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_collections.py +0 -0
  236. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_diffdir.py +0 -0
  237. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_dup_temp.py +0 -0
  238. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_dup_time.py +0 -0
  239. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_file_naming.py +0 -0
  240. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_globmatch.py +0 -0
  241. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_gpg.py +0 -0
  242. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_gpginterface.py +0 -0
  243. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_lazy.py +0 -0
  244. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_manifest.py +0 -0
  245. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_path.py +0 -0
  246. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_selection.py +0 -0
  247. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_statistics.py +0 -0
  248. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_tarfile.py +0 -0
  249. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_tempdir.py +0 -0
  250. {duplicity-2.2.2 → duplicity-2.2.3}/testing/unit/test_util.py +0 -0
@@ -1,6 +1,89 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## rel.2.2.3 (2024-03-20)
5
+
6
+ ### New
7
+
8
+ * Add test case issue725.sh. [Kenneth Loafman]
9
+
10
+ ### Changes
11
+
12
+ * Remove setuptools\_scm and usages. [Kenneth Loafman]
13
+
14
+ * Run po/update-pot. [Kenneth Loafman]
15
+
16
+ * !Minor changes to pip build and test. [Kenneth Loafman]
17
+
18
+ * Pip install duplicity fails with "AssertionError: es\_PR" (or other
19
+ language) [Kenneth Loafman]
20
+
21
+ * Launchpad PPAs not built correctly for 2.2.[0,1], PyPI pips for
22
+ 2.2.[0,1,2] [Kenneth Loafman]
23
+
24
+ - tested on:
25
+ - Ubuntu 20.04, 22.04, 24.04
26
+ - Python 3.8, 3.9, 3.10, 3.11, 3.12
27
+ - added tools/testpip
28
+ - added wheel builds to .gitlab-ci.yml
29
+ - build wheels for manylinux
30
+ - added tools/testpip
31
+ - remove disutils includes in setup.py
32
+ - remove old test command in setup.py
33
+ - moved use of setuptools-scm to scmversion command
34
+ - add tools to build pyenv & pythons & more.
35
+ - installpyenv - install pyenv and environ
36
+ - installpythons - install pythons and environ
37
+ - pushpip - push pip sdist and wheels
38
+ - tools/list_python_files - removed, unused
39
+
40
+ Fixes #797
41
+
42
+ * Change ngettext to \_ and fix wording. [Kenneth Loafman]
43
+
44
+ * Move validate to backend. [Thomas Laubrock]
45
+
46
+ * Run only one pipeline on merge request. [Kenneth Loafman]
47
+
48
+ * Small fixes to the test system. [Kenneth Loafman]
49
+
50
+ - run test cases with the same Python as pytest
51
+ - fix some comments that pointed to wrong config file
52
+ - code tests now run without RUN_CODE_TESTS=1
53
+
54
+ * Change from --capture=no to --capture=fd. [Kenneth Loafman]
55
+
56
+ - Should only print on test failure.
57
+
58
+ * Warn on symmetric encrypt with --use-agent. [Kenneth Loafman]
59
+
60
+ Fixes #799
61
+
62
+ * Warn on symmetric encrypt with --use-agent. [Kenneth Loafman]
63
+
64
+ Fixes #799
65
+
66
+ * Remove last of bin/duplicity. [Kenneth Loafman]
67
+
68
+ ### Fix
69
+
70
+ * Add fail open condition, for backends with no support. [Thomas Laubrock]
71
+
72
+ * Update homepage. [sblondon]
73
+
74
+ * Change path logging from Info to Debug. [Kenneth Loafman]
75
+
76
+ Fixes #804
77
+
78
+ * Whoops! Remove parens in fix of #803. [Kenneth Loafman]
79
+
80
+ * Exception using --exclude-older-than. [Kenneth Loafman]
81
+
82
+ fixes #803
83
+
84
+ * Manpage remove superfluous indentions, some formatting fixes. [ede]
85
+
86
+
4
87
  ## rel.2.2.2 (2024-02-03)
5
88
 
6
89
  ### Changes
@@ -1,7 +1,5 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: duplicity
3
- Version: 2.2.2
4
- Home-page: http://duplicity.us
5
- Platform: any
3
+ Version: 2.2.3
6
4
  License-File: COPYING
7
5
  License-File: AUTHORS.md
@@ -21,7 +21,7 @@
21
21
 
22
22
  import gettext
23
23
 
24
- __version__: str = "2.2.2"
25
- __reldate__: str = "February 03, 2024"
24
+ __version__: str = "2.2.3"
25
+ __reldate__: str = "March 20, 2024"
26
26
 
27
27
  gettext.install("duplicity", names=["ngettext"])
@@ -24,13 +24,14 @@ Provides a common interface to all backends and certain sevices
24
24
  intended to be used by the backends themselves.
25
25
  """
26
26
 
27
+ from datetime import datetime
27
28
  import errno
28
29
  import getpass
29
30
  import os
30
31
  import re
31
- import socket
32
32
  import sys
33
33
  import time
34
+ from typing import Tuple
34
35
  import urllib.error
35
36
  import urllib.parse
36
37
  import urllib.request
@@ -604,6 +605,42 @@ class BackendWrapper(object):
604
605
  else:
605
606
  raise NotImplementedError()
606
607
 
608
+ def validate(self, remote_filename, expected_size, source_path=None) -> Tuple[bool, str]:
609
+ """
610
+ validates a file transferred to the backend by comparing the size.
611
+ source_path is optional to allow the backend do further validation, e.g. by
612
+ calulating a hash or so.
613
+ Returns: return a tuple of (result, reason)
614
+ - result: bool: True if file validation successful otherwise False
615
+ - reason: str: If False a reason why validation failed; True: emtpy str or description
616
+ """
617
+ msg = "Validation fail for unknow reaons."
618
+ if hasattr(self.backend, "_validate"):
619
+ return self.backend._validate(remote_filename, expected_size, source_path=source_path)
620
+ else:
621
+ # as some backends take some time refresh file attributes after upload
622
+ # retry config.num_retries times.
623
+ for attempt in range(0, config.num_retries + 1):
624
+ info = self.query_info([remote_filename])[remote_filename]
625
+ size = info["size"]
626
+ if size is None:
627
+ log.Warn(
628
+ "File size can't be validated, because of missing capabilities of the backend. "
629
+ "Please verify the backup separately."
630
+ )
631
+ return (True, "Backend has no capabilities to check filesize, skip validation.")
632
+ if size == expected_size:
633
+ return (True, "Validation OK, file size matches.")
634
+ msg = _("%s Remote filesize %d for %s does not match local size %d") % (
635
+ datetime.now(),
636
+ size,
637
+ util.escape(remote_filename),
638
+ expected_size,
639
+ )
640
+ log.Notice(f"{msg}, retrying.")
641
+ time.sleep(2**attempt)
642
+ return (False, f"{msg}.")
643
+
607
644
  def pre_process_download(self, remote_filename):
608
645
  """
609
646
  Manages remote access before downloading files
@@ -19,6 +19,16 @@ _delete
19
19
 
20
20
  There are other methods you may optionally implement:
21
21
 
22
+ _validate
23
+ - minimum, compare the file size with the given value
24
+ - has two pos args: remote_filename and size in bytes
25
+ - accept **kwargs, which will held following arguments:
26
+ - source_path as path.Path object pointing to the source file in the local fs.
27
+ - (future arguments, that should be ignored.)
28
+ - must handle retries on its own if e.g. size need some time to be refreshed after upload.
29
+ - return a tuple of (bool, str)
30
+ - bool: True if file validation successful otherwise False
31
+ - str: If False a description why validation failed; True: emtpy str or description
22
32
  _delete_list
23
33
  - Delete list of files
24
34
  - This is used in preference of _delete if defined
@@ -20,6 +20,7 @@
20
20
  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
21
 
22
22
  import glob
23
+ import hashlib
23
24
  import inspect
24
25
  import json
25
26
  import logging
@@ -39,12 +40,14 @@ ERROR_ON = {}
39
40
 
40
41
 
41
42
  class BackendErrors:
42
- FAIL_WITH_EXCEPTION = "DUP_FAIL_WITH_EXCEPTION" # set to substring mached by the filename.
43
- WAIT_FOR_OTHER_VOLUME = (
44
- "DUP_FAIL_WAIT_FOR_VOLUME" # set to json string: ["file_to_delay", "file_wait_for"] (substing match)
45
- )
46
- LAST_BYTE_MISSING = "DUP_FAIL_LAST_BYTE_MISSING" # set to substring mached by the filename.
47
- SKIP_PUT_SILENT = "DUP_FAIL_SKIP_PUT_SILENT" # Don't put file contains string
43
+ # FAIL_WITH_EXCEPTION: set to substring mached by the filename.
44
+ FAIL_WITH_EXCEPTION = "DUP_FAIL_WITH_EXCEPTION"
45
+ # WAIT_FOR_OTHER_VOLUME: set to json string: ["file_to_delay", "file_wait_for"] (substing match)
46
+ WAIT_FOR_OTHER_VOLUME = "DUP_FAIL_WAIT_FOR_VOLUME"
47
+ # LAST_BYTE_MISSING: set to substring mached by the filename.
48
+ LAST_BYTE_MISSING = "DUP_FAIL_LAST_BYTE_MISSING"
49
+ # SKIP_PUT_SILENT: Don't put file if name contains string
50
+ SKIP_PUT_SILENT = "DUP_FAIL_SKIP_PUT_SILENT"
48
51
 
49
52
 
50
53
  class _TestBackend(duplicity.backend.Backend):
@@ -145,6 +148,43 @@ class _TestBackend(duplicity.backend.Backend):
145
148
  def _list(self):
146
149
  return self.remote_pathdir.listdir()
147
150
 
151
+ def _validate(self, remote_filename, size, source_path=None, **kwargs):
152
+ # poc to show that validate can do additional things.
153
+
154
+ self._remove_last_byte(remote_filename)
155
+
156
+ results_str = []
157
+ results_bool = []
158
+ try:
159
+ if source_path:
160
+ target_path = self.remote_pathdir.append(remote_filename)
161
+ target_hash = self.__hash_fileobj(target_path.open())
162
+ source_hash = self.__hash_fileobj(source_path.open())
163
+ if target_hash == source_hash:
164
+ results_str.append(f"file hash {target_hash} matches")
165
+ results_bool.append(True)
166
+ else:
167
+ results_str.append(f"expected hash {source_hash} doesn't match file hash {target_hash}")
168
+ results_bool.append(False)
169
+ if size == self._query(remote_filename)["size"]:
170
+ results_str.append(f"file size {size}")
171
+ results_bool.append(True)
172
+ else:
173
+ results_str.append(
174
+ f'expected size {size} and file size {self._query(remote_filename)["size"]} don\'t match'
175
+ )
176
+ results_bool.append(False)
177
+ except FileNotFoundError as e:
178
+ results_bool.append(False)
179
+ results_str.append(f"FileNotFoundError: {e}")
180
+ except Exception as e:
181
+ log.FatalError(
182
+ _("Unexpected exception while validate %s.") % os.fsdecode(remote_filename),
183
+ log.ErrorCode.backend_verification_failed,
184
+ extra=f"Exception: {e}",
185
+ )
186
+ return (all(results_bool), ", ".join(results_str))
187
+
148
188
  def _delete(self, filename):
149
189
  self._fail_with_exception(filename)
150
190
  self.remote_pathdir.append(filename).delete()
@@ -161,5 +201,33 @@ class _TestBackend(duplicity.backend.Backend):
161
201
  size = target_file.getsize() if target_file.exists() else -1
162
202
  return {"size": size}
163
203
 
204
+ def _query_list(self, filename_list):
205
+ return {x: self._query(x) for x in filename_list}
206
+
207
+ def _retry_cleanup(self):
208
+ pass
209
+
210
+ def _close(self):
211
+ pass
212
+
213
+ def _error_code(self, operation, e): # pylint: disable=unused-argument
214
+ return log.ErrorCode.backend_error
215
+
216
+ def __hash_file(self, filename):
217
+ self.__hash_fileobj(open(filename, "rb"))
218
+
219
+ def __hash_fileobj(self, fileobj):
220
+ h = hashlib.sha1()
221
+
222
+ # loop till the end of the file
223
+ chunk = 0
224
+ while chunk != b"":
225
+ # read only 1024 bytes at a time
226
+ chunk = fileobj.read(1024)
227
+ h.update(chunk)
228
+ fileobj.close()
229
+ # return the hex representation of digest
230
+ return h.hexdigest()
231
+
164
232
 
165
233
  duplicity.backend.register_backend("fortestsonly", _TestBackend)
@@ -217,9 +217,11 @@ class B2Backend(duplicity.backend.Backend):
217
217
  log.Log(f"Query: {self.path}{os.fsdecode(filename)}", log.INFO)
218
218
  file_version_info = self.file_info(quote_plus(self.path + os.fsdecode(filename), "/"))
219
219
  return {
220
- "size": int(file_version_info.size)
221
- if file_version_info is not None and file_version_info.size is not None
222
- else -1
220
+ "size": (
221
+ int(file_version_info.size)
222
+ if file_version_info is not None and file_version_info.size is not None
223
+ else -1
224
+ )
223
225
  }
224
226
 
225
227
  def file_info(self, filename):
@@ -224,6 +224,7 @@ OptionKwargs = dict(
224
224
  ),
225
225
  exclude_older_than=dict(
226
226
  metavar=_("time"),
227
+ type=check_time,
227
228
  action=AddSelectionAction,
228
229
  help="Exclude files older than time",
229
230
  default=dflt(None),
@@ -298,6 +298,19 @@ def process_command_line(cmdline_list):
298
298
  gpg_version = ".".join(map(str, config.gpg_profile.gpg_version))
299
299
  log.Info(_(f"GPG binary is {config.gpg_binary}, version {gpg_version}"))
300
300
 
301
+ # --use-agent is not valid for symmetric encryption. Turn it off and notify user.
302
+ if (
303
+ config.use_agent
304
+ and config.gpg_profile.sign_key is None
305
+ and len(config.gpg_profile.recipients) == 0
306
+ and len(config.gpg_profile.hidden_recipients) == 0
307
+ ):
308
+ config.use_agent = False
309
+ log.Warn(
310
+ "Option --gpg-agent is unsafe with symmetric encryption. Ignoring --gpg-agent.\n"
311
+ "Refer to https://gitlab.com/duplicity/duplicity/-/issues/799 for more information."
312
+ )
313
+
301
314
  # shorten incremental to inc, replace backup with inc
302
315
  if config.action in ("incremental", "backup"):
303
316
  if config.action == "backup":
@@ -767,14 +767,7 @@ class CollectionsStatus(object):
767
767
 
768
768
  # get local filename list
769
769
  local_filename_list = self.archive_dir_path.listdir()
770
- log.Debug(
771
- ngettext(
772
- "%d file exists in cache",
773
- "%d files exist in cache",
774
- len(local_filename_list),
775
- )
776
- % len(local_filename_list)
777
- )
770
+ log.Debug(_("%d file(s) exist in cache") % len(local_filename_list))
778
771
 
779
772
  # check for partial backups
780
773
  partials = []
@@ -314,31 +314,6 @@ def write_multivol(backup_type, tarblock_iter, man_outfp, sig_outfp, backend):
314
314
  end_block -= 1
315
315
  return start_index, start_block, end_index, end_block
316
316
 
317
- def validate_block(orig_size, dest_filename):
318
- info = backend.query_info([dest_filename])[dest_filename]
319
- size = info["size"]
320
- if size is None:
321
- return # error querying file
322
- for attempt in range(1, config.num_retries + 1):
323
- info = backend.query_info([dest_filename])[dest_filename]
324
- size = info["size"]
325
- if size == orig_size:
326
- break
327
- if size is None:
328
- return
329
- log.Notice(
330
- _("%s Remote filesize %d for %s does not match local size %d, retrying.")
331
- % (datetime.now(), size, util.escape(dest_filename), orig_size)
332
- )
333
- time.sleep(2**attempt)
334
- if size != orig_size:
335
- code_extra = f"{util.escape(dest_filename)} {int(orig_size)} {int(size)}"
336
- log.FatalError(
337
- _("File %s was corrupted during upload.") % os.fsdecode(dest_filename),
338
- log.ErrorCode.volume_wrong_size,
339
- code_extra,
340
- )
341
-
342
317
  def put(tdp, dest_filename, vol_num):
343
318
  """
344
319
  Retrieve file size *before* calling backend.put(), which may (at least
@@ -348,7 +323,15 @@ def write_multivol(backup_type, tarblock_iter, man_outfp, sig_outfp, backend):
348
323
  putsize = tdp.getsize()
349
324
  if config.skip_volume != vol_num: # for testing purposes only
350
325
  backend.put(tdp, dest_filename)
351
- validate_block(putsize, dest_filename)
326
+ res, msg = backend.validate(dest_filename, putsize, source_path=tdp)
327
+ if not res:
328
+ code_extra = f"{util.escape(dest_filename)}: {msg}"
329
+ log.FatalError(
330
+ _("File %s was corrupted during upload.") % os.fsdecode(dest_filename),
331
+ log.ErrorCode.backend_verification_failed,
332
+ code_extra,
333
+ )
334
+
352
335
  if tdp.stat:
353
336
  tdp.delete()
354
337
  return putsize
@@ -346,6 +346,7 @@ class ErrorCode(object):
346
346
  backend_no_space = 53
347
347
  backend_command_error = 54
348
348
  backend_code_error = 55
349
+ backend_verification_failed = 56
349
350
 
350
351
  # file selection filter mode errors
351
352
  redundant_filter = 70
@@ -602,7 +602,7 @@ class Path(ROPath):
602
602
 
603
603
  def mkdir(self):
604
604
  """Make directory(s) at specified path"""
605
- log.Info(_("Making directory %s") % self.uc_name)
605
+ log.Debug(_("Making directory %s") % self.uc_name)
606
606
  try:
607
607
  os.makedirs(self.name)
608
608
  except OSError:
@@ -612,7 +612,7 @@ class Path(ROPath):
612
612
 
613
613
  def delete(self):
614
614
  """Remove this file"""
615
- log.Info(_("Deleting %s") % self.uc_name)
615
+ log.Debug(_("Deleting %s") % self.uc_name)
616
616
  if self.isdir():
617
617
  util.ignore_missing(os.rmdir, self.name)
618
618
  else:
@@ -621,7 +621,7 @@ class Path(ROPath):
621
621
 
622
622
  def touch(self):
623
623
  """Open the file, write 0 bytes, close"""
624
- log.Info(_("Touching %s") % self.uc_name)
624
+ log.Debug(_("Touching %s") % self.uc_name)
625
625
  fp = self.open("wb")
626
626
  fp.close()
627
627
 
@@ -629,7 +629,7 @@ class Path(ROPath):
629
629
  """Remove self by recursively deleting files under it"""
630
630
  from duplicity import selection # TODO: avoid circ. dep. issue
631
631
 
632
- log.Info(_("Deleting tree %s") % self.uc_name)
632
+ log.Debug(_("Deleting tree %s") % self.uc_name)
633
633
  itr = IterTreeReducer(PathDeleter, [])
634
634
  for path in selection.Select(self).set_iter():
635
635
  itr(path.index, path)
@@ -1,7 +1,5 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: duplicity
3
- Version: 2.2.2
4
- Home-page: http://duplicity.us
5
- Platform: any
3
+ Version: 2.2.3
6
4
  License-File: COPYING
7
5
  License-File: AUTHORS.md
@@ -280,6 +280,7 @@ testing/regression/issue25.json
280
280
  testing/regression/issue25.sh
281
281
  testing/regression/issue26.sh
282
282
  testing/regression/issue683.sh
283
+ testing/regression/issue725.sh
283
284
  testing/regression/issue73.env
284
285
  testing/regression/issue73.sh
285
286
  testing/regression/issue781.json
@@ -308,11 +309,14 @@ testing/unit/test_statistics.py
308
309
  testing/unit/test_tarfile.py
309
310
  testing/unit/test_tempdir.py
310
311
  testing/unit/test_util.py
312
+ tools/installpyenv
313
+ tools/installpythons
311
314
  tools/installsnap
312
- tools/list_python_files
313
315
  tools/makechangelog
314
316
  tools/makepip
315
317
  tools/makesnap
318
+ tools/pushpip
316
319
  tools/pushsnap
317
320
  tools/release-prep
321
+ tools/testpip
318
322
  tools/testsnap