mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-20 21:27:32 +00:00
Merge remote-tracking branch 'jdchristensen/prune-within'
Conflicts: attic/archiver.py
This commit is contained in:
commit
5898b3b935
3 changed files with 62 additions and 9 deletions
|
@ -13,8 +13,8 @@
|
|||
from attic.key import key_creator
|
||||
from attic.helpers import Error, location_validator, format_time, \
|
||||
format_file_mode, ExcludePattern, exclude_path, adjust_patterns, to_localtime, \
|
||||
get_cache_dir, get_keys_dir, format_timedelta, prune_split, Manifest, remove_surrogates, \
|
||||
is_a_terminal, update_excludes
|
||||
get_cache_dir, get_keys_dir, format_timedelta, prune_within, prune_split, \
|
||||
Manifest, remove_surrogates, is_a_terminal, update_excludes
|
||||
from attic.remote import RepositoryServer, RemoteRepository
|
||||
|
||||
|
||||
|
@ -312,15 +312,17 @@ def do_prune(self, args):
|
|||
cache = Cache(repository, key, manifest)
|
||||
archives = list(sorted(Archive.list_archives(repository, key, manifest, cache),
|
||||
key=attrgetter('ts'), reverse=True))
|
||||
if args.hourly + args.daily + args.weekly + args.monthly + args.yearly == 0:
|
||||
self.print_error('At least one of the "hourly", "daily", "weekly", "monthly" or "yearly" '
|
||||
if args.hourly + args.daily + args.weekly + args.monthly + args.yearly == 0 and args.within is None:
|
||||
self.print_error('At least one of the "within", "hourly", "daily", "weekly", "monthly" or "yearly" '
|
||||
'settings must be specified')
|
||||
return 1
|
||||
if args.prefix:
|
||||
archives = [archive for archive in archives if archive.name.startswith(args.prefix)]
|
||||
keep = []
|
||||
if args.within:
|
||||
keep += prune_within(archives, args.within)
|
||||
if args.hourly:
|
||||
keep += prune_split(archives, '%Y-%m-%d %H', args.hourly)
|
||||
keep += prune_split(archives, '%Y-%m-%d %H', args.hourly, keep)
|
||||
if args.daily:
|
||||
keep += prune_split(archives, '%Y-%m-%d', args.daily, keep)
|
||||
if args.weekly:
|
||||
|
@ -496,7 +498,12 @@ def run(self, args=None):
|
|||
are applied from hourly to yearly, and backups selected by previous rules do
|
||||
not count towards those of later rules. Dates and times are interpreted in
|
||||
the local timezone, and weeks go from Monday to Sunday. Specifying a
|
||||
negative number of archives to keep means that there is no limit. If a
|
||||
negative number of archives to keep means that there is no limit.
|
||||
The "--within" option takes an argument of the form "<int><char>",
|
||||
where char is "H", "d", "w", "m", "y". For example, "--within 2d" means
|
||||
to keep all archives that were created within the past 48 hours.
|
||||
"1m" is taken to mean "31d". The archives kept with this option do not
|
||||
count towards the totals specified by any other options. If a
|
||||
prefix is set with -p, then only archives that start with the prefix are
|
||||
considered for deletion and only those archives count towards the totals
|
||||
specified by the rules.'''
|
||||
|
@ -505,6 +512,8 @@ def run(self, args=None):
|
|||
description=self.do_prune.__doc__,
|
||||
epilog=prune_epilog)
|
||||
subparser.set_defaults(func=self.do_prune)
|
||||
subparser.add_argument('--within', dest='within', type=str, metavar='WITHIN',
|
||||
help='keep all archives within this time interval')
|
||||
subparser.add_argument('-H', '--hourly', dest='hourly', type=int, default=0,
|
||||
help='number of hourly archives to keep')
|
||||
subparser.add_argument('-d', '--daily', dest='daily', type=int, default=0,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import stat
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from fnmatch import translate
|
||||
from operator import attrgetter
|
||||
import fcntl
|
||||
|
@ -91,6 +91,19 @@ def write(self):
|
|||
self.repository.put(self.MANIFEST_ID, self.key.encrypt(data))
|
||||
|
||||
|
||||
def prune_within(archives, within):
|
||||
multiplier = {'H': 1, 'd': 24, 'w': 24*7, 'm': 24*31, 'y': 24*365}
|
||||
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 --within option: "%s"' % within)
|
||||
if hours <= 0:
|
||||
raise argparse.ArgumentTypeError('Number specified using --within option must be positive')
|
||||
target = datetime.now(timezone.utc) - timedelta(seconds=hours*60*60)
|
||||
return [a for a in archives if a.ts > target]
|
||||
|
||||
|
||||
def prune_split(archives, pattern, n, skip=[]):
|
||||
last = None
|
||||
keep = []
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from time import mktime, strptime
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_split, to_localtime
|
||||
from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, to_localtime
|
||||
from attic.testsuite import AtticTestCase
|
||||
|
||||
|
||||
|
@ -145,3 +145,34 @@ def dotest(test_archives, n, skip, indices):
|
|||
dotest(test_archives, 3, [test_archives[5]], [6, 2, 0])
|
||||
dotest(test_archives, 3, [test_archives[4]], [6, 5, 2])
|
||||
dotest(test_archives, 0, [], [])
|
||||
|
||||
|
||||
class PruneWithinTestCase(AtticTestCase):
|
||||
|
||||
def test(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)),
|
||||
subset(test_archives, indices))
|
||||
|
||||
# 1 minute, 1.5 hours, 2.5 hours, 3.5 hours, 25 hours, 49 hours
|
||||
test_offsets = [60, 90*60, 150*60, 210*60, 25*60*60, 49*60*60]
|
||||
now = datetime.now(timezone.utc)
|
||||
test_dates = [now - timedelta(seconds=s) for s in test_offsets]
|
||||
test_archives = [MockArchive(date) for date in test_dates]
|
||||
|
||||
dotest(test_archives, '1H', [0])
|
||||
dotest(test_archives, '2H', [0, 1])
|
||||
dotest(test_archives, '3H', [0, 1, 2])
|
||||
dotest(test_archives, '24H', [0, 1, 2, 3])
|
||||
dotest(test_archives, '26H', [0, 1, 2, 3, 4])
|
||||
dotest(test_archives, '2d', [0, 1, 2, 3, 4])
|
||||
dotest(test_archives, '50H', [0, 1, 2, 3, 4, 5])
|
||||
dotest(test_archives, '3d', [0, 1, 2, 3, 4, 5])
|
||||
dotest(test_archives, '1w', [0, 1, 2, 3, 4, 5])
|
||||
dotest(test_archives, '1m', [0, 1, 2, 3, 4, 5])
|
||||
dotest(test_archives, '1y', [0, 1, 2, 3, 4, 5])
|
||||
|
|
Loading…
Reference in a new issue