diff --git a/NEWS b/NEWS
index 8ffbbde..f051887 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,9 @@
1.7.11.dev0
+ * #479: Automatically use the "archive_name_format" option to filter which archives get used for
+ borgmatic actions that operate on multiple archives. See the documentation for more information:
+ https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#archive-naming
+ * #479: The "prefix" options have been deprecated in favor of the new "archive_name_format"
+ auto-matching behavior (see above).
* #662: Fix regression in which "check_repositories" option failed to match repositories.
* #663: Fix regression in which the "transfer" action produced a traceback.
diff --git a/borgmatic/borg/check.py b/borgmatic/borg/check.py
index 993a3c0..4630782 100644
--- a/borgmatic/borg/check.py
+++ b/borgmatic/borg/check.py
@@ -12,7 +12,6 @@ DEFAULT_CHECKS = (
{'name': 'repository', 'frequency': '1 month'},
{'name': 'archives', 'frequency': '1 month'},
)
-DEFAULT_PREFIX = '{hostname}-' # noqa: FS003
logger = logging.getLogger(__name__)
@@ -146,9 +145,10 @@ def filter_checks_on_frequency(
return tuple(filtered_checks)
-def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
+def make_check_flags(local_borg_version, storage_config, checks, check_last=None, prefix=None):
'''
- Given the local Borg version and a parsed sequence of checks, transform the checks into tuple of
+ Given the local Borg version, a storge configuration dict, a parsed sequence of checks, the
+ check last value, and a consistency check prefix, transform the checks into tuple of
command-line flags.
For example, given parsed checks of:
@@ -174,10 +174,19 @@ def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
if 'archives' in checks:
last_flags = ('--last', str(check_last)) if check_last else ()
- if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
- match_archives_flags = ('--match-archives', f'sh:{prefix}*') if prefix else ()
- else:
- match_archives_flags = ('--glob-archives', f'{prefix}*') if prefix else ()
+ match_archives_flags = (
+ (
+ ('--match-archives', f'sh:{prefix}*')
+ if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
+ else ('--glob-archives', f'{prefix}*')
+ )
+ if prefix
+ else (
+ flags.make_match_archives_flags(
+ storage_config.get('archive_name_format'), local_borg_version
+ )
+ )
+ )
else:
last_flags = ()
match_archives_flags = ()
@@ -291,7 +300,7 @@ def check_archives(
extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
if set(checks).intersection({'repository', 'archives', 'data'}):
- lock_wait = storage_config.get('lock_wait', None)
+ lock_wait = storage_config.get('lock_wait')
verbosity_flags = ()
if logger.isEnabledFor(logging.INFO):
@@ -299,12 +308,12 @@ def check_archives(
if logger.isEnabledFor(logging.DEBUG):
verbosity_flags = ('--debug', '--show-rc')
- prefix = consistency_config.get('prefix', DEFAULT_PREFIX)
+ prefix = consistency_config.get('prefix')
full_command = (
(local_path, 'check')
+ (('--repair',) if repair else ())
- + make_check_flags(local_borg_version, checks, check_last, prefix)
+ + make_check_flags(local_borg_version, storage_config, checks, check_last, prefix)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ verbosity_flags
diff --git a/borgmatic/borg/flags.py b/borgmatic/borg/flags.py
index 5dcebf5..1354d65 100644
--- a/borgmatic/borg/flags.py
+++ b/borgmatic/borg/flags.py
@@ -1,4 +1,5 @@
import itertools
+import re
from borgmatic.borg import feature
@@ -56,3 +57,20 @@ def make_repository_archive_flags(repository_path, archive, local_borg_version):
if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
else (f'{repository_path}::{archive}',)
)
+
+
+def make_match_archives_flags(archive_name_format, local_borg_version):
+ '''
+ Return the match archives flags that would match archives created with the given archive name
+ format (if any). This is done by replacing certain archive name format placeholders for
+ ephemeral data (like "{now}") with globs.
+ '''
+ if not archive_name_format:
+ return ()
+
+ match_archives = re.sub(r'\{(now|utcnow|pid)([:%\w\.-]*)\}', '*', archive_name_format)
+
+ if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
+ return ('--match-archives', f'sh:{match_archives}')
+ else:
+ return ('--glob-archives', f'{match_archives}')
diff --git a/borgmatic/borg/info.py b/borgmatic/borg/info.py
index 6142104..fa59165 100644
--- a/borgmatic/borg/info.py
+++ b/borgmatic/borg/info.py
@@ -44,7 +44,11 @@ def display_archives_info(
else flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
)
if info_arguments.prefix
- else ()
+ else (
+ flags.make_match_archives_flags(
+ storage_config.get('archive_name_format'), local_borg_version
+ )
+ )
)
+ flags.make_flags_from_arguments(
info_arguments, excludes=('repository', 'archive', 'prefix')
diff --git a/borgmatic/borg/prune.py b/borgmatic/borg/prune.py
index d21ceee..08cb0c1 100644
--- a/borgmatic/borg/prune.py
+++ b/borgmatic/borg/prune.py
@@ -7,10 +7,10 @@ from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
-def make_prune_flags(retention_config, local_borg_version):
+def make_prune_flags(storage_config, retention_config, local_borg_version):
'''
- Given a retention config dict mapping from option name to value, tranform it into an iterable of
- command-line name-value flag pairs.
+ Given a retention config dict mapping from option name to value, tranform it into an sequence of
+ command-line flags.
For example, given a retention config of:
@@ -24,7 +24,7 @@ def make_prune_flags(retention_config, local_borg_version):
)
'''
config = retention_config.copy()
- prefix = config.pop('prefix', '{hostname}-') # noqa: FS003
+ prefix = config.pop('prefix', None)
if prefix:
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
@@ -32,10 +32,16 @@ def make_prune_flags(retention_config, local_borg_version):
else:
config['glob_archives'] = f'{prefix}*'
- return (
+ flag_pairs = (
('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
)
+ return tuple(
+ element for pair in flag_pairs for element in pair
+ ) + flags.make_match_archives_flags(
+ storage_config.get('archive_name_format'), local_borg_version
+ )
+
def prune_archives(
dry_run,
@@ -60,11 +66,7 @@ def prune_archives(
full_command = (
(local_path, 'prune')
- + tuple(
- element
- for pair in make_prune_flags(retention_config, local_borg_version)
- for element in pair
- )
+ + make_prune_flags(storage_config, retention_config, local_borg_version)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
diff --git a/borgmatic/borg/rlist.py b/borgmatic/borg/rlist.py
index 8625363..f3935a1 100644
--- a/borgmatic/borg/rlist.py
+++ b/borgmatic/borg/rlist.py
@@ -94,7 +94,11 @@ def make_rlist_command(
else flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
)
if rlist_arguments.prefix
- else ()
+ else (
+ flags.make_match_archives_flags(
+ storage_config.get('archive_name_format'), local_borg_version
+ )
+ )
)
+ flags.make_flags_from_arguments(rlist_arguments, excludes=MAKE_FLAGS_EXCLUDES)
+ flags.make_repository_flags(repository_path, local_borg_version)
diff --git a/borgmatic/borg/transfer.py b/borgmatic/borg/transfer.py
index 29e205c..a350eff 100644
--- a/borgmatic/borg/transfer.py
+++ b/borgmatic/borg/transfer.py
@@ -34,9 +34,16 @@ def transfer_archives(
'match-archives', transfer_arguments.match_archives or transfer_arguments.archive
)
)
- + flags.make_flags_from_arguments(
- transfer_arguments,
- excludes=('repository', 'source_repository', 'archive', 'match_archives'),
+ + (
+ flags.make_flags_from_arguments(
+ transfer_arguments,
+ excludes=('repository', 'source_repository', 'archive', 'match_archives'),
+ )
+ or (
+ flags.make_match_archives_flags(
+ storage_config.get('archive_name_format'), local_borg_version
+ )
+ )
)
+ flags.make_repository_flags(repository_path, local_borg_version)
+ flags.make_flags('other-repo', transfer_arguments.source_repository)
diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml
index 469b1f5..d437452 100644
--- a/borgmatic/config/schema.yaml
+++ b/borgmatic/config/schema.yaml
@@ -378,11 +378,9 @@ properties:
description: |
Name of the archive. Borg placeholders can be used. See the
output of "borg help placeholders" for details. Defaults to
- "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this
- option, consider also specifying a prefix in the retention
- and consistency sections to avoid accidental
- pruning/checking of archives with different archive name
- formats.
+ "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". When running
+ actions like rlist, info, or check, borgmatic automatically
+ tries to match only archives created with this name format.
example: "{hostname}-documents-{now}"
relocated_repo_access_is_ok:
type: boolean
@@ -477,10 +475,12 @@ properties:
prefix:
type: string
description: |
- When pruning, only consider archive names starting with this
- prefix. Borg placeholders can be used. See the output of
- "borg help placeholders" for details. Defaults to
- "{hostname}-". Use an empty value to disable the default.
+ Deprecated. When pruning, only consider archive names
+ starting with this prefix. Borg placeholders can be used.
+ See the output of "borg help placeholders" for details.
+ If a prefix is not specified, borgmatic defaults to
+ matching archives based on the archive_name_format (see
+ above).
example: sourcehostname
consistency:
type: object
@@ -556,11 +556,12 @@ properties:
prefix:
type: string
description: |
- When performing the "archives" check, only consider archive
- names starting with this prefix. Borg placeholders can be
- used. See the output of "borg help placeholders" for
- details. Defaults to "{hostname}-". Use an empty value to
- disable the default.
+ Deprecated. When performing the "archives" check, only
+ consider archive names starting with this prefix. Borg
+ placeholders can be used. See the output of "borg help
+ placeholders" for details. If a prefix is not specified,
+ borgmatic defaults to matching archives based on the
+ archive_name_format (see above).
example: sourcehostname
output:
type: object
diff --git a/docs/how-to/make-per-application-backups.md b/docs/how-to/make-per-application-backups.md
index e5ba037..6e5d999 100644
--- a/docs/how-to/make-per-application-backups.md
+++ b/docs/how-to/make-per-application-backups.md
@@ -54,6 +54,72 @@ choice](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot),
each entry using borgmatic's `--config` flag instead of relying on
`/etc/borgmatic.d`.
+
+## Archive naming
+
+If you've got multiple borgmatic configuration files, you might want to create
+archives with different naming schemes for each one. This is especially handy
+if each configuration file is backing up to the same Borg repository but you
+still want to be able to distinguish backup archives for one application from
+another.
+
+borgmatic supports this use case with an `archive_name_format` option. The
+idea is that you define a string format containing a number of [Borg
+placeholders](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-placeholders),
+and borgmatic uses that format to name any new archive it creates. For
+instance:
+
+```yaml
+location:
+ ...
+ archive_name_format: home-directories-{now}
+```
+
+This means that when borgmatic creates an archive, its name will start with
+the string `home-directories-` and end with a timestamp for its creation time.
+If `archive_name_format` is unspecified, the default is
+`{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}`, meaning your system hostname plus a
+timestamp in a particular format.
+
+New in version 1.7.11 borgmatic
+uses the `archive_name_format` option to automatically limit which archives
+get used for actions operating on multiple archives. This prevents, for
+instance, duplicate archives from showing up in `rlist` or `info` results—even
+if the same repository appears in multiple borgmatic configuration files. To
+take advantage of this feature, simply use a different `archive_name_format`
+in each configuration file.
+
+Under the hood, borgmatic accomplishes this by substituting globs for certain
+ephemeral data placeholders in your `archive_name_format`—and using the result
+to filter archives for supported actions.
+
+For instance, let's say that you have this in your configuration:
+
+```yaml
+location:
+ ...
+ archive_name_format: {hostname}-user-data-{now}
+```
+
+borgmatic considers `{now}` an emphemeral placeholder that will probably
+change per archive, while `{hostname}` won't. So it turns the example value
+into `{hostname}-user-data-*` and applies it to filter down the set of
+archives used for actions like `rlist`, `info`, `prune`, `check`, etc.
+
+The end result is that when borgmatic runs the actions for a particular
+application-specific configuration file, it only operates on the archives
+created for that application. Of course, this doesn't apply to actions like
+`compact` that operate on an entire repository.
+
+Prior to 1.7.11 The way to
+limit the archives used was a `prefix` option in the `retention` section for
+matching against the start of archive names used for a `prune` action and
+a separate `prefix` option in the `consistency` section for matching against
+the start of archive names used for a `check` action. Both of these options
+are deprecated in favor of the auto-matching behavior in newer versions of
+borgmatic mentioned above.
+
+
## Configuration includes
Once you have multiple different configuration files, you might want to share
@@ -272,7 +338,7 @@ Here's an example usage:
```yaml
constants:
user: foo
- my_prefix: bar-
+ archive_prefix: bar
location:
source_directories:
@@ -281,20 +347,14 @@ location:
...
storage:
- archive_name_format: '{my_prefix}{now}'
-
-retention:
- prefix: {my_prefix}
-
-consistency:
- prefix: {my_prefix}
+ archive_name_format: '{archive_prefix}-{now}'
```
In this example, when borgmatic runs, all instances of `{user}` get replaced
-with `foo` and all instances of `{my_prefix}` get replaced with `bar-`. (And
-in this particular example, `{now}` doesn't get replaced with anything, but
-gets passed directly to Borg.) After substitution, the logical result looks
-something like this:
+with `foo` and all instances of `{archive-prefix}` get replaced with `bar-`.
+(And in this particular example, `{now}` doesn't get replaced with anything,
+but gets passed directly to Borg.) After substitution, the logical result
+looks something like this:
```yaml
location:
@@ -305,12 +365,6 @@ location:
storage:
archive_name_format: 'bar-{now}'
-
-retention:
- prefix: bar-
-
-consistency:
- prefix: bar-
```
An alternate to constants is passing in your values via [environment
diff --git a/tests/unit/borg/test_check.py b/tests/unit/borg/test_check.py
index 7575556..7c233bc 100644
--- a/tests/unit/borg/test_check.py
+++ b/tests/unit/borg/test_check.py
@@ -189,150 +189,170 @@ def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_
def test_make_check_flags_with_repository_check_returns_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository',))
+ flags = module.make_check_flags('1.2.3', {}, ('repository',))
assert flags == ('--repository-only',)
def test_make_check_flags_with_archives_check_returns_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('archives',))
+ flags = module.make_check_flags('1.2.3', {}, ('archives',))
assert flags == ('--archives-only',)
def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('data',))
+ flags = module.make_check_flags('1.2.3', {}, ('data',))
assert flags == ('--archives-only', '--verify-data',)
def test_make_check_flags_with_extract_omits_extract_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('extract',))
+ flags = module.make_check_flags('1.2.3', {}, ('extract',))
assert flags == ()
def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository', 'data',))
+ flags = module.make_check_flags('1.2.3', {}, ('repository', 'data',))
assert flags == ('--verify-data',)
-def test_make_check_flags_with_default_checks_and_default_prefix_returns_default_flags():
+def test_make_check_flags_with_default_checks_and_prefix_returns_default_flags():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
+
+ flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo',)
+
+ assert flags == ('--match-archives', 'sh:foo*')
+
+
+def test_make_check_flags_with_all_checks_and_prefix_returns_default_flags():
+ flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags(
- '1.2.3', ('repository', 'archives'), prefix=module.DEFAULT_PREFIX
+ '1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
)
- assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
+ assert flags == ('--match-archives', 'sh:foo*')
-def test_make_check_flags_with_all_checks_and_default_prefix_returns_default_flags():
- flexmock(module.feature).should_receive('available').and_return(True)
-
- flags = module.make_check_flags(
- '1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
- )
-
- assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
-
-
-def test_make_check_flags_with_all_checks_and_default_prefix_without_borg_features_returns_glob_archives_flags():
+def test_make_check_flags_with_all_checks_and_prefix_without_borg_features_returns_glob_archives_flags():
flexmock(module.feature).should_receive('available').and_return(False)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags(
- '1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
+ '1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
)
- assert flags == ('--glob-archives', f'{module.DEFAULT_PREFIX}*')
+ assert flags == ('--glob-archives', 'foo*')
def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('archives',), check_last=3)
+ flags = module.make_check_flags('1.2.3', {}, ('archives',), check_last=3)
assert flags == ('--archives-only', '--last', '3')
def test_make_check_flags_with_data_check_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('data',), check_last=3)
+ flags = module.make_check_flags('1.2.3', {}, ('data',), check_last=3)
assert flags == ('--archives-only', '--last', '3', '--verify-data')
def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository',), check_last=3)
+ flags = module.make_check_flags('1.2.3', {}, ('repository',), check_last=3)
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository', 'archives'), check_last=3)
+ flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), check_last=3)
assert flags == ('--last', '3')
def test_make_check_flags_with_archives_check_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('archives',), prefix='foo-')
+ flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix='foo-')
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
def test_make_check_flags_with_data_check_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('data',), prefix='foo-')
+ flags = module.make_check_flags('1.2.3', {}, ('data',), prefix='foo-')
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*', '--verify-data')
-def test_make_check_flags_with_archives_check_and_empty_prefix_omits_match_archives_flag():
+def test_make_check_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ 'bar-{now}', '1.2.3' # noqa: FS003
+ ).and_return(('--match-archives', 'sh:bar-*'))
- flags = module.make_check_flags('1.2.3', ('archives',), prefix='')
+ flags = module.make_check_flags(
+ '1.2.3', {'archive_name_format': 'bar-{now}'}, ('archives',), prefix='' # noqa: FS003
+ )
- assert flags == ('--archives-only',)
+ assert flags == ('--archives-only', '--match-archives', 'sh:bar-*')
def test_make_check_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('archives',), prefix=None)
+ flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix=None)
assert flags == ('--archives-only',)
def test_make_check_flags_with_repository_check_and_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository',), prefix='foo-')
+ flags = module.make_check_flags('1.2.3', {}, ('repository',), prefix='foo-')
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- flags = module.make_check_flags('1.2.3', ('repository', 'archives'), prefix='foo-')
+ flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo-')
assert flags == ('--match-archives', 'sh:foo-*')
@@ -427,7 +447,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
- '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+ '1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))
@@ -581,7 +601,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
- '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+ '1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg1', 'check', 'repo'))
@@ -608,7 +628,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
- '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+ '1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
@@ -628,6 +648,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
checks = ('repository',)
check_last = flexmock()
+ storage_config = {'lock_wait': 5}
consistency_config = {'check_last': check_last}
flexmock(module).should_receive('parse_checks')
flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
@@ -635,7 +656,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
- '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+ '1.2.3', storage_config, checks, check_last, None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
@@ -645,7 +666,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
module.check_archives(
repository_path='repo',
location_config={},
- storage_config={'lock_wait': 5},
+ storage_config=storage_config,
consistency_config=consistency_config,
local_borg_version='1.2.3',
)
@@ -662,7 +683,7 @@ def test_check_archives_with_retention_prefix():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
- '1.2.3', checks, check_last, prefix
+ '1.2.3', {}, checks, check_last, prefix
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))
diff --git a/tests/unit/borg/test_flags.py b/tests/unit/borg/test_flags.py
index 1985d81..cf9eedb 100644
--- a/tests/unit/borg/test_flags.py
+++ b/tests/unit/borg/test_flags.py
@@ -1,3 +1,4 @@
+import pytest
from flexmock import flexmock
from borgmatic.borg import flags as module
@@ -78,3 +79,35 @@ def test_make_repository_archive_flags_with_borg_features_joins_repository_and_a
assert module.make_repository_archive_flags(
repository_path='repo', archive='archive', local_borg_version='1.2.3'
) == ('repo::archive',)
+
+
+@pytest.mark.parametrize(
+ 'archive_name_format,feature_available,expected_result',
+ (
+ (None, True, ()),
+ ('', True, ()),
+ (
+ '{hostname}-docs-{now}', # noqa: FS003
+ True,
+ ('--match-archives', 'sh:{hostname}-docs-*'), # noqa: FS003
+ ),
+ ('{utcnow}-docs-{user}', True, ('--match-archives', 'sh:*-docs-{user}')), # noqa: FS003
+ ('{fqdn}-{pid}', True, ('--match-archives', 'sh:{fqdn}-*')), # noqa: FS003
+ (
+ 'stuff-{now:%Y-%m-%dT%H:%M:%S.%f}', # noqa: FS003
+ True,
+ ('--match-archives', 'sh:stuff-*'),
+ ),
+ ('{hostname}-docs-{now}', False, ('--glob-archives', '{hostname}-docs-*')), # noqa: FS003
+ ('{utcnow}-docs-{user}', False, ('--glob-archives', '*-docs-{user}')), # noqa: FS003
+ ),
+)
+def test_make_match_archives_flags_makes_flags_with_globs(
+ archive_name_format, feature_available, expected_result
+):
+ flexmock(module.feature).should_receive('available').and_return(feature_available)
+
+ assert (
+ module.make_match_archives_flags(archive_name_format, local_borg_version=flexmock())
+ == expected_result
+ )
diff --git a/tests/unit/borg/test_info.py b/tests/unit/borg/test_info.py
index fcc556a..b25f29f 100644
--- a/tests/unit/borg/test_info.py
+++ b/tests/unit/borg/test_info.py
@@ -12,6 +12,9 @@ def test_display_archives_info_calls_borg_with_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -34,6 +37,9 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -56,6 +62,9 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -78,6 +87,9 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -101,6 +113,9 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -123,6 +138,9 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -147,6 +165,9 @@ def test_display_archives_info_with_archive_calls_borg_with_match_archives_param
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -169,6 +190,9 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -195,6 +219,9 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
flexmock(module.flags).should_receive('make_flags').with_args(
'remote-path', 'borg1'
).and_return(('--remote-path', 'borg1'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -221,6 +248,9 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
('--lock-wait', '5')
)
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
storage_config = {'lock_wait': 5}
@@ -240,13 +270,16 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
)
-def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parameters():
+def test_display_archives_info_transforms_prefix_into_match_archives_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -265,12 +298,68 @@ def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parame
)
+def test_display_archives_info_prefers_prefix_over_archive_name_format():
+ flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+ flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+ flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_flags').with_args(
+ 'match-archives', 'sh:foo*'
+ ).and_return(('--match-archives', 'sh:foo*'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
+ flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+ flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+ flexmock(module.environment).should_receive('make_environment')
+ flexmock(module).should_receive('execute_command').with_args(
+ ('borg', 'info', '--match-archives', 'sh:foo*', '--repo', 'repo'),
+ output_log_level=module.borgmatic.logger.ANSWER,
+ borg_local_path='borg',
+ extra_environment=None,
+ )
+
+ module.display_archives_info(
+ repository_path='repo',
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
+ local_borg_version='2.3.4',
+ info_arguments=flexmock(archive=None, json=False, prefix='foo'),
+ )
+
+
+def test_display_archives_info_transforms_archive_name_format_into_match_archives_parameters():
+ flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+ flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+ flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ 'bar-{now}', '2.3.4' # noqa: FS003
+ ).and_return(('--match-archives', 'sh:bar-*'))
+ flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+ flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+ flexmock(module.environment).should_receive('make_environment')
+ flexmock(module).should_receive('execute_command').with_args(
+ ('borg', 'info', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
+ output_log_level=module.borgmatic.logger.ANSWER,
+ borg_local_path='borg',
+ extra_environment=None,
+ )
+
+ module.display_archives_info(
+ repository_path='repo',
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
+ local_borg_version='2.3.4',
+ info_arguments=flexmock(archive=None, json=False, prefix=None),
+ )
+
+
@pytest.mark.parametrize('argument_name', ('match_archives', 'sort_by', 'first', 'last'))
def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flag_name = f"--{argument_name.replace('_', ' ')}"
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '2.3.4'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(flag_name, 'value')
)
diff --git a/tests/unit/borg/test_prune.py b/tests/unit/borg/test_prune.py
index f33c993..1c8843e 100644
--- a/tests/unit/borg/test_prune.py
+++ b/tests/unit/borg/test_prune.py
@@ -18,18 +18,17 @@ def insert_execute_command_mock(prune_command, output_log_level):
).once()
-BASE_PRUNE_FLAGS = (('--keep-daily', '1'), ('--keep-weekly', '2'), ('--keep-monthly', '3'))
+BASE_PRUNE_FLAGS = ('--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
-def test_make_prune_flags_returns_flags_from_config_plus_default_prefix_glob():
+def test_make_prune_flags_returns_flags_from_config():
retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+ result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
- assert tuple(result) == BASE_PRUNE_FLAGS + (
- ('--match-archives', 'sh:{hostname}-*'), # noqa: FS003
- )
+ assert result == BASE_PRUNE_FLAGS
def test_make_prune_flags_accepts_prefix_with_placeholders():
@@ -37,15 +36,18 @@ def test_make_prune_flags_accepts_prefix_with_placeholders():
(('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
)
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+ result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
expected = (
- ('--keep-daily', '1'),
- ('--match-archives', 'sh:Documents_{hostname}-{now}*'), # noqa: FS003
+ '--keep-daily',
+ '1',
+ '--match-archives',
+ 'sh:Documents_{hostname}-{now}*', # noqa: FS003
)
- assert tuple(result) == expected
+ assert result == expected
def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
@@ -53,37 +55,38 @@ def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives()
(('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
)
flexmock(module.feature).should_receive('available').and_return(False)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
- result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+ result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
expected = (
- ('--keep-daily', '1'),
- ('--glob-archives', 'Documents_{hostname}-{now}*'), # noqa: FS003
+ '--keep-daily',
+ '1',
+ '--glob-archives',
+ 'Documents_{hostname}-{now}*', # noqa: FS003
)
- assert tuple(result) == expected
+ assert result == expected
-def test_make_prune_flags_treats_empty_prefix_as_no_prefix():
- retention_config = OrderedDict((('keep_daily', 1), ('prefix', '')))
- flexmock(module.feature).should_receive('available').and_return(True)
-
- result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
-
- expected = (('--keep-daily', '1'),)
-
- assert tuple(result) == expected
-
-
-def test_make_prune_flags_treats_none_prefix_as_no_prefix():
+def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
+ storage_config = {'archive_name_format': 'bar-{now}'} # noqa: FS003
retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
flexmock(module.feature).should_receive('available').and_return(True)
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ 'bar-{now}', '1.2.3' # noqa: FS003
+ ).and_return(('--match-archives', 'sh:bar-*'))
- result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+ result = module.make_prune_flags(storage_config, retention_config, local_borg_version='1.2.3')
- expected = (('--keep-daily', '1'),)
+ expected = (
+ '--keep-daily',
+ '1',
+ '--match-archives',
+ 'sh:bar-*', # noqa: FS003
+ )
- assert tuple(result) == expected
+ assert result == expected
PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
diff --git a/tests/unit/borg/test_rlist.py b/tests/unit/borg/test_rlist.py
index a098caf..8a10b07 100644
--- a/tests/unit/borg/test_rlist.py
+++ b/tests/unit/borg/test_rlist.py
@@ -127,6 +127,9 @@ def test_resolve_archive_name_with_lock_wait_calls_borg_with_lock_wait_parameter
def test_make_rlist_command_includes_log_info():
insert_logging_mock(logging.INFO)
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -143,6 +146,9 @@ def test_make_rlist_command_includes_log_info():
def test_make_rlist_command_includes_json_but_not_info():
insert_logging_mock(logging.INFO)
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -159,6 +165,9 @@ def test_make_rlist_command_includes_json_but_not_info():
def test_make_rlist_command_includes_log_debug():
insert_logging_mock(logging.DEBUG)
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -175,6 +184,9 @@ def test_make_rlist_command_includes_log_debug():
def test_make_rlist_command_includes_json_but_not_debug():
insert_logging_mock(logging.DEBUG)
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -190,6 +202,9 @@ def test_make_rlist_command_includes_json_but_not_debug():
def test_make_rlist_command_includes_json():
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -207,6 +222,9 @@ def test_make_rlist_command_includes_lock_wait():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(
('--lock-wait', '5')
).and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -222,6 +240,9 @@ def test_make_rlist_command_includes_lock_wait():
def test_make_rlist_command_includes_local_path():
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -240,6 +261,9 @@ def test_make_rlist_command_includes_remote_path():
flexmock(module.flags).should_receive('make_flags').and_return(
('--remote-path', 'borg2')
).and_return(()).and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -258,6 +282,9 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
('--match-archives', 'sh:foo*')
)
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -271,8 +298,47 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
+def test_make_rlist_command_prefers_prefix_over_archive_name_format():
+ flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
+ ('--match-archives', 'sh:foo*')
+ )
+ flexmock(module.flags).should_receive('make_match_archives_flags').never()
+ flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+ flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
+
+ command = module.make_rlist_command(
+ repository_path='repo',
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
+ local_borg_version='1.2.3',
+ rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix='foo'),
+ )
+
+ assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
+
+
+def test_make_rlist_command_transforms_archive_name_format_into_match_archives():
+ flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ 'bar-{now}', '1.2.3' # noqa: FS003
+ ).and_return(('--match-archives', 'sh:bar-*'))
+ flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+ flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
+
+ command = module.make_rlist_command(
+ repository_path='repo',
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
+ local_borg_version='1.2.3',
+ rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix=None),
+ )
+
+ assert command == ('borg', 'list', '--match-archives', 'sh:bar-*', 'repo')
+
+
def test_make_rlist_command_includes_short():
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--short',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -301,6 +367,9 @@ def test_make_rlist_command_includes_short():
)
def test_make_rlist_command_includes_additional_flags(argument_name):
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ None, '1.2.3'
+ ).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(f"--{argument_name.replace('_', '-')}", 'value')
)
diff --git a/tests/unit/borg/test_transfer.py b/tests/unit/borg/test_transfer.py
index a481442..3628a1d 100644
--- a/tests/unit/borg/test_transfer.py
+++ b/tests/unit/borg/test_transfer.py
@@ -12,6 +12,7 @@ def test_transfer_archives_calls_borg_with_flags():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -41,6 +42,7 @@ def test_transfer_archives_with_dry_run_calls_borg_with_dry_run_flag():
flexmock(module.flags).should_receive('make_flags').with_args('dry-run', True).and_return(
('--dry-run',)
)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -67,6 +69,7 @@ def test_transfer_archives_with_log_info_calls_borg_with_info_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -93,6 +96,7 @@ def test_transfer_archives_with_log_debug_calls_borg_with_debug_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -123,6 +127,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -137,7 +142,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
module.transfer_archives(
dry_run=False,
repository_path='repo',
- storage_config={},
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive='archive', progress=None, match_archives=None, source_repository=None
@@ -152,6 +157,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -166,7 +172,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
module.transfer_archives(
dry_run=False,
repository_path='repo',
- storage_config={},
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive=None, progress=None, match_archives='sh:foo*', source_repository=None
@@ -174,10 +180,40 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
)
+def test_transfer_archives_with_archive_name_format_calls_borg_with_match_archives_flag():
+ flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+ flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+ flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+ 'bar-{now}', '2.3.4' # noqa: FS003
+ ).and_return(('--match-archives', 'sh:bar-*'))
+ flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+ flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+ flexmock(module.environment).should_receive('make_environment')
+ flexmock(module).should_receive('execute_command').with_args(
+ ('borg', 'transfer', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
+ output_log_level=module.borgmatic.logger.ANSWER,
+ output_file=None,
+ borg_local_path='borg',
+ extra_environment=None,
+ )
+
+ module.transfer_archives(
+ dry_run=False,
+ repository_path='repo',
+ storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
+ local_borg_version='2.3.4',
+ transfer_arguments=flexmock(
+ archive=None, progress=None, match_archives=None, source_repository=None
+ ),
+ )
+
+
def test_transfer_archives_with_local_path_calls_borg_via_local_path():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -208,6 +244,7 @@ def test_transfer_archives_with_remote_path_calls_borg_with_remote_path_flags():
flexmock(module.flags).should_receive('make_flags').with_args(
'remote-path', 'borg2'
).and_return(('--remote-path', 'borg2'))
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -238,6 +275,7 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
('--lock-wait', '5')
)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
storage_config = {'lock_wait': 5}
@@ -265,6 +303,7 @@ def test_transfer_archives_with_progress_calls_borg_with_progress_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@@ -293,6 +332,7 @@ def test_transfer_archives_passes_through_arguments_to_borg(argument_name):
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flag_name = f"--{argument_name.replace('_', ' ')}"
flexmock(module.flags).should_receive('make_flags').and_return(())
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(flag_name, 'value')
)
@@ -327,6 +367,7 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
flexmock(module.flags).should_receive('make_flags').with_args('other-repo', 'other').and_return(
('--other-repo', 'other')
)
+ flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')