duplicity 3.0.8.dev3__tar.gz → 3.0.8.dev4__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.8.dev3 → duplicity-3.0.8.dev4}/CHANGELOG.md +24 -2
  2. {duplicity-3.0.8.dev3/duplicity.egg-info → duplicity-3.0.8.dev4}/PKG-INFO +1 -1
  3. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/__init__.py +2 -2
  4. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/_librsyncmodule.c +4 -46
  5. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/_cf_cloudfiles.py +2 -4
  6. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/_cf_pyrax.py +2 -4
  7. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/b2backend.py +2 -1
  8. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/dpbxbackend.py +2 -4
  9. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/gdrivebackend.py +2 -4
  10. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/hubicbackend.py +2 -4
  11. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/idrivedbackend.py +0 -1
  12. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/imapbackend.py +6 -6
  13. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/mediafirebackend.py +2 -4
  14. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/onedrivebackend.py +0 -1
  15. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/pcabackend.py +2 -4
  16. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/pyrax_identity/hubic.py +2 -4
  17. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/rclonebackend.py +4 -2
  18. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/rsyncbackend.py +1 -1
  19. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/s3_boto3_backend.py +3 -2
  20. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/slatebackend.py +2 -4
  21. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/ssh_paramiko_backend.py +2 -4
  22. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/ssh_pexpect_backend.py +2 -4
  23. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/swiftbackend.py +2 -4
  24. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/webdavbackend.py +13 -23
  25. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/cli_data.py +7 -4
  26. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/cli_main.py +7 -21
  27. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/cli_util.py +2 -8
  28. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/config.py +4 -0
  29. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/diffdir.py +1 -0
  30. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/dup_main.py +17 -11
  31. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/dup_time.py +4 -8
  32. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/patchdir.py +1 -0
  33. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/path.py +0 -14
  34. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/selection.py +12 -41
  35. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4/duplicity.egg-info}/PKG-INFO +1 -1
  36. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/man/duplicity.1 +8 -1
  37. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/pyproject.toml +1 -2
  38. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/setup.py +1 -1
  39. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/AUTHORS.md +0 -0
  40. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/COPYING +0 -0
  41. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/README-LOG.md +0 -0
  42. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/README-REPO.md +0 -0
  43. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/README-TESTING.md +0 -0
  44. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/README.md +0 -0
  45. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/__main__.py +0 -0
  46. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/argparse311.py +0 -0
  47. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backend.py +0 -0
  48. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backend_pool.py +0 -0
  49. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/__init__.py +0 -0
  50. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/_testbackend.py +0 -0
  51. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/adbackend.py +0 -0
  52. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/azurebackend.py +0 -0
  53. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/boxbackend.py +0 -0
  54. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/cfbackend.py +0 -0
  55. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/gdocsbackend.py +0 -0
  56. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/giobackend.py +0 -0
  57. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/hsibackend.py +0 -0
  58. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/jottacloudbackend.py +0 -0
  59. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/lftpbackend.py +0 -0
  60. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/localbackend.py +0 -0
  61. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/megabackend.py +0 -0
  62. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/megav2backend.py +0 -0
  63. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/megav3backend.py +0 -0
  64. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/multibackend.py +0 -0
  65. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/ncftpbackend.py +0 -0
  66. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/par2backend.py +0 -0
  67. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/pydrivebackend.py +0 -0
  68. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/pyrax_identity/__init__.py +0 -0
  69. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/sxbackend.py +0 -0
  70. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/tahoebackend.py +0 -0
  71. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/backends/xorrisobackend.py +0 -0
  72. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/cached_ops.py +0 -0
  73. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/dup_collections.py +0 -0
  74. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/dup_tarfile.py +0 -0
  75. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/dup_temp.py +0 -0
  76. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/errors.py +0 -0
  77. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/file_naming.py +0 -0
  78. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/filechunkio.py +0 -0
  79. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/globmatch.py +0 -0
  80. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/gpg.py +0 -0
  81. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/gpginterface.py +0 -0
  82. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/lazy.py +0 -0
  83. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/librsync.py +0 -0
  84. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/log.py +0 -0
  85. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/manifest.py +0 -0
  86. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/progress.py +0 -0
  87. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/robust.py +0 -0
  88. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/statistics.py +0 -0
  89. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/tempdir.py +0 -0
  90. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity/util.py +0 -0
  91. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity.egg-info/SOURCES.txt +0 -0
  92. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity.egg-info/dependency_links.txt +0 -0
  93. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity.egg-info/entry_points.txt +0 -0
  94. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity.egg-info/requires.txt +0 -0
  95. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/duplicity.egg-info/top_level.txt +0 -0
  96. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/af_ZA/duplicity.mo +0 -0
  97. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ar_SA/duplicity.mo +0 -0
  98. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ca_ES/duplicity.mo +0 -0
  99. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/cs_CZ/duplicity.mo +0 -0
  100. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/da_DK/duplicity.mo +0 -0
  101. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/de_AT/duplicity.mo +0 -0
  102. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/de_DE/duplicity.mo +0 -0
  103. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/el_GR/duplicity.mo +0 -0
  104. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/en_AU/duplicity.mo +0 -0
  105. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/en_GB/duplicity.mo +0 -0
  106. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/en_PR/duplicity.mo +0 -0
  107. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/en_US/duplicity.mo +0 -0
  108. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/es_EM/duplicity.mo +0 -0
  109. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/es_ES/duplicity.mo +0 -0
  110. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/es_MX/duplicity.mo +0 -0
  111. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/es_PR/duplicity.mo +0 -0
  112. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/es_US/duplicity.mo +0 -0
  113. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/fi_FI/duplicity.mo +0 -0
  114. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/fr_FR/duplicity.mo +0 -0
  115. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/he_IL/duplicity.mo +0 -0
  116. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/hu_HU/duplicity.mo +0 -0
  117. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/it_IT/duplicity.mo +0 -0
  118. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ja_JP/duplicity.mo +0 -0
  119. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ko_KR/duplicity.mo +0 -0
  120. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/nl_BE/duplicity.mo +0 -0
  121. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/nl_NL/duplicity.mo +0 -0
  122. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/nl_SR/duplicity.mo +0 -0
  123. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/no_NO/duplicity.mo +0 -0
  124. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/pl_PL/duplicity.mo +0 -0
  125. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/pt_BR/duplicity.mo +0 -0
  126. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/pt_PT/duplicity.mo +0 -0
  127. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ro_RO/duplicity.mo +0 -0
  128. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ru_BY/duplicity.mo +0 -0
  129. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ru_MD/duplicity.mo +0 -0
  130. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ru_RU/duplicity.mo +0 -0
  131. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/ru_UA/duplicity.mo +0 -0
  132. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/sr_SP/duplicity.mo +0 -0
  133. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/sv_SE/duplicity.mo +0 -0
  134. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/tr_TR/duplicity.mo +0 -0
  135. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/uk_UA/duplicity.mo +0 -0
  136. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/vi_VN/duplicity.mo +0 -0
  137. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/zh_CN/duplicity.mo +0 -0
  138. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/zh_HK/duplicity.mo +0 -0
  139. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/zh_MO/duplicity.mo +0 -0
  140. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/zh_SG/duplicity.mo +0 -0
  141. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/po/zh_TW/duplicity.mo +0 -0
  142. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/requirements.txt +0 -0
  143. {duplicity-3.0.8.dev3 → duplicity-3.0.8.dev4}/setup.cfg +0 -0
@@ -1,7 +1,29 @@
1
1
 
2
- (Unreleased) / 2025-12-31
3
- =========================
2
+ rel.3.0.8.dev4 / 2026-03-31
3
+ ===========================
4
+
5
+ * c17b03bc:fix: try/except block masking real S3UploadFailedError
6
+ * db127ce5:new: Add --full-if-n-inc flag for count-based full backup.
7
+ * dc977419:fix: Simplify swig module definition.
8
+ * 0de30e69:chg: Adjust to newer sphinx.
9
+ * c1883117:fix: Add templates for bug and feature requests.
10
+ * 39150d34:fix: delete unused Path.patch_with_attribs.
11
+ * 1a7c6790:fix: Remove support for Python 2.
12
+ * 564dd593:fix: fix flag name --gpg-agent -> --use-agent.
13
+ * 866680d0:chg: Multiple changes for 2026 are planned
14
+ * 29e00b55:fix: Fix incompatibility with Python 3.14.
15
+ * 50faabfc:chg: Partial fix for #924.
16
+ * 00ae7687:fix: webdav, always send warmup OPTIONS request, prevent loop
17
+ * 37841a0e:fix: Handle local version identifiers in b2backend
18
+
19
+ rel.3.0.8.dev3 / 2026-01-14
20
+ ===========================
21
+
22
+ * 3475dd05:fix: Unnecessary passphrase prompt with --encrypt-key --no-check-remote.
23
+ * b8508f6d:chg: Remove slow marker from tests.
4
24
 
25
+ rel.3.0.8.dev2 / 2026-01-14
26
+ ===========================
5
27
 
6
28
 
7
29
  rel.3.0.7 / 2025-12-31
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duplicity
3
- Version: 3.0.8.dev3
3
+ Version: 3.0.8.dev4
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.8.dev3"
25
- __reldate__: str = "January 14, 2026"
24
+ __version__: str = "3.0.8.dev4"
25
+ __reldate__: str = "March 31, 2026"
26
26
 
27
27
  gettext.install("duplicity", names=["ngettext"])
@@ -90,11 +90,7 @@ _librsync_sigmaker_cycle(_librsync_SigMakerObject *self, PyObject *args)
90
90
  rs_buffers_t buf;
91
91
  rs_result result;
92
92
 
93
- #if PY_MAJOR_VERSION >= 3
94
93
  if (!PyArg_ParseTuple(args, "y#:cycle", &inbuf, &inbuf_length))
95
- #else
96
- if (!PyArg_ParseTuple(args, "s#:cycle", &inbuf, &inbuf_length))
97
- #endif
98
94
  return NULL;
99
95
 
100
96
  buf.next_in = inbuf;
@@ -110,11 +106,7 @@ _librsync_sigmaker_cycle(_librsync_SigMakerObject *self, PyObject *args)
110
106
  return NULL;
111
107
  }
112
108
 
113
- #if PY_MAJOR_VERSION >= 3
114
109
  return Py_BuildValue("(ily#)", (result == RS_DONE),
115
- #else
116
- return Py_BuildValue("(ils#)", (result == RS_DONE),
117
- #endif
118
110
  (long)inbuf_length - (long)buf.avail_in,
119
111
  outbuf, RS_JOB_BLOCKSIZE - (long)buf.avail_out);
120
112
  }
@@ -178,11 +170,7 @@ _librsync_new_deltamaker(PyObject* self, PyObject* args)
178
170
  rs_buffers_t buf;
179
171
  rs_result result;
180
172
 
181
- #if PY_MAJOR_VERSION >= 3
182
173
  if (!PyArg_ParseTuple(args,"y#:new_deltamaker", &sig_string, &sig_length))
183
- #else
184
- if (!PyArg_ParseTuple(args,"s#:new_deltamaker", &sig_string, &sig_length))
185
- #endif
186
174
  return NULL;
187
175
 
188
176
  dm = PyObject_New(_librsync_DeltaMakerObject, &_librsync_DeltaMakerType);
@@ -237,11 +225,7 @@ _librsync_deltamaker_cycle(_librsync_DeltaMakerObject *self, PyObject *args)
237
225
  rs_buffers_t buf;
238
226
  rs_result result;
239
227
 
240
- #if PY_MAJOR_VERSION >= 3
241
228
  if (!PyArg_ParseTuple(args, "y#:cycle", &inbuf, &inbuf_length))
242
- #else
243
- if (!PyArg_ParseTuple(args, "s#:cycle", &inbuf, &inbuf_length))
244
- #endif
245
229
  return NULL;
246
230
 
247
231
  buf.next_in = inbuf;
@@ -256,11 +240,7 @@ _librsync_deltamaker_cycle(_librsync_DeltaMakerObject *self, PyObject *args)
256
240
  return NULL;
257
241
  }
258
242
 
259
- #if PY_MAJOR_VERSION >= 3
260
243
  return Py_BuildValue("(ily#)", (result == RS_DONE),
261
- #else
262
- return Py_BuildValue("(ils#)", (result == RS_DONE),
263
- #endif
264
244
  (long)inbuf_length - (long)buf.avail_in,
265
245
  outbuf, RS_JOB_BLOCKSIZE - (long)buf.avail_out);
266
246
  }
@@ -374,11 +354,7 @@ _librsync_patchmaker_cycle(_librsync_PatchMakerObject *self, PyObject *args)
374
354
  rs_buffers_t buf;
375
355
  rs_result result;
376
356
 
377
- #if PY_MAJOR_VERSION >= 3
378
357
  if (!PyArg_ParseTuple(args, "y#:cycle", &inbuf, &inbuf_length))
379
- #else
380
- if (!PyArg_ParseTuple(args, "s#:cycle", &inbuf, &inbuf_length))
381
- #endif
382
358
  return NULL;
383
359
 
384
360
  buf.next_in = inbuf;
@@ -393,11 +369,7 @@ _librsync_patchmaker_cycle(_librsync_PatchMakerObject *self, PyObject *args)
393
369
  return NULL;
394
370
  }
395
371
 
396
- #if PY_MAJOR_VERSION >= 3
397
372
  return Py_BuildValue("(ily#)", (result == RS_DONE),
398
- #else
399
- return Py_BuildValue("(ils#)", (result == RS_DONE),
400
- #endif
401
373
  (long)inbuf_length - (long)buf.avail_in,
402
374
  outbuf, RS_JOB_BLOCKSIZE - (long)buf.avail_out);
403
375
  }
@@ -441,16 +413,6 @@ static PyTypeObject _librsync_PatchMakerType = {
441
413
 
442
414
  /* --------------- _librsync module definition */
443
415
 
444
- #if PY_MAJOR_VERSION >= 3
445
- #define MOD_DEF(ob, name, doc, methods) \
446
- static struct PyModuleDef moduledef = { \
447
- PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
448
- ob = PyModule_Create(&moduledef);
449
- #else
450
- #define MOD_DEF(ob, name, doc, methods) \
451
- ob = Py_InitModule3(name, methods, doc);
452
- #endif
453
-
454
416
  static PyMethodDef _librsyncMethods[] = {
455
417
  {"new_sigmaker", _librsync_new_sigmaker, METH_VARARGS,
456
418
  "Return a sigmaker object, for finding the signature of an object"},
@@ -461,6 +423,9 @@ static PyMethodDef _librsyncMethods[] = {
461
423
  {NULL, NULL, 0, NULL}
462
424
  };
463
425
 
426
+ static struct PyModuleDef moduledef = {
427
+ PyModuleDef_HEAD_INIT, "_librsync", "", -1, _librsyncMethods, };
428
+
464
429
  static PyObject *
465
430
  moduleinit(void)
466
431
  {
@@ -474,7 +439,7 @@ moduleinit(void)
474
439
  Py_TYPE(&_librsync_DeltaMakerType) = &PyType_Type;
475
440
  #endif
476
441
 
477
- MOD_DEF(m, "_librsync", "", _librsyncMethods)
442
+ m = PyModule_Create(&moduledef);
478
443
  if (m == NULL)
479
444
  return NULL;
480
445
 
@@ -491,14 +456,7 @@ moduleinit(void)
491
456
  return m;
492
457
  }
493
458
 
494
- #if PY_MAJOR_VERSION < 3
495
- void init_librsync(void)
496
- {
497
- moduleinit();
498
- }
499
- #else
500
459
  PyObject *PyInit__librsync(void)
501
460
  {
502
461
  return moduleinit();
503
462
  }
504
- #endif
@@ -39,10 +39,8 @@ class CloudFilesBackend(duplicity.backend.Backend):
39
39
  from cloudfiles import consts
40
40
  from cloudfiles.errors import NoSuchObject
41
41
  except ImportError as e:
42
- raise BackendException(
43
- f"""Cloudfiles backend requires the cloudfiles library available from Rackspace.
44
- Exception: {str(e)}"""
45
- )
42
+ raise BackendException(f"""Cloudfiles backend requires the cloudfiles library available from Rackspace.
43
+ Exception: {str(e)}""")
46
44
 
47
45
  self.resp_exc = ResponseError
48
46
  conn_kwargs = {}
@@ -37,10 +37,8 @@ class PyraxBackend(duplicity.backend.Backend):
37
37
  try:
38
38
  import pyrax
39
39
  except ImportError as e:
40
- raise BackendException(
41
- f"""Pyrax backend requires the pyrax library available from Rackspace.
42
- Exception: {str(e)}"""
43
- )
40
+ raise BackendException(f"""Pyrax backend requires the pyrax library available from Rackspace.
41
+ Exception: {str(e)}""")
44
42
 
45
43
  # Inform Pyrax that we're talking to Rackspace
46
44
  # per Jesus Monzon (gsusmonzon)
@@ -73,7 +73,8 @@ class B2Backend(duplicity.backend.Backend):
73
73
  try: # figure out what version of b2sdk we have
74
74
  from b2sdk import __version__ as VERSION # pylint: disable=import-error
75
75
 
76
- v_split = VERSION.split(".")
76
+ v_public = VERSION.partition("+")[0] # ignore local version identifier if present
77
+ v_split = v_public.split(".")
77
78
  self.v_num = [int(x) for x in v_split]
78
79
  except Exception as e:
79
80
  self.v_num = [0, 0, 0]
@@ -120,11 +120,9 @@ class DPBXBackend(duplicity.backend.Backend):
120
120
  )
121
121
  from dropbox.oauth import DropboxOAuth2FlowNoRedirect
122
122
  except ImportError as e:
123
- raise BackendException(
124
- f"""This backend requires the dropbox package version 6.9.0
123
+ raise BackendException(f"""This backend requires the dropbox package version 6.9.0
125
124
  To install use "sudo pip install dropbox==6.9.0"
126
- Exception: {str(e)}"""
127
- )
125
+ Exception: {str(e)}""")
128
126
 
129
127
  self.api_account = None
130
128
  self.api_client = None
@@ -38,11 +38,9 @@ class GDriveBackend(duplicity.backend.Backend):
38
38
  from googleapiclient.discovery import build
39
39
  from google.oauth2.service_account import Credentials
40
40
  except ImportError as e:
41
- raise BackendException(
42
- f"""GDrive backend requires Google API client installation.
41
+ raise BackendException(f"""GDrive backend requires Google API client installation.
43
42
  Please read the manpage for setup details.
44
- Exception: {str(e)}"""
45
- )
43
+ Exception: {str(e)}""")
46
44
 
47
45
  # Note Google has 2 drive methods, `Shared(previously Team) Drives` and `My Drive`
48
46
  # both can be shared but require different addressing
@@ -38,10 +38,8 @@ class HubicBackend(PyraxBackend):
38
38
  try:
39
39
  import pyrax
40
40
  except ImportError as e:
41
- raise BackendException(
42
- f"""Hubic backend requires the pyrax library available from Rackspace.
43
- Exception: {str(e)}"""
44
- )
41
+ raise BackendException(f"""Hubic backend requires the pyrax library available from Rackspace.
42
+ Exception: {str(e)}""")
45
43
 
46
44
  # Inform Pyrax that we're talking to Hubic
47
45
  pyrax.set_setting("identity_type", "duplicity.backends.pyrax_identity.hubic.HubicIdentity")
@@ -31,7 +31,6 @@ from duplicity import config
31
31
  from duplicity import log
32
32
  from duplicity.errors import BackendException
33
33
 
34
-
35
34
  #
36
35
  # This backend works with the IDrive "dedup implementation". V0.1
37
36
  # (for all new and recent accounts)
@@ -168,7 +168,7 @@ class ImapBackend(duplicity.backend.Backend):
168
168
  while allowedTimeout > 0:
169
169
  try:
170
170
  self.conn.select(config.imap_mailbox)
171
- (result, flist) = self.conn.search(None, "Subject", remote_filename)
171
+ result, flist = self.conn.search(None, "Subject", remote_filename)
172
172
  if result != "OK":
173
173
  raise Exception(flist[0])
174
174
 
@@ -176,7 +176,7 @@ class ImapBackend(duplicity.backend.Backend):
176
176
  if flist[0] == "":
177
177
  raise Exception("no mail with subject %s")
178
178
 
179
- (result, flist) = self.conn.fetch(flist[0], "(RFC822)")
179
+ result, flist = self.conn.fetch(flist[0], "(RFC822)")
180
180
 
181
181
  if result != "OK":
182
182
  raise Exception(flist[0])
@@ -211,7 +211,7 @@ class ImapBackend(duplicity.backend.Backend):
211
211
 
212
212
  def _list(self):
213
213
  ret = []
214
- (result, flist) = self.conn.select(config.imap_mailbox)
214
+ result, flist = self.conn.select(config.imap_mailbox)
215
215
  if result != "OK":
216
216
  raise BackendException(flist[0])
217
217
 
@@ -219,14 +219,14 @@ class ImapBackend(duplicity.backend.Backend):
219
219
  # address
220
220
 
221
221
  # Search returns an error if you haven't selected an IMAP folder.
222
- (result, flist) = self.conn.search(None, "FROM", self.remote_dir)
222
+ result, flist = self.conn.search(None, "FROM", self.remote_dir)
223
223
  if result != "OK":
224
224
  raise Exception(flist[0])
225
225
  if flist[0] == b"":
226
226
  return ret
227
227
  nums = flist[0].strip().split(b" ")
228
228
  set = b"%s:%s" % (nums[0], nums[-1]) # pylint: disable=redefined-builtin
229
- (result, flist) = self.conn.fetch(set, "(BODY[HEADER])")
229
+ result, flist = self.conn.fetch(set, "(BODY[HEADER])")
230
230
  if result != "OK":
231
231
  raise Exception(flist[0])
232
232
 
@@ -247,7 +247,7 @@ class ImapBackend(duplicity.backend.Backend):
247
247
  return ret
248
248
 
249
249
  def imapf(self, fun, *args):
250
- (ret, flist) = fun(*args)
250
+ ret, flist = fun(*args)
251
251
  if ret != "OK":
252
252
  raise Exception(flist[0])
253
253
  return flist
@@ -38,10 +38,8 @@ class MediafireBackend(duplicity.backend.Backend):
38
38
  try:
39
39
  import mediafire.client
40
40
  except ImportError as e:
41
- raise BackendException(
42
- f"""Mediafire backend requires the mediafire library.
43
- Exception: {str(e)}"""
44
- )
41
+ raise BackendException(f"""Mediafire backend requires the mediafire library.
42
+ Exception: {str(e)}""")
45
43
 
46
44
  duplicity.backend.Backend.__init__(self, parsed_url)
47
45
 
@@ -34,7 +34,6 @@ from duplicity import log
34
34
  from duplicity import util
35
35
  from duplicity.errors import BackendException
36
36
 
37
-
38
37
  # For documentation on the API, see
39
38
  # The previous Live SDK API required the use of opaque folder IDs to navigate paths, but the Microsoft Graph
40
39
  # API allows the use of parent/child/grandchild pathnames.
@@ -39,10 +39,8 @@ class PCABackend(duplicity.backend.Backend):
39
39
  from swiftclient import Connection
40
40
  from swiftclient import ClientException
41
41
  except ImportError as e:
42
- raise BackendException(
43
- f"""PCA backend requires the python-swiftclient library.
44
- Exception: {str(e)}"""
45
- )
42
+ raise BackendException(f"""PCA backend requires the python-swiftclient library.
43
+ Exception: {str(e)}""")
46
44
 
47
45
  self.resp_exc = ClientException
48
46
  self.conn_cls = Connection
@@ -24,10 +24,8 @@ try:
24
24
  )
25
25
  import pyrax.exceptions as exc
26
26
  except ImportError as e:
27
- raise BackendException(
28
- f"""Hubic backend requires the pyrax library available from Rackspace.
29
- Exception: {str(e)}"""
30
- )
27
+ raise BackendException(f"""Hubic backend requires the pyrax library available from Rackspace.
28
+ Exception: {str(e)}""")
31
29
 
32
30
  OAUTH_ENDPOINT = "https://api.hubic.com/oauth/"
33
31
  API_ENDPOINT = "https://api.hubic.com/1.0/"
@@ -95,8 +95,10 @@ class RcloneBackend(duplicity.backend.Backend):
95
95
  size = -1
96
96
  elif rc == 0:
97
97
  size = int(o)
98
- finally:
99
- return {"size": size}
98
+ except Exception as e:
99
+ log.Debug(f"Failed to query file size for {remote_filename}: {e}")
100
+ return {"size": None}
101
+ return {"size": size}
100
102
 
101
103
  def _delete(self, remote_filename):
102
104
  remote_filename = os.fsdecode(remote_filename)
@@ -60,7 +60,7 @@ class RsyncBackend(duplicity.backend.Backend):
60
60
  del os.environ["RSYNC_RSH"]
61
61
  if self.over_rsyncd():
62
62
  # its a module path
63
- (path, port) = self.get_rsync_path()
63
+ path, port = self.get_rsync_path()
64
64
  self.url_string = f"{host}::{path.lstrip('/:')}"
65
65
  if port:
66
66
  port = f" --port={port}"
@@ -208,10 +208,11 @@ class S3Boto3Backend(duplicity.backend.Backend):
208
208
  )
209
209
  except S3UploadFailedError as e:
210
210
  if boto3.__version__ > "1.36.0" or botocore.__version__ > "1.36.0":
211
- log.FatalError(
211
+ raise FatalBackendException(
212
212
  f"Failed to upload file {remote_filename}, got S3UploadFailedError\n"
213
213
  f"See https://gitlab.com/duplicity/duplicity/-/issues/870 for details.\n"
214
- f"Quick fix is: [sudo] pip3 install boto3<1.36.0 botocore<1.36.0"
214
+ f"Quick fix is: [sudo] pip3 install boto3<1.36.0 botocore<1.36.0\n"
215
+ f"API-Error: {e}"
215
216
  )
216
217
  else:
217
218
  raise e
@@ -40,10 +40,8 @@ class SlateBackend(duplicity.backend.Backend):
40
40
  duplicity.backend.Backend.__init__(self, parsed_url)
41
41
  log.Debug("loading slate backend...")
42
42
  if "SLATE_API_KEY" not in os.environ.keys():
43
- raise BackendException(
44
- """You must set an environment variable SLATE_API_KEY
45
- as the value of your slate API key"""
46
- )
43
+ raise BackendException("""You must set an environment variable SLATE_API_KEY
44
+ as the value of your slate API key""")
47
45
  else:
48
46
  self.key = os.environ["SLATE_API_KEY"]
49
47
 
@@ -104,13 +104,11 @@ class SSHParamikoBackend(duplicity.backend.Backend):
104
104
  if hasattr(key, "fingerprint"):
105
105
  fingerprints += f"\n {key.fingerprint}"
106
106
  keyname = key.get_name().upper()
107
- question = dedent(
108
- """
107
+ question = dedent("""
109
108
  The authenticity of host '{hostname}' can't be established.
110
109
  {keyname} fingerprint is
111
110
  {fingerprints}.
112
- Are you sure you want to continue connecting (yes/no)? """
113
- ).format(**locals())
111
+ Are you sure you want to continue connecting (yes/no)? """).format(**locals())
114
112
  while True:
115
113
  sys.stdout.write(question)
116
114
  choice = input().lower()
@@ -49,14 +49,12 @@ class SSHPExpectBackend(duplicity.backend.Backend):
49
49
  raise
50
50
 
51
51
  if pexpect.__version__ < "4.5.0":
52
- log.FatalError(
53
- f"""
52
+ log.FatalError(f"""
54
53
  The version of pexpect, '{pexexpect.__version__}`, is too old. We need version 4.5.0 or above to run.
55
54
  See https://gitlab.com/duplicity/duplicity/-/issues/125 for the gory details.
56
55
 
57
56
  Use "python3 -m pip install pexpect" to install the latest version.
58
- """
59
- )
57
+ """)
60
58
 
61
59
  self.retry_delay = 10
62
60
 
@@ -43,10 +43,8 @@ class SwiftBackend(duplicity.backend.Backend):
43
43
  from swiftclient import Connection
44
44
  from swiftclient import ClientException
45
45
  except ImportError as e:
46
- raise BackendException(
47
- f"""Swift backend requires the python-swiftclient library.
48
- Exception: {str(e)}"""
49
- )
46
+ raise BackendException(f"""Swift backend requires the python-swiftclient library.
47
+ Exception: {str(e)}""")
50
48
 
51
49
  self.resp_exc = ClientException
52
50
  conn_kwargs = {}
@@ -191,20 +191,18 @@ class WebDAVBackend(duplicity.backend.Backend):
191
191
  else:
192
192
  raise FatalBackendException(_("WebDAV Unknown URI scheme: %s") % self.parsed_url.scheme)
193
193
 
194
- if self.username or self.password:
195
- # Workaround cpython http.client issue
196
- # https://github.com/python/cpython/issues/70107
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)
201
- response.close()
194
+ # Workaround cpython http.client issue
195
+ # https://github.com/python/cpython/issues/70107
196
+ # PUT may not return a proper error when ran as first request but throw SSL-EOF-Error or hang
197
+ # as a workaround we run an OPTIONS request to make sure that PUT is never the first request
198
+ response = self.request("OPTIONS", self.directory, None, connect=False)
199
+ response.close()
202
200
 
203
201
  def _close(self):
204
202
  if self.conn:
205
203
  self.conn.close()
206
204
 
207
- def request(self, method, path, data=None, redirected=0):
205
+ def request(self, method, path, data=None, redirected=0, connect=True):
208
206
  """
209
207
  Wraps the connection.request method to retry once if authentication is
210
208
  required
@@ -221,7 +219,8 @@ class WebDAVBackend(duplicity.backend.Backend):
221
219
  return headers_copy
222
220
 
223
221
  self._close() # or we get previous request's data or exception
224
- self.connect()
222
+ if connect:
223
+ self.connect()
225
224
 
226
225
  quoted_path = urllib.parse.quote(path, "/:~")
227
226
 
@@ -275,21 +274,12 @@ class WebDAVBackend(duplicity.backend.Backend):
275
274
  try:
276
275
  return self.get_kerberos_authorization()
277
276
  except ImportError:
278
- log.Warn(
279
- _(
280
- "python-kerberos needed to use kerberos \
281
- authorization, falling back to basic auth."
282
- )
283
- )
277
+ log.Warn(_("python-kerberos needed to use kerberos \
278
+ authorization, falling back to basic auth."))
284
279
  return self.get_basic_authorization()
285
280
  except Exception as e:
286
- log.Warn(
287
- _(
288
- "Kerberos authorization failed: %s.\
289
- Falling back to basic auth."
290
- )
291
- % e
292
- )
281
+ log.Warn(_("Kerberos authorization failed: %s.\
282
+ Falling back to basic auth.") % e)
293
283
  return self.get_basic_authorization()
294
284
  elif token.lower() == "basic":
295
285
  return self.get_basic_authorization()
@@ -354,6 +354,12 @@ OptionKwargs = dict(
354
354
  help="Perform full backup if last full is older than 'time'",
355
355
  default=dflt(config.full_if_older_than),
356
356
  ),
357
+ full_if_n_inc=dict(
358
+ metavar=_("number"),
359
+ type=int,
360
+ help="Perform full backup if latest chain has N or more incremental backups",
361
+ default=dflt(config.full_if_n_inc),
362
+ ),
357
363
  gpg_binary=dict(
358
364
  metavar=_("path"),
359
365
  type=check_file,
@@ -996,9 +1002,7 @@ trans = {
996
1002
  "remote": _("remote"),
997
1003
  }
998
1004
 
999
- help_url_formats = (
1000
- _("Backends and their URL formats:")
1001
- + f"""
1005
+ help_url_formats = _("Backends and their URL formats:") + f"""
1002
1006
  azure://{trans['container_name']}
1003
1007
  b2://{trans['account_id']}[:{trans['application_key']}]@{trans['bucket_name']}/[{trans['some_dir']}/]
1004
1008
  boto3+s3://{trans['bucket_name']}[/{trans['prefix']}]
@@ -1033,4 +1037,3 @@ help_url_formats = (
1033
1037
  webdav://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
1034
1038
  webdavs://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
1035
1039
  """
1036
- )
@@ -173,37 +173,23 @@ def parse_cmdline_options(arglist):
173
173
  for opt in remainder:
174
174
  if opt.startswith("-"):
175
175
  if opt in changed_options:
176
- command_line_error(
177
- dedent(
178
- f"""\
176
+ command_line_error(dedent(f"""\
179
177
  Option '{opt} was changed in 2.0.0.
180
178
  --file-to-restore to --path-to-restore
181
179
  --do-not-restore-ownership to --no-restore-ownership
182
- """
183
- )
184
- )
180
+ """))
185
181
  elif opt in removed_options:
186
182
  removed_commands_string = "\n".join(f" {c}" for c in sorted(removed_options))
187
- command_line_error(
188
- dedent(
189
- f"""\
183
+ command_line_error(dedent(f"""\
190
184
  Option '{opt}' was removed in 2.0.0.
191
185
  The following options were deprecated and removed in 2.0.0
192
- """
193
- )
194
- + f"{removed_commands_string}"
195
- )
186
+ """) + f"{removed_commands_string}")
196
187
  elif opt in removed_backup_options and args.action in ("backup", "full", "incremental"):
197
188
  removed_commands_string = "\n".join(f" {c}" for c in sorted(removed_backup_options))
198
- command_line_error(
199
- dedent(
200
- f"""\
189
+ command_line_error(dedent(f"""\
201
190
  Option '{opt}' was removed for backup actions in 2.0.0.
202
191
  The following options were deprecated and removed in 2.0.0
203
- """
204
- )
205
- + f"{removed_commands_string}"
206
- )
192
+ """) + f"{removed_commands_string}")
207
193
 
208
194
  # check for proper action
209
195
  if remainder and remainder[0] in all_commands:
@@ -280,7 +266,7 @@ def process_command_line(cmdline_list):
280
266
  and len(config.gpg_profile.hidden_recipients) == 0
281
267
  ):
282
268
  log.Warn(
283
- "Option --gpg-agent is unsafe with symmetric encryption.\n"
269
+ "Option --use-agent is unsafe with symmetric encryption.\n"
284
270
  "Refer to https://gitlab.com/duplicity/duplicity/-/issues/799 for more information."
285
271
  )
286
272