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