mirror of https://github.com/borgbackup/borg.git
Split up parsing and filtering for --keep-within (#2726)
Split up parsing and filtering for --keep-within Fixes #2610 Parse --keep-within argument early, via new method within_range passed to argparse type=, so that better error messages can be given. Also swallows ValueError stacktrace per the comment in the old code that including it wasn't desirable.
This commit is contained in:
parent
04aa426334
commit
932fb9ec7a
|
@ -21,7 +21,7 @@ import collections
|
|||
from . import __version__
|
||||
from .helpers import Error, location_validator, archivename_validator, format_line, format_time, format_file_size, \
|
||||
parse_pattern, PathPrefixPattern, to_localtime, timestamp, safe_timestamp, bin_to_hex, \
|
||||
get_cache_dir, prune_within, prune_split, check_python, \
|
||||
get_cache_dir, interval, prune_within, prune_split, check_python, \
|
||||
Manifest, NoManifestError, remove_surrogates, update_excludes, format_archive, check_extension_modules, Statistics, \
|
||||
dir_is_tagged, bigint_to_int, ChunkerParams, CompressionSpec, PrefixSpec, is_slow_msgpack, yes, sysinfo, \
|
||||
EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, log_multi, PatternMatcher, ErrorIgnoringTextIOWrapper, set_ec, \
|
||||
|
@ -1717,7 +1717,7 @@ class Archiver:
|
|||
subparser.add_argument('--list', dest='output_list',
|
||||
action='store_true', default=False,
|
||||
help='output verbose list of archives it keeps/prunes. Requires -v/--verbose.')
|
||||
subparser.add_argument('--keep-within', dest='within', type=str, metavar='WITHIN',
|
||||
subparser.add_argument('--keep-within', dest='within', type=interval, metavar='INTERVAL',
|
||||
help='keep all archives within this time interval')
|
||||
subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
|
||||
help='number of hourly archives to keep')
|
||||
|
|
|
@ -279,15 +279,32 @@ class Manifest:
|
|||
return archives
|
||||
|
||||
|
||||
def prune_within(archives, within):
|
||||
def interval(s):
|
||||
"""Convert a string representing a valid interval to a number of hours."""
|
||||
multiplier = {'H': 1, 'd': 24, 'w': 24 * 7, 'm': 24 * 31, 'y': 24 * 365}
|
||||
|
||||
if s.endswith(tuple(multiplier.keys())):
|
||||
number = s[:-1]
|
||||
suffix = s[-1]
|
||||
else:
|
||||
# range suffixes in ascending multiplier order
|
||||
ranges = [k for k, v in sorted(multiplier.items(), key=lambda t: t[1])]
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Unexpected interval time unit "%s": expected one of %r' % (s[-1], ranges))
|
||||
|
||||
try:
|
||||
hours = int(within[:-1]) * multiplier[within[-1]]
|
||||
except (KeyError, ValueError):
|
||||
# I don't like how this displays the original exception too:
|
||||
raise argparse.ArgumentTypeError('Unable to parse --keep-within option: "%s"' % within)
|
||||
hours = int(number) * multiplier[suffix]
|
||||
except ValueError:
|
||||
hours = -1
|
||||
|
||||
if hours <= 0:
|
||||
raise argparse.ArgumentTypeError('Number specified using --keep-within option must be positive')
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Unexpected interval number "%s": expected an integer greater than 0' % number)
|
||||
|
||||
return hours
|
||||
|
||||
|
||||
def prune_within(archives, hours):
|
||||
target = datetime.now(timezone.utc) - timedelta(seconds=hours * 3600)
|
||||
return [a for a in archives if a.ts > target]
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import hashlib
|
||||
from argparse import ArgumentTypeError
|
||||
from time import mktime, strptime
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from io import StringIO
|
||||
|
@ -11,7 +12,7 @@ import msgpack.fallback
|
|||
import time
|
||||
|
||||
from ..helpers import Location, format_file_size, format_timedelta, format_line, PlaceholderError, make_path_safe, \
|
||||
prune_within, prune_split, get_cache_dir, get_keys_dir, get_security_dir, Statistics, is_slow_msgpack, \
|
||||
interval, prune_within, prune_split, get_cache_dir, get_keys_dir, get_security_dir, Statistics, is_slow_msgpack, \
|
||||
yes, TRUISH, FALSISH, DEFAULTISH, \
|
||||
StableDict, int_to_bigint, bigint_to_int, parse_timestamp, CompressionSpec, ChunkerParams, \
|
||||
ProgressIndicatorPercent, ProgressIndicatorEndless, load_excludes, parse_pattern, \
|
||||
|
@ -664,16 +665,48 @@ class PruneSplitTestCase(BaseTestCase):
|
|||
dotest(test_archives, 0, [], [])
|
||||
|
||||
|
||||
class PruneWithinTestCase(BaseTestCase):
|
||||
class IntervalTestCase(BaseTestCase):
|
||||
def test_interval(self):
|
||||
self.assert_equal(interval('1H'), 1)
|
||||
self.assert_equal(interval('1d'), 24)
|
||||
self.assert_equal(interval('1w'), 168)
|
||||
self.assert_equal(interval('1m'), 744)
|
||||
self.assert_equal(interval('1y'), 8760)
|
||||
|
||||
def test(self):
|
||||
def test_interval_time_unit(self):
|
||||
with pytest.raises(ArgumentTypeError) as exc:
|
||||
interval('H')
|
||||
self.assert_equal(
|
||||
exc.value.args,
|
||||
('Unexpected interval number "": expected an integer greater than 0',))
|
||||
with pytest.raises(ArgumentTypeError) as exc:
|
||||
interval('-1d')
|
||||
self.assert_equal(
|
||||
exc.value.args,
|
||||
('Unexpected interval number "-1": expected an integer greater than 0',))
|
||||
with pytest.raises(ArgumentTypeError) as exc:
|
||||
interval('food')
|
||||
self.assert_equal(
|
||||
exc.value.args,
|
||||
('Unexpected interval number "foo": expected an integer greater than 0',))
|
||||
|
||||
def test_interval_number(self):
|
||||
with pytest.raises(ArgumentTypeError) as exc:
|
||||
interval('5')
|
||||
self.assert_equal(
|
||||
exc.value.args,
|
||||
("Unexpected interval time unit \"5\": expected one of ['H', 'd', 'w', 'm', 'y']",))
|
||||
|
||||
|
||||
class PruneWithinTestCase(BaseTestCase):
|
||||
def test_prune_within(self):
|
||||
|
||||
def subset(lst, indices):
|
||||
return {lst[i] for i in indices}
|
||||
|
||||
def dotest(test_archives, within, indices):
|
||||
for ta in test_archives, reversed(test_archives):
|
||||
self.assert_equal(set(prune_within(ta, within)),
|
||||
self.assert_equal(set(prune_within(ta, interval(within))),
|
||||
subset(test_archives, indices))
|
||||
|
||||
# 1 minute, 1.5 hours, 2.5 hours, 3.5 hours, 25 hours, 49 hours
|
||||
|
|
Loading…
Reference in New Issue