duplicity 3.0.8.dev6__tar.gz → 3.1.0.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.8.dev6 → duplicity-3.1.0.dev0}/CHANGELOG.md +10 -1
- {duplicity-3.0.8.dev6/duplicity.egg-info → duplicity-3.1.0.dev0}/PKG-INFO +3 -3
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/README.md +2 -1
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/__init__.py +2 -2
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/cli_main.py +18 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/dup_main.py +8 -1
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/selection.py +159 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0/duplicity.egg-info}/PKG-INFO +3 -3
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity.egg-info/requires.txt +0 -1
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/man/duplicity.1 +33 -3
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/pyproject.toml +43 -10
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/requirements.txt +0 -1
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/setup.py +1 -1
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/AUTHORS.md +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/COPYING +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/README-LOG.md +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/README-REPO.md +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/README-TESTING.md +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/__main__.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/_librsyncmodule.c +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/argparse311.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backend_pool.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/__init__.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/_cf_cloudfiles.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/_cf_pyrax.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/_testbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/adbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/azurebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/b2backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/boxbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/cfbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/dpbxbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/gdocsbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/gdrivebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/giobackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/hsibackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/hubicbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/idrivedbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/imapbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/jottacloudbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/lftpbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/localbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/mediafirebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/megabackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/megav2backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/megav3backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/multibackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/ncftpbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/onedrivebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/par2backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/pcabackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/pydrivebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/pyrax_identity/__init__.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/pyrax_identity/hubic.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/rclonebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/rsyncbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/s3_boto3_backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/slatebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/ssh_paramiko_backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/ssh_pexpect_backend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/swiftbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/sxbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/tahoebackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/webdavbackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/backends/xorrisobackend.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/cached_ops.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/cli_data.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/cli_util.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/config.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/diffdir.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/dup_collections.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/dup_tarfile.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/dup_temp.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/dup_time.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/errors.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/file_naming.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/filechunkio.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/globmatch.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/gpg.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/gpginterface.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/lazy.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/librsync.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/log.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/manifest.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/patchdir.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/path.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/progress.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/robust.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/statistics.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/tempdir.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity/util.py +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity.egg-info/SOURCES.txt +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity.egg-info/dependency_links.txt +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity.egg-info/entry_points.txt +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/duplicity.egg-info/top_level.txt +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/af_ZA/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ar_SA/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ca_ES/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/cs_CZ/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/da_DK/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/de_AT/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/de_DE/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/el_GR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/en_AU/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/en_GB/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/en_PR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/en_US/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/es_EM/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/es_ES/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/es_MX/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/es_PR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/es_US/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/fi_FI/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/fr_FR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/he_IL/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/hu_HU/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/it_IT/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ja_JP/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ko_KR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/nl_BE/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/nl_NL/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/nl_SR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/no_NO/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/pl_PL/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/pt_BR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/pt_PT/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ro_RO/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ru_BY/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ru_MD/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ru_RU/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/ru_UA/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/sr_SP/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/sv_SE/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/tr_TR/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/uk_UA/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/vi_VN/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/zh_CN/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/zh_HK/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/zh_MO/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/zh_SG/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/po/zh_TW/duplicity.mo +0 -0
- {duplicity-3.0.8.dev6 → duplicity-3.1.0.dev0}/setup.cfg +0 -0
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
|
|
2
|
-
(Unreleased) / 2026-
|
|
2
|
+
(Unreleased) / 2026-06-04
|
|
3
3
|
=========================
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
rel.3.1.0.dev0 / 2026-06-04
|
|
8
|
+
===========================
|
|
9
|
+
|
|
10
|
+
* 5c4cdc2f:new: Now do pip builds for macOS and Linux
|
|
11
|
+
* 0140a374:new: Add include/exclude restore filtering using archive-relative paths
|
|
12
|
+
* 46a4796b:chg: Update snapcraft.yaml for license, type, and typo fixes.
|
|
13
|
+
* 143373b8:chg: Use `--build-on` for snapcraft remote-build.
|
|
14
|
+
* 182d6e33:chg: Use `python3 -m build` for sdist in release-prep.
|
|
15
|
+
|
|
7
16
|
rel.3.0.8.dev5 / 2026-05-12
|
|
8
17
|
===========================
|
|
9
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: duplicity
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.1.0.dev0
|
|
4
4
|
Summary: Encrypted backup using rsync algorithm
|
|
5
5
|
Author-email: Kenneth Loafman <kenneth@loafman.com>
|
|
6
6
|
Maintainer: Edgar Soldin, Thomas Laubrock
|
|
@@ -44,7 +44,6 @@ Requires-Dist: httplib2
|
|
|
44
44
|
Requires-Dist: pysocks
|
|
45
45
|
Requires-Dist: jottalib
|
|
46
46
|
Requires-Dist: keyring
|
|
47
|
-
Requires-Dist: lxml
|
|
48
47
|
Requires-Dist: mediafire
|
|
49
48
|
Requires-Dist: megatools
|
|
50
49
|
Requires-Dist: paramiko
|
|
@@ -74,6 +73,7 @@ sudo apt-get install -y \
|
|
|
74
73
|
openssl \
|
|
75
74
|
par2 \
|
|
76
75
|
python3-dev \
|
|
76
|
+
python3-lxml \
|
|
77
77
|
python3-pip \
|
|
78
78
|
python3-venv \
|
|
79
79
|
python3 \
|
|
@@ -101,7 +101,7 @@ packaging tools like this:
|
|
|
101
101
|
```shell
|
|
102
102
|
sudo python3 -m pip install --upgrade pip pipx
|
|
103
103
|
````
|
|
104
|
-
**NOTE: _Failure to upgrade will probably result in a failed install._
|
|
104
|
+
**NOTE: _Failure to upgrade will probably result in a failed install._ ←IMPORTANT!**
|
|
105
105
|
|
|
106
106
|
To make sure the pipx dirs are on your path do:
|
|
107
107
|
```shell
|
|
@@ -15,6 +15,7 @@ sudo apt-get install -y \
|
|
|
15
15
|
openssl \
|
|
16
16
|
par2 \
|
|
17
17
|
python3-dev \
|
|
18
|
+
python3-lxml \
|
|
18
19
|
python3-pip \
|
|
19
20
|
python3-venv \
|
|
20
21
|
python3 \
|
|
@@ -42,7 +43,7 @@ packaging tools like this:
|
|
|
42
43
|
```shell
|
|
43
44
|
sudo python3 -m pip install --upgrade pip pipx
|
|
44
45
|
````
|
|
45
|
-
**NOTE: _Failure to upgrade will probably result in a failed install._
|
|
46
|
+
**NOTE: _Failure to upgrade will probably result in a failed install._ ←IMPORTANT!**
|
|
46
47
|
|
|
47
48
|
To make sure the pipx dirs are on your path do:
|
|
48
49
|
```shell
|
|
@@ -320,6 +320,24 @@ def process_command_line(cmdline_list):
|
|
|
320
320
|
# count is only used by the remove-* commands
|
|
321
321
|
config.keep_chains = config.count
|
|
322
322
|
|
|
323
|
+
# restore selection uses archive-relative paths and is applied while restoring
|
|
324
|
+
if config.action == "restore" and config.select_opts:
|
|
325
|
+
if config.restore_path:
|
|
326
|
+
command_line_error(
|
|
327
|
+
"--path-to-restore cannot be combined with restore file selection options. "
|
|
328
|
+
"Use --path-to-restore for one archive path, or --include/--exclude for pattern-based restore."
|
|
329
|
+
)
|
|
330
|
+
unsupported_restore_selection = {
|
|
331
|
+
"--exclude-if-present",
|
|
332
|
+
"--exclude-other-filesystems",
|
|
333
|
+
"--files-from",
|
|
334
|
+
}
|
|
335
|
+
unsupported = sorted({opt for opt, _arg in config.select_opts if opt in unsupported_restore_selection})
|
|
336
|
+
if unsupported:
|
|
337
|
+
command_line_error(
|
|
338
|
+
"The following file selection options are not supported for restore: " + ", ".join(unsupported)
|
|
339
|
+
)
|
|
340
|
+
|
|
323
341
|
# selection only applies to certain commands
|
|
324
342
|
if config.action in ["full", "inc", "verify"]:
|
|
325
343
|
set_selection()
|
|
@@ -57,6 +57,7 @@ from duplicity import (
|
|
|
57
57
|
patchdir,
|
|
58
58
|
path,
|
|
59
59
|
progress,
|
|
60
|
+
selection,
|
|
60
61
|
tempdir,
|
|
61
62
|
util,
|
|
62
63
|
)
|
|
@@ -908,6 +909,9 @@ def restore_get_patched_rop_iter(col_stats):
|
|
|
908
909
|
f"available chains: {col_stats.all_backup_chains}"
|
|
909
910
|
)
|
|
910
911
|
backup_setlist = backup_chain.get_sets_at_time(time)
|
|
912
|
+
restore_selection = None
|
|
913
|
+
if config.action == "restore" and config.select_opts:
|
|
914
|
+
restore_selection = selection.get_restore_selection(config.select_opts, config.select_files)
|
|
911
915
|
num_vols = 0
|
|
912
916
|
for s in backup_setlist:
|
|
913
917
|
num_vols += len(s)
|
|
@@ -957,7 +961,10 @@ def restore_get_patched_rop_iter(col_stats):
|
|
|
957
961
|
|
|
958
962
|
fileobj_iters = list(map(get_fileobj_iter, backup_setlist))
|
|
959
963
|
tarfiles = list(map(patchdir.TarFile_FromFileobjs, fileobj_iters))
|
|
960
|
-
|
|
964
|
+
rop_iter = patchdir.tarfiles2rop_iter(tarfiles, index)
|
|
965
|
+
if restore_selection:
|
|
966
|
+
rop_iter = selection.filter_restore_path_iter(rop_iter, restore_selection)
|
|
967
|
+
return rop_iter
|
|
961
968
|
|
|
962
969
|
|
|
963
970
|
def restore_get_enc_fileobj(backend, filename, volume_info):
|
|
@@ -702,3 +702,162 @@ class Select(object):
|
|
|
702
702
|
return None
|
|
703
703
|
|
|
704
704
|
return test_fn
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class _ArchivePath(object):
|
|
708
|
+
"""Adapter exposing an archive ROPath with an archive-relative name."""
|
|
709
|
+
|
|
710
|
+
def __init__(self, ropath):
|
|
711
|
+
self.ropath = ropath
|
|
712
|
+
self.index = ropath.index
|
|
713
|
+
if self.index:
|
|
714
|
+
self.uc_name = os.fsdecode(b"/".join(self.index))
|
|
715
|
+
else:
|
|
716
|
+
self.uc_name = ""
|
|
717
|
+
|
|
718
|
+
def __getattr__(self, attr):
|
|
719
|
+
return getattr(self.ropath, attr)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
class ArchiveSelect(Select):
|
|
723
|
+
"""Apply selection rules to archive-relative restore paths.
|
|
724
|
+
|
|
725
|
+
Restore paths are matched in the namespace printed by list-current-files
|
|
726
|
+
and accepted by --path-to-restore, not in the original source filesystem
|
|
727
|
+
namespace used by backup selection.
|
|
728
|
+
"""
|
|
729
|
+
|
|
730
|
+
def __init__(self):
|
|
731
|
+
super().__init__(Path(b"."))
|
|
732
|
+
|
|
733
|
+
@staticmethod
|
|
734
|
+
def _normalize_archive_pattern(pattern):
|
|
735
|
+
while pattern.startswith("./"):
|
|
736
|
+
pattern = pattern[2:]
|
|
737
|
+
if pattern.startswith("/"):
|
|
738
|
+
raise FilePrefixError(pattern)
|
|
739
|
+
if any(part in (".", "..") for part in pattern.split("/") if part):
|
|
740
|
+
raise FilePrefixError(pattern)
|
|
741
|
+
return pattern
|
|
742
|
+
|
|
743
|
+
def parse_catch_error(self, exc):
|
|
744
|
+
"""Deal with restore archive-relative selection errors."""
|
|
745
|
+
if isinstance(exc, FilePrefixError):
|
|
746
|
+
log.FatalError(
|
|
747
|
+
dedent(_("""\
|
|
748
|
+
Fatal Error: Restore file selection pattern
|
|
749
|
+
%s
|
|
750
|
+
is invalid. Restore file selection patterns must be relative to the
|
|
751
|
+
archive root, using paths as printed by list-current-files, and must
|
|
752
|
+
not be absolute or contain '.' or '..' path elements.""")) % (exc,),
|
|
753
|
+
log.ErrorCode.file_prefix_error,
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
super().parse_catch_error(exc)
|
|
757
|
+
|
|
758
|
+
def glob_get_sf(self, glob_str, include, ignore_case=False):
|
|
759
|
+
"""Return archive-relative selection function based on glob_str."""
|
|
760
|
+
glob_str = self._normalize_archive_pattern(glob_str)
|
|
761
|
+
if glob_str == "**":
|
|
762
|
+
sel_func = lambda path: include
|
|
763
|
+
else:
|
|
764
|
+
sel_func = select_fn_from_glob(glob_str, include, ignore_case)
|
|
765
|
+
|
|
766
|
+
sel_func.exclude = not include
|
|
767
|
+
sel_func.name = (
|
|
768
|
+
f"archive shell glob {include and 'include' or 'exclude'} " f"{ignore_case and 'no-' or ''}case: {glob_str}"
|
|
769
|
+
)
|
|
770
|
+
return sel_func
|
|
771
|
+
|
|
772
|
+
def literal_get_sf(self, lit_str, include, ignore_case=False):
|
|
773
|
+
"""Return archive-relative selection function based on literal string."""
|
|
774
|
+
lit_str = self._normalize_archive_pattern(lit_str)
|
|
775
|
+
sel_func = self.select_fn_from_literal(lit_str, include, ignore_case)
|
|
776
|
+
sel_func.exclude = not include
|
|
777
|
+
sel_func.name = (
|
|
778
|
+
f"archive literal string {include and 'include' or 'exclude'} "
|
|
779
|
+
f"{ignore_case and 'no-' or ''}case: {lit_str}"
|
|
780
|
+
)
|
|
781
|
+
return sel_func
|
|
782
|
+
|
|
783
|
+
def exclude_older_get_sf(self, date):
|
|
784
|
+
"""Return archive-relative selection function based on archived mtime."""
|
|
785
|
+
|
|
786
|
+
def sel_func(path):
|
|
787
|
+
if not path.isreg():
|
|
788
|
+
return None
|
|
789
|
+
if path.getmtime() < date:
|
|
790
|
+
return 0
|
|
791
|
+
return None
|
|
792
|
+
|
|
793
|
+
sel_func.exclude = True
|
|
794
|
+
sel_func.name = f"Select older than {date}"
|
|
795
|
+
return sel_func
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def get_restore_selection(argtuples, filelists):
|
|
799
|
+
"""Return an archive-relative selector for restore filtering."""
|
|
800
|
+
sel = ArchiveSelect()
|
|
801
|
+
sel.ParseArgs(argtuples, filelists)
|
|
802
|
+
return sel
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def filter_restore_path_iter(path_iter, sel):
|
|
806
|
+
"""Yield selected restore ROPaths, including deferred parent directories."""
|
|
807
|
+
deferred_dirs = []
|
|
808
|
+
excluded_dirs = []
|
|
809
|
+
yielded_dirs = set()
|
|
810
|
+
|
|
811
|
+
def is_ancestor(parent_index, child_index):
|
|
812
|
+
return parent_index == child_index[: len(parent_index)]
|
|
813
|
+
|
|
814
|
+
def prune_deferred_dirs(index):
|
|
815
|
+
while deferred_dirs and not is_ancestor(deferred_dirs[-1].index, index):
|
|
816
|
+
deferred_dirs.pop()
|
|
817
|
+
|
|
818
|
+
def prune_excluded_dirs(index):
|
|
819
|
+
while excluded_dirs and not is_ancestor(excluded_dirs[-1], index):
|
|
820
|
+
excluded_dirs.pop()
|
|
821
|
+
|
|
822
|
+
def is_excluded_by_parent(index):
|
|
823
|
+
return excluded_dirs and is_ancestor(excluded_dirs[-1], index)
|
|
824
|
+
|
|
825
|
+
def yield_deferred_dirs():
|
|
826
|
+
for deferred_dir in deferred_dirs:
|
|
827
|
+
if deferred_dir.index not in yielded_dirs:
|
|
828
|
+
yielded_dirs.add(deferred_dir.index)
|
|
829
|
+
yield deferred_dir
|
|
830
|
+
del deferred_dirs[:]
|
|
831
|
+
|
|
832
|
+
def close_unselected(ropath):
|
|
833
|
+
fileobj = getattr(ropath, "fileobj", None)
|
|
834
|
+
if fileobj:
|
|
835
|
+
try:
|
|
836
|
+
fileobj.close()
|
|
837
|
+
except Exception:
|
|
838
|
+
pass
|
|
839
|
+
|
|
840
|
+
for ropath in path_iter:
|
|
841
|
+
prune_deferred_dirs(ropath.index)
|
|
842
|
+
prune_excluded_dirs(ropath.index)
|
|
843
|
+
if is_excluded_by_parent(ropath.index):
|
|
844
|
+
close_unselected(ropath)
|
|
845
|
+
continue
|
|
846
|
+
|
|
847
|
+
if ropath.index == ():
|
|
848
|
+
if ropath.index not in yielded_dirs:
|
|
849
|
+
deferred_dirs.append(ropath)
|
|
850
|
+
continue
|
|
851
|
+
|
|
852
|
+
result = sel.Select(_ArchivePath(ropath))
|
|
853
|
+
if result == 1:
|
|
854
|
+
yield from yield_deferred_dirs()
|
|
855
|
+
if ropath.isdir():
|
|
856
|
+
yielded_dirs.add(ropath.index)
|
|
857
|
+
yield ropath
|
|
858
|
+
elif result == 2 and ropath.isdir() and ropath.index not in yielded_dirs:
|
|
859
|
+
deferred_dirs.append(ropath)
|
|
860
|
+
else:
|
|
861
|
+
if result == 0 and ropath.isdir():
|
|
862
|
+
excluded_dirs.append(ropath.index)
|
|
863
|
+
close_unselected(ropath)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: duplicity
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.1.0.dev0
|
|
4
4
|
Summary: Encrypted backup using rsync algorithm
|
|
5
5
|
Author-email: Kenneth Loafman <kenneth@loafman.com>
|
|
6
6
|
Maintainer: Edgar Soldin, Thomas Laubrock
|
|
@@ -44,7 +44,6 @@ Requires-Dist: httplib2
|
|
|
44
44
|
Requires-Dist: pysocks
|
|
45
45
|
Requires-Dist: jottalib
|
|
46
46
|
Requires-Dist: keyring
|
|
47
|
-
Requires-Dist: lxml
|
|
48
47
|
Requires-Dist: mediafire
|
|
49
48
|
Requires-Dist: megatools
|
|
50
49
|
Requires-Dist: paramiko
|
|
@@ -74,6 +73,7 @@ sudo apt-get install -y \
|
|
|
74
73
|
openssl \
|
|
75
74
|
par2 \
|
|
76
75
|
python3-dev \
|
|
76
|
+
python3-lxml \
|
|
77
77
|
python3-pip \
|
|
78
78
|
python3-venv \
|
|
79
79
|
python3 \
|
|
@@ -101,7 +101,7 @@ packaging tools like this:
|
|
|
101
101
|
```shell
|
|
102
102
|
sudo python3 -m pip install --upgrade pip pipx
|
|
103
103
|
````
|
|
104
|
-
**NOTE: _Failure to upgrade will probably result in a failed install._
|
|
104
|
+
**NOTE: _Failure to upgrade will probably result in a failed install._ ←IMPORTANT!**
|
|
105
105
|
|
|
106
106
|
To make sure the pipx dirs are on your path do:
|
|
107
107
|
```shell
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH DUPLICITY 1 "
|
|
1
|
+
.TH DUPLICITY 1 "June 04, 2026" "Version 3.1.0.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
|
|
@@ -29,7 +29,7 @@ source_url target_directory
|
|
|
29
29
|
target_url
|
|
30
30
|
|
|
31
31
|
.B duplicity [restore]
|
|
32
|
-
.I [options] [--path-to-restore <relpath>] [--time time]
|
|
32
|
+
.I [options] [--path-to-restore <relpath> | file selection options] [--time time]
|
|
33
33
|
source_url target_directory
|
|
34
34
|
|
|
35
35
|
.B duplicity remove-older-than <time>
|
|
@@ -215,10 +215,21 @@ the other hand if the archive has been deleted or corrupted, this
|
|
|
215
215
|
action will not detect it.
|
|
216
216
|
|
|
217
217
|
.TP
|
|
218
|
-
.BI "restore, rb " "[--path-to-restore <relpath>] [--time <time>] <url> <target_folder>"
|
|
218
|
+
.BI "restore, rb " "[--path-to-restore <relpath> | file selection options] [--time <time>] <url> <target_folder>"
|
|
219
219
|
You can restore the full monty or selected folders/files from a specific time.
|
|
220
220
|
Use the relative path as it is printed by
|
|
221
221
|
.BR list-current-files .
|
|
222
|
+
.br
|
|
223
|
+
For pattern-based restore filtering, file selection options such as
|
|
224
|
+
.BR --include ,
|
|
225
|
+
.BR --exclude ,
|
|
226
|
+
.BR --include-regexp ,
|
|
227
|
+
and
|
|
228
|
+
.BR --exclude-regexp
|
|
229
|
+
also match paths relative to the archive root, as printed by
|
|
230
|
+
.BR list-current-files .
|
|
231
|
+
These file selection options cannot be combined with
|
|
232
|
+
.BR --path-to-restore .
|
|
222
233
|
Usually not needed as duplicity enters restore mode when it detects that the URL
|
|
223
234
|
comes before the local folder.
|
|
224
235
|
|
|
@@ -1658,6 +1669,25 @@ system, unless
|
|
|
1658
1669
|
.B "--files-from"
|
|
1659
1670
|
has been specified in which case the passed list of individual files
|
|
1660
1671
|
is used instead.
|
|
1672
|
+
During restore, supported file selection options filter archive-relative
|
|
1673
|
+
paths, matching the names printed by
|
|
1674
|
+
.BR list-current-files ,
|
|
1675
|
+
and are mutually exclusive with
|
|
1676
|
+
.BR --path-to-restore .
|
|
1677
|
+
Restore file selection patterns must be relative to the archive root,
|
|
1678
|
+
may optionally start with
|
|
1679
|
+
.BR ./ ,
|
|
1680
|
+
and must not be absolute or contain
|
|
1681
|
+
.B .
|
|
1682
|
+
or
|
|
1683
|
+
.B ..
|
|
1684
|
+
path elements.
|
|
1685
|
+
Options requiring live source filesystem semantics, such as
|
|
1686
|
+
.BR --exclude-if-present ,
|
|
1687
|
+
.BR --exclude-other-filesystems ,
|
|
1688
|
+
and
|
|
1689
|
+
.BR --files-from ,
|
|
1690
|
+
are not supported during restore.
|
|
1661
1691
|
|
|
1662
1692
|
The file selection system comprises a number of file
|
|
1663
1693
|
selection conditions, which are set using one of the following command
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "duplicity"
|
|
3
|
-
version = "3.0.
|
|
3
|
+
version = "3.1.0.dev0"
|
|
4
4
|
dynamic = ["dependencies"]
|
|
5
5
|
description = "Encrypted backup using rsync algorithm"
|
|
6
6
|
authors = [
|
|
@@ -128,14 +128,16 @@ testpaths = [
|
|
|
128
128
|
|
|
129
129
|
|
|
130
130
|
[tool.cibuildwheel]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
"python -m pip install -q
|
|
131
|
+
before-build = [
|
|
132
|
+
# Clean up stale build folders and old egg-info before compiling each wheel
|
|
133
|
+
# to build_ext with proper python version. Build any extensions needed.
|
|
134
|
+
# make sure we build_ext with the current python
|
|
135
|
+
"rm -rf build/ *.egg-info",
|
|
136
|
+
"rm -rf .setuptools-cmake-build _skbuild",
|
|
137
|
+
# install all the python requirements but not lxml
|
|
138
|
+
"python -m pip install -q --upgrade pip",
|
|
139
|
+
"grep -v lxml requirements.txt > requirements.whl",
|
|
140
|
+
"python -m pip install -q -r requirements.whl",
|
|
139
141
|
"python ./setup.py build_ext",
|
|
140
142
|
]
|
|
141
143
|
|
|
@@ -143,15 +145,46 @@ before-all = [
|
|
|
143
145
|
build-option = "--use-pep517"
|
|
144
146
|
|
|
145
147
|
[tool.cibuildwheel.linux]
|
|
148
|
+
before-all = [
|
|
149
|
+
# WARNING: wheel builds are RedHat-based, not Debian.
|
|
150
|
+
"yum update -y",
|
|
151
|
+
"yum install -y gcc gcc-c++ make git intltool librsync-devel",
|
|
152
|
+
"yum install -y libffi-devel openssl-devel openssl tzdata",
|
|
153
|
+
"yum install -y libxml2-devel libxslt-devel python3-lxml",
|
|
154
|
+
]
|
|
146
155
|
archs = [
|
|
147
|
-
"x86_64",
|
|
148
156
|
"aarch64",
|
|
157
|
+
# "armv7l", # is 32-bit
|
|
158
|
+
# "ppc64le", # lxml build fails
|
|
159
|
+
# "riscv64", # no librsync-devel
|
|
160
|
+
# "s390x", # lxml build fails
|
|
161
|
+
"x86_64",
|
|
149
162
|
]
|
|
150
163
|
build = [
|
|
151
164
|
"cp{310,311,312,313,314}-manylinux_aarch64",
|
|
165
|
+
# "cp{310,311,312,313,314}-manylinux_armv7l", # is 32-bit
|
|
166
|
+
# "cp{310,311,312,313,314}-manylinux_ppc64le", # lxml build fails
|
|
167
|
+
# "cp{310,311,312,313,314}-manylinux_riscv64", # no librsync-devel
|
|
168
|
+
# "cp{310,311,312,313,314}-manylinux_s390x", # lxml build fails
|
|
152
169
|
"cp{310,311,312,313,314}-manylinux_x86_64",
|
|
153
170
|
]
|
|
154
171
|
|
|
172
|
+
[tool.cibuildwheel.macos]
|
|
173
|
+
environment = "LIBRSYNC_DIR=$HOMEBREW_PREFIX MACOSX_DEPLOYMENT_TARGET=26.0"
|
|
174
|
+
before-all = [
|
|
175
|
+
# install packages using homebrew
|
|
176
|
+
"echo $LIBRSYNC_DIR",
|
|
177
|
+
"brew install cmake intltool librsync",
|
|
178
|
+
]
|
|
179
|
+
archs = [
|
|
180
|
+
"arm64",
|
|
181
|
+
"x86_64",
|
|
182
|
+
]
|
|
183
|
+
build = [
|
|
184
|
+
"cp{310,311,312,313,314}-macosx_arm64",
|
|
185
|
+
"cp{310,311,312,313,314}-macosx_x86_64",
|
|
186
|
+
]
|
|
187
|
+
|
|
155
188
|
|
|
156
189
|
[tool.pylint]
|
|
157
190
|
jobs = 0
|
|
@@ -38,7 +38,7 @@ elif not ((3, 10) <= sys.version_info[:2] <= (3, 14)):
|
|
|
38
38
|
print("Sorry, duplicity requires Python version 3.10 thru 3.14.", file=sys.stderr)
|
|
39
39
|
sys.exit(1)
|
|
40
40
|
|
|
41
|
-
Version: str = "3.0.
|
|
41
|
+
Version: str = "3.1.0.dev0"
|
|
42
42
|
|
|
43
43
|
# READTHEDOCS uses setup.py sdist but can't handle extensions
|
|
44
44
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|