duplicity 3.0.8.dev2__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.8.dev2/duplicity.egg-info → duplicity-3.0.8.dev3}/PKG-INFO +1 -1
  2. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/__init__.py +1 -1
  3. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/dup_main.py +30 -29
  4. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/util.py +0 -89
  5. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3/duplicity.egg-info}/PKG-INFO +1 -1
  6. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/man/duplicity.1 +1 -1
  7. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/pyproject.toml +1 -4
  8. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/setup.py +1 -1
  9. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/AUTHORS.md +0 -0
  10. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/CHANGELOG.md +0 -0
  11. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/COPYING +0 -0
  12. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/README-LOG.md +0 -0
  13. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/README-REPO.md +0 -0
  14. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/README-TESTING.md +0 -0
  15. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/README.md +0 -0
  16. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/__main__.py +0 -0
  17. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/_librsyncmodule.c +0 -0
  18. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/argparse311.py +0 -0
  19. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backend.py +0 -0
  20. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backend_pool.py +0 -0
  21. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/__init__.py +0 -0
  22. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/_cf_cloudfiles.py +0 -0
  23. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/_cf_pyrax.py +0 -0
  24. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/_testbackend.py +0 -0
  25. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/adbackend.py +0 -0
  26. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/azurebackend.py +0 -0
  27. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/b2backend.py +0 -0
  28. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/boxbackend.py +0 -0
  29. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/cfbackend.py +0 -0
  30. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/dpbxbackend.py +0 -0
  31. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/gdocsbackend.py +0 -0
  32. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/gdrivebackend.py +0 -0
  33. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/giobackend.py +0 -0
  34. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/hsibackend.py +0 -0
  35. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/hubicbackend.py +0 -0
  36. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/idrivedbackend.py +0 -0
  37. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/imapbackend.py +0 -0
  38. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/jottacloudbackend.py +0 -0
  39. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/lftpbackend.py +0 -0
  40. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/localbackend.py +0 -0
  41. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/mediafirebackend.py +0 -0
  42. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/megabackend.py +0 -0
  43. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/megav2backend.py +0 -0
  44. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/megav3backend.py +0 -0
  45. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/multibackend.py +0 -0
  46. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/ncftpbackend.py +0 -0
  47. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/onedrivebackend.py +0 -0
  48. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/par2backend.py +0 -0
  49. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/pcabackend.py +0 -0
  50. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/pydrivebackend.py +0 -0
  51. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/pyrax_identity/__init__.py +0 -0
  52. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/pyrax_identity/hubic.py +0 -0
  53. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/rclonebackend.py +0 -0
  54. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/rsyncbackend.py +0 -0
  55. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/s3_boto3_backend.py +0 -0
  56. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/slatebackend.py +0 -0
  57. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/ssh_paramiko_backend.py +0 -0
  58. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/ssh_pexpect_backend.py +0 -0
  59. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/swiftbackend.py +0 -0
  60. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/sxbackend.py +0 -0
  61. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/tahoebackend.py +0 -0
  62. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/webdavbackend.py +0 -0
  63. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/backends/xorrisobackend.py +0 -0
  64. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/cached_ops.py +0 -0
  65. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/cli_data.py +0 -0
  66. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/cli_main.py +0 -0
  67. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/cli_util.py +0 -0
  68. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/config.py +0 -0
  69. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/diffdir.py +0 -0
  70. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/dup_collections.py +0 -0
  71. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/dup_tarfile.py +0 -0
  72. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/dup_temp.py +0 -0
  73. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/dup_time.py +0 -0
  74. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/errors.py +0 -0
  75. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/file_naming.py +0 -0
  76. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/filechunkio.py +0 -0
  77. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/globmatch.py +0 -0
  78. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/gpg.py +0 -0
  79. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/gpginterface.py +0 -0
  80. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/lazy.py +0 -0
  81. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/librsync.py +0 -0
  82. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/log.py +0 -0
  83. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/manifest.py +0 -0
  84. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/patchdir.py +0 -0
  85. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/path.py +0 -0
  86. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/progress.py +0 -0
  87. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/robust.py +0 -0
  88. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/selection.py +0 -0
  89. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/statistics.py +0 -0
  90. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity/tempdir.py +0 -0
  91. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity.egg-info/SOURCES.txt +0 -0
  92. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity.egg-info/dependency_links.txt +0 -0
  93. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity.egg-info/entry_points.txt +0 -0
  94. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity.egg-info/requires.txt +0 -0
  95. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/duplicity.egg-info/top_level.txt +0 -0
  96. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/af_ZA/duplicity.mo +0 -0
  97. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ar_SA/duplicity.mo +0 -0
  98. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ca_ES/duplicity.mo +0 -0
  99. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/cs_CZ/duplicity.mo +0 -0
  100. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/da_DK/duplicity.mo +0 -0
  101. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/de_AT/duplicity.mo +0 -0
  102. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/de_DE/duplicity.mo +0 -0
  103. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/el_GR/duplicity.mo +0 -0
  104. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/en_AU/duplicity.mo +0 -0
  105. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/en_GB/duplicity.mo +0 -0
  106. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/en_PR/duplicity.mo +0 -0
  107. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/en_US/duplicity.mo +0 -0
  108. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/es_EM/duplicity.mo +0 -0
  109. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/es_ES/duplicity.mo +0 -0
  110. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/es_MX/duplicity.mo +0 -0
  111. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/es_PR/duplicity.mo +0 -0
  112. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/es_US/duplicity.mo +0 -0
  113. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/fi_FI/duplicity.mo +0 -0
  114. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/fr_FR/duplicity.mo +0 -0
  115. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/he_IL/duplicity.mo +0 -0
  116. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/hu_HU/duplicity.mo +0 -0
  117. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/it_IT/duplicity.mo +0 -0
  118. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ja_JP/duplicity.mo +0 -0
  119. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ko_KR/duplicity.mo +0 -0
  120. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/nl_BE/duplicity.mo +0 -0
  121. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/nl_NL/duplicity.mo +0 -0
  122. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/nl_SR/duplicity.mo +0 -0
  123. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/no_NO/duplicity.mo +0 -0
  124. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/pl_PL/duplicity.mo +0 -0
  125. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/pt_BR/duplicity.mo +0 -0
  126. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/pt_PT/duplicity.mo +0 -0
  127. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ro_RO/duplicity.mo +0 -0
  128. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ru_BY/duplicity.mo +0 -0
  129. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ru_MD/duplicity.mo +0 -0
  130. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ru_RU/duplicity.mo +0 -0
  131. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/ru_UA/duplicity.mo +0 -0
  132. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/sr_SP/duplicity.mo +0 -0
  133. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/sv_SE/duplicity.mo +0 -0
  134. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/tr_TR/duplicity.mo +0 -0
  135. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/uk_UA/duplicity.mo +0 -0
  136. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/vi_VN/duplicity.mo +0 -0
  137. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/zh_CN/duplicity.mo +0 -0
  138. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/zh_HK/duplicity.mo +0 -0
  139. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/zh_MO/duplicity.mo +0 -0
  140. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/zh_SG/duplicity.mo +0 -0
  141. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/po/zh_TW/duplicity.mo +0 -0
  142. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/requirements.txt +0 -0
  143. {duplicity-3.0.8.dev2 → duplicity-3.0.8.dev3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duplicity
3
- Version: 3.0.8.dev2
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.8.dev2"
24
+ __version__: str = "3.0.8.dev3"
25
25
  __reldate__: str = "January 14, 2026"
26
26
 
27
27
  gettext.install("duplicity", names=["ngettext"])
@@ -132,46 +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
+ # 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
+
135
142
  # no passphrase if --no-encryption or --use-agent
136
143
  if not config.encryption or config.use_agent:
137
144
  return ""
138
145
 
139
- # no passphrase if --passphrase* in --gpg-options
140
- if "--passphrase" in config.gpg_options:
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
+ ]:
141
154
  return ""
142
155
 
143
- # Check if encryption passphrase is needed
144
- asymmetric = False
145
- need_passphrase = False
146
- profile = config.gpg_profile
147
- encrypt_keys = profile.recipients + profile.hidden_recipients
148
- if profile.sign_key:
149
- encrypt_keys.append(profile.sign_key)
150
- if encrypt_keys and config.check_remote:
151
- asymmetric = True
152
- for key in encrypt_keys:
153
- if util.key_needs_passphrase(config.gpg_binary, key):
154
- log.Notice(f"Key {key} needs passphrase.")
155
- need_passphrase = True
156
- break
157
- else:
158
- log.Notice("No encryption keys need passphrase.")
159
- else:
160
- need_passphrase = True
161
- log.Notice("No encryption keys configured.")
162
-
163
- skips = copy.copy(skips_sync_archive)
164
- skips.remove("full")
165
- if (action == "full" and asymmetric) or config.restart or action in skips:
166
- log.Notice(f"Skipping passphrase request for action {action}")
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
+ ):
167
163
  return ""
168
164
 
169
- elif asymmetric and not need_passphrase:
170
- log.Notice(_("Skipping because no encryption key passphrase is needed."))
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."))
171
171
  return ""
172
172
 
173
+ # Finally, ask the user for the passphrase
173
174
  else:
174
- log.Notice(_("No environment variables are set, asking user."))
175
+ log.Info(_("PASSPHRASE variable not set, asking user."))
175
176
  use_cache = True
176
177
  while True:
177
178
  # ask the user to enter a new passphrase to avoid an infinite loop
@@ -205,95 +205,6 @@ def release_lockfile():
205
205
  pass
206
206
 
207
207
 
208
- def key_needs_passphrase(gpgbin, key, logfile=None):
209
- """
210
- Determine whether a GnuPG key requires a passphrase.
211
-
212
- This helper invokes the specified GnuPG frontend in a non‑destructive
213
- way to discover if the secret key is protected by a passphrase. It uses
214
- `pexpect` to spawn the command and watch for prompts or agent errors,
215
- never changing the key material itself.
216
-
217
- How it works
218
- - Runs: ``<gpgbin> --pinentry-mode cancel --dry-run --change-passphrase <key>``
219
- with a C UTF‑8 locale to ensure predictable output.
220
- - Interprets the interaction:
221
- - If the process reaches EOF without a passphrase prompt, the key is
222
- considered not to need a passphrase.
223
- - If a passphrase prompt appears (matches ``passphrase.*:``), the key
224
- is considered to need a passphrase.
225
- - If ``gpg-agent`` fails to start or ignores an inquiry, we log an
226
- error and return ``None`` to signal an indeterminate result.
227
-
228
- Parameters
229
- - gpgbin: str
230
- The GnuPG command to execute, e.g. ``"gpg"`` or ``"gpgsm"``.
231
- - key: str
232
- The key identifier understood by the given binary. Examples:
233
- - For ``gpg`` (OpenPGP): a key ID or fingerprint, e.g. ``"56538CCF"``.
234
- - For ``gpgsm`` (S/MIME): a certificate keyref, e.g.
235
- ``"\\&165F2FB4F58D..."``.
236
- - logfile: a file-like object or ``None``
237
- If provided, raw pexpect I/O is mirrored to this stream for debugging
238
- (e.g. ``sys.stdout``). Defaults to ``None``.
239
-
240
- Returns
241
- - ``True`` if the key requires a passphrase.
242
- - ``False`` if the key does not require a passphrase.
243
- - ``None`` if the status cannot be determined due to a runtime error
244
- (e.g., agent failed to start or pexpect raised an exception).
245
-
246
- Notes
247
- - The check is read‑only: ``--dry-run`` and ``--pinentry-mode cancel`` are
248
- used to avoid modifying the key or prompting the user.
249
- - Environment variables ``LANG`` and ``LC_ALL`` are forced to ``C.utf8``
250
- to make output matching stable across locales.
251
- - For end‑to‑end manual verification with the repository’s test keyring,
252
- see ``testing/manual/needspass.py``.
253
- """
254
-
255
- environ = {**os.environ, "LANG": "C.utf8", "LC_ALL": "C.utf8"}
256
- cmd = f"{gpgbin} --pinentry-mode cancel --dry-run --change-passphrase {key} "
257
-
258
- log.Debug(f"{cmd=}")
259
-
260
- try:
261
- child = pexpect.spawn(cmd, encoding="utf-8", env=environ)
262
- child.logfile = logfile
263
- except pexpect.ExceptionPexpect as e:
264
- log.Error(f"An unexpected error occurred: {e}")
265
- return None
266
-
267
- try:
268
- got = child.expect(
269
- [
270
- pexpect.EOF,
271
- "passphrase.*:",
272
- "failed to start gpg-agent",
273
- "ignoring gpg-agent inquiry",
274
- ]
275
- )
276
- except pexpect.ExceptionPexpect as e:
277
- log.Error(f"Exception while checking if passphrase needed for: {key}:\n{e}")
278
- return None
279
-
280
- child.close()
281
- log.Debug(f"{child.exitstatus=}, {child.signalstatus=}, {got=}, {child.after=}")
282
-
283
- if got == 0:
284
- log.Debug(f"Key {key} does not need passphrase")
285
- return False
286
- elif got == 1:
287
- log.Debug(f"Key {key} needs passphrase")
288
- return True
289
- elif got == 2:
290
- log.Error(f"gpg-agent failed to start.")
291
- return None
292
- elif got == 3:
293
- log.Error(f"gpg-agent failed inquiry ignored.")
294
- return None
295
-
296
-
297
208
  def copyfileobj(infp, outfp, byte_count=-1):
298
209
  """
299
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.8.dev2
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 "January 14, 2026" "Version 3.0.8.dev2" "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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "duplicity"
3
- version = "3.0.8.dev2"
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.8.dev2"
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