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