mirror of
https://github.com/borgbackup/borg.git
synced 2025-03-15 00:21:56 +00:00
move prune command to archiver.prune
This commit is contained in:
parent
94e6477e51
commit
ea03562b11
3 changed files with 235 additions and 209 deletions
|
@ -123,6 +123,7 @@ per_file_ignores =
|
||||||
src/borg/archiver/debug.py:F405
|
src/borg/archiver/debug.py:F405
|
||||||
src/borg/archiver/help.py:E501,F405
|
src/borg/archiver/help.py:E501,F405
|
||||||
src/borg/archiver/keys.py:F405
|
src/borg/archiver/keys.py:F405
|
||||||
|
src/borg/archiver/prune.py:F405
|
||||||
src/borg/archiver/tar.py:F405
|
src/borg/archiver/tar.py:F405
|
||||||
src/borg/cache.py:E127,E128,E402,E501,E722,W504
|
src/borg/cache.py:E127,E128,E402,E501,E722,W504
|
||||||
src/borg/fuse.py:E402,E501,E722,W504
|
src/borg/fuse.py:E402,E501,E722,W504
|
||||||
|
|
|
@ -12,7 +12,6 @@ try:
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shlex
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
import stat
|
import stat
|
||||||
|
@ -43,7 +42,6 @@ try:
|
||||||
from ..helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
|
from ..helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
|
||||||
from ..helpers import format_timedelta, format_file_size, parse_file_size, format_archive
|
from ..helpers import format_timedelta, format_file_size, parse_file_size, format_archive
|
||||||
from ..helpers import remove_surrogates, bin_to_hex, eval_escapes
|
from ..helpers import remove_surrogates, bin_to_hex, eval_escapes
|
||||||
from ..helpers import interval, prune_within, prune_split, PRUNING_PATTERNS
|
|
||||||
from ..helpers import timestamp
|
from ..helpers import timestamp
|
||||||
from ..helpers import get_cache_dir, os_stat
|
from ..helpers import get_cache_dir, os_stat
|
||||||
from ..helpers import Manifest
|
from ..helpers import Manifest
|
||||||
|
@ -105,11 +103,14 @@ from .debug import DebugMixIn
|
||||||
from .help import HelpMixIn
|
from .help import HelpMixIn
|
||||||
from .keys import KeysMixIn
|
from .keys import KeysMixIn
|
||||||
from .locks import LocksMixIn
|
from .locks import LocksMixIn
|
||||||
|
from .prune import PruneMixIn
|
||||||
from .tar import TarMixIn
|
from .tar import TarMixIn
|
||||||
from .transfer import TransferMixIn
|
from .transfer import TransferMixIn
|
||||||
|
|
||||||
|
|
||||||
class Archiver(ConfigMixIn, DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, LocksMixIn, HelpMixIn, TransferMixIn):
|
class Archiver(
|
||||||
|
ConfigMixIn, DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, LocksMixIn, PruneMixIn, HelpMixIn, TransferMixIn
|
||||||
|
):
|
||||||
def __init__(self, lock_wait=None, prog=None):
|
def __init__(self, lock_wait=None, prog=None):
|
||||||
self.exit_code = EXIT_SUCCESS
|
self.exit_code = EXIT_SUCCESS
|
||||||
self.lock_wait = lock_wait
|
self.lock_wait = lock_wait
|
||||||
|
@ -1164,93 +1165,6 @@ class Archiver(ConfigMixIn, DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, Loc
|
||||||
json_print(basic_json_data(manifest, cache=cache, extra={"archives": output_data}))
|
json_print(basic_json_data(manifest, cache=cache, extra={"archives": output_data}))
|
||||||
return self.exit_code
|
return self.exit_code
|
||||||
|
|
||||||
@with_repository(exclusive=True, compatibility=(Manifest.Operation.DELETE,))
|
|
||||||
def do_prune(self, args, repository, manifest, key):
|
|
||||||
"""Prune repository archives according to specified rules"""
|
|
||||||
if not any(
|
|
||||||
(args.secondly, args.minutely, args.hourly, args.daily, args.weekly, args.monthly, args.yearly, args.within)
|
|
||||||
):
|
|
||||||
self.print_error(
|
|
||||||
'At least one of the "keep-within", "keep-last", '
|
|
||||||
'"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
|
|
||||||
'"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.'
|
|
||||||
)
|
|
||||||
return self.exit_code
|
|
||||||
checkpoint_re = r"\.checkpoint(\.\d+)?"
|
|
||||||
archives_checkpoints = manifest.archives.list(
|
|
||||||
glob=args.glob_archives,
|
|
||||||
consider_checkpoints=True,
|
|
||||||
match_end=r"(%s)?\Z" % checkpoint_re,
|
|
||||||
sort_by=["ts"],
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
is_checkpoint = re.compile(r"(%s)\Z" % checkpoint_re).search
|
|
||||||
checkpoints = [arch for arch in archives_checkpoints if is_checkpoint(arch.name)]
|
|
||||||
# keep the latest checkpoint, if there is no later non-checkpoint archive
|
|
||||||
if archives_checkpoints and checkpoints and archives_checkpoints[0] is checkpoints[0]:
|
|
||||||
keep_checkpoints = checkpoints[:1]
|
|
||||||
else:
|
|
||||||
keep_checkpoints = []
|
|
||||||
checkpoints = set(checkpoints)
|
|
||||||
# ignore all checkpoint archives to avoid keeping one (which is an incomplete backup)
|
|
||||||
# that is newer than a successfully completed backup - and killing the successful backup.
|
|
||||||
archives = [arch for arch in archives_checkpoints if arch not in checkpoints]
|
|
||||||
keep = []
|
|
||||||
# collect the rule responsible for the keeping of each archive in this dict
|
|
||||||
# keys are archive ids, values are a tuple
|
|
||||||
# (<rulename>, <how many archives were kept by this rule so far >)
|
|
||||||
kept_because = {}
|
|
||||||
|
|
||||||
# find archives which need to be kept because of the keep-within rule
|
|
||||||
if args.within:
|
|
||||||
keep += prune_within(archives, args.within, kept_because)
|
|
||||||
|
|
||||||
# find archives which need to be kept because of the various time period rules
|
|
||||||
for rule in PRUNING_PATTERNS.keys():
|
|
||||||
num = getattr(args, rule, None)
|
|
||||||
if num is not None:
|
|
||||||
keep += prune_split(archives, rule, num, kept_because)
|
|
||||||
|
|
||||||
to_delete = (set(archives) | checkpoints) - (set(keep) | set(keep_checkpoints))
|
|
||||||
stats = Statistics(iec=args.iec)
|
|
||||||
with Cache(repository, key, manifest, lock_wait=self.lock_wait, iec=args.iec) as cache:
|
|
||||||
list_logger = logging.getLogger("borg.output.list")
|
|
||||||
# set up counters for the progress display
|
|
||||||
to_delete_len = len(to_delete)
|
|
||||||
archives_deleted = 0
|
|
||||||
pi = ProgressIndicatorPercent(total=len(to_delete), msg="Pruning archives %3.0f%%", msgid="prune")
|
|
||||||
for archive in archives_checkpoints:
|
|
||||||
if archive in to_delete:
|
|
||||||
pi.show()
|
|
||||||
if args.dry_run:
|
|
||||||
log_message = "Would prune:"
|
|
||||||
else:
|
|
||||||
archives_deleted += 1
|
|
||||||
log_message = "Pruning archive (%d/%d):" % (archives_deleted, to_delete_len)
|
|
||||||
archive = Archive(
|
|
||||||
repository, key, manifest, archive.name, cache, consider_part_files=args.consider_part_files
|
|
||||||
)
|
|
||||||
archive.delete(stats, forced=args.forced)
|
|
||||||
else:
|
|
||||||
if is_checkpoint(archive.name):
|
|
||||||
log_message = "Keeping checkpoint archive:"
|
|
||||||
else:
|
|
||||||
log_message = "Keeping archive (rule: {rule} #{num}):".format(
|
|
||||||
rule=kept_because[archive.id][0], num=kept_because[archive.id][1]
|
|
||||||
)
|
|
||||||
if args.output_list:
|
|
||||||
list_logger.info(
|
|
||||||
"{message:<40} {archive}".format(message=log_message, archive=format_archive(archive))
|
|
||||||
)
|
|
||||||
pi.finish()
|
|
||||||
if to_delete and not args.dry_run:
|
|
||||||
manifest.write()
|
|
||||||
repository.commit(compact=False, save_space=args.save_space)
|
|
||||||
cache.commit()
|
|
||||||
if args.stats:
|
|
||||||
log_multi(str(stats), logger=logging.getLogger("borg.output.stats"))
|
|
||||||
return self.exit_code
|
|
||||||
|
|
||||||
@with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.CHECK,))
|
@with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.CHECK,))
|
||||||
def do_recreate(self, args, repository, manifest, key, cache):
|
def do_recreate(self, args, repository, manifest, key, cache):
|
||||||
"""Re-create archives"""
|
"""Re-create archives"""
|
||||||
|
@ -1714,6 +1628,7 @@ class Archiver(ConfigMixIn, DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, Loc
|
||||||
|
|
||||||
self.build_parser_benchmarks(subparsers, common_parser, mid_common_parser)
|
self.build_parser_benchmarks(subparsers, common_parser, mid_common_parser)
|
||||||
self.build_parser_locks(subparsers, common_parser, mid_common_parser)
|
self.build_parser_locks(subparsers, common_parser, mid_common_parser)
|
||||||
|
self.build_parser_prune(subparsers, common_parser, mid_common_parser)
|
||||||
|
|
||||||
# borg check
|
# borg check
|
||||||
check_epilog = process_epilog(
|
check_epilog = process_epilog(
|
||||||
|
@ -2888,125 +2803,6 @@ class Archiver(ConfigMixIn, DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, Loc
|
||||||
)
|
)
|
||||||
define_borg_mount(subparser)
|
define_borg_mount(subparser)
|
||||||
|
|
||||||
# borg prune
|
|
||||||
prune_epilog = process_epilog(
|
|
||||||
"""
|
|
||||||
The prune command prunes a repository by deleting all archives not matching
|
|
||||||
any of the specified retention options.
|
|
||||||
|
|
||||||
Important: Repository disk space is **not** freed until you run ``borg compact``.
|
|
||||||
|
|
||||||
This command is normally used by automated backup scripts wanting to keep a
|
|
||||||
certain number of historic backups. This retention policy is commonly referred to as
|
|
||||||
`GFS <https://en.wikipedia.org/wiki/Backup_rotation_scheme#Grandfather-father-son>`_
|
|
||||||
(Grandfather-father-son) backup rotation scheme.
|
|
||||||
|
|
||||||
Also, prune automatically removes checkpoint archives (incomplete archives left
|
|
||||||
behind by interrupted backup runs) except if the checkpoint is the latest
|
|
||||||
archive (and thus still needed). Checkpoint archives are not considered when
|
|
||||||
comparing archive counts against the retention limits (``--keep-X``).
|
|
||||||
|
|
||||||
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.
|
|
||||||
Otherwise, *all* archives in the repository are candidates for deletion!
|
|
||||||
There is no automatic distinction between archives representing different
|
|
||||||
contents. These need to be distinguished by specifying matching prefixes.
|
|
||||||
|
|
||||||
If you have multiple sequences of archives with different data sets (e.g.
|
|
||||||
from different machines) in one shared repository, use one prune call per
|
|
||||||
data set that matches only the respective archives using the -P option.
|
|
||||||
|
|
||||||
The ``--keep-within`` option takes an argument of the form "<int><char>",
|
|
||||||
where char is "H", "d", "w", "m", "y". For example, ``--keep-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.
|
|
||||||
|
|
||||||
A good procedure is to thin out more and more the older your backups get.
|
|
||||||
As an example, ``--keep-daily 7`` means to keep the latest backup on each day,
|
|
||||||
up to 7 most recent days with backups (days without backups do not count).
|
|
||||||
The rules are applied from secondly to yearly, and backups selected by previous
|
|
||||||
rules do not count towards those of later rules. The time that each backup
|
|
||||||
starts is used for pruning purposes. 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. As of borg
|
|
||||||
1.2.0, borg will retain the oldest archive if any of the secondly, minutely,
|
|
||||||
hourly, daily, weekly, monthly, or yearly rules was not otherwise able to meet
|
|
||||||
its retention target. This enables the first chronological archive to continue
|
|
||||||
aging until it is replaced by a newer archive that meets the retention criteria.
|
|
||||||
|
|
||||||
The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will
|
|
||||||
keep the last N archives under the assumption that you do not create more than one
|
|
||||||
backup archive in the same second).
|
|
||||||
|
|
||||||
When using ``--stats``, you will get some statistics about how much data was
|
|
||||||
deleted - the "Deleted data" deduplicated size there is most interesting as
|
|
||||||
that is how much your repository will shrink.
|
|
||||||
Please note that the "All archives" stats refer to the state after pruning.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
subparser = subparsers.add_parser(
|
|
||||||
"prune",
|
|
||||||
parents=[common_parser],
|
|
||||||
add_help=False,
|
|
||||||
description=self.do_prune.__doc__,
|
|
||||||
epilog=prune_epilog,
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
help="prune archives",
|
|
||||||
)
|
|
||||||
subparser.set_defaults(func=self.do_prune)
|
|
||||||
subparser.add_argument("-n", "--dry-run", dest="dry_run", action="store_true", help="do not change repository")
|
|
||||||
subparser.add_argument(
|
|
||||||
"--force",
|
|
||||||
dest="forced",
|
|
||||||
action="store_true",
|
|
||||||
help="force pruning of corrupted archives, " "use ``--force --force`` in case ``--force`` does not work.",
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-s", "--stats", dest="stats", action="store_true", help="print statistics for the deleted archive"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--list", dest="output_list", action="store_true", help="output verbose list of archives it keeps/prunes"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--keep-within",
|
|
||||||
metavar="INTERVAL",
|
|
||||||
dest="within",
|
|
||||||
type=interval,
|
|
||||||
help="keep all archives within this time interval",
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--keep-last",
|
|
||||||
"--keep-secondly",
|
|
||||||
dest="secondly",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="number of secondly archives to keep",
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--keep-minutely", dest="minutely", type=int, default=0, help="number of minutely archives to keep"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-H", "--keep-hourly", dest="hourly", type=int, default=0, help="number of hourly archives to keep"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-d", "--keep-daily", dest="daily", type=int, default=0, help="number of daily archives to keep"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-w", "--keep-weekly", dest="weekly", type=int, default=0, help="number of weekly archives to keep"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-m", "--keep-monthly", dest="monthly", type=int, default=0, help="number of monthly archives to keep"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"-y", "--keep-yearly", dest="yearly", type=int, default=0, help="number of yearly archives to keep"
|
|
||||||
)
|
|
||||||
define_archive_filters_group(subparser, sort_by=False, first_last=False)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--save-space", dest="save_space", action="store_true", help="work slower, but using less space"
|
|
||||||
)
|
|
||||||
|
|
||||||
# borg recreate
|
# borg recreate
|
||||||
recreate_epilog = process_epilog(
|
recreate_epilog = process_epilog(
|
||||||
"""
|
"""
|
||||||
|
|
229
src/borg/archiver/prune.py
Normal file
229
src/borg/archiver/prune.py
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import with_repository
|
||||||
|
from ..archive import Archive, Statistics
|
||||||
|
from ..cache import Cache
|
||||||
|
from ..constants import * # NOQA
|
||||||
|
from ..helpers import format_archive
|
||||||
|
from ..helpers import interval, prune_within, prune_split, PRUNING_PATTERNS
|
||||||
|
from ..helpers import Manifest
|
||||||
|
from ..helpers import log_multi
|
||||||
|
from ..helpers import ProgressIndicatorPercent
|
||||||
|
|
||||||
|
from ..logger import create_logger
|
||||||
|
|
||||||
|
logger = create_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class PruneMixIn:
|
||||||
|
@with_repository(exclusive=True, compatibility=(Manifest.Operation.DELETE,))
|
||||||
|
def do_prune(self, args, repository, manifest, key):
|
||||||
|
"""Prune repository archives according to specified rules"""
|
||||||
|
if not any(
|
||||||
|
(args.secondly, args.minutely, args.hourly, args.daily, args.weekly, args.monthly, args.yearly, args.within)
|
||||||
|
):
|
||||||
|
self.print_error(
|
||||||
|
'At least one of the "keep-within", "keep-last", '
|
||||||
|
'"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
|
||||||
|
'"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.'
|
||||||
|
)
|
||||||
|
return self.exit_code
|
||||||
|
checkpoint_re = r"\.checkpoint(\.\d+)?"
|
||||||
|
archives_checkpoints = manifest.archives.list(
|
||||||
|
glob=args.glob_archives,
|
||||||
|
consider_checkpoints=True,
|
||||||
|
match_end=r"(%s)?\Z" % checkpoint_re,
|
||||||
|
sort_by=["ts"],
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
is_checkpoint = re.compile(r"(%s)\Z" % checkpoint_re).search
|
||||||
|
checkpoints = [arch for arch in archives_checkpoints if is_checkpoint(arch.name)]
|
||||||
|
# keep the latest checkpoint, if there is no later non-checkpoint archive
|
||||||
|
if archives_checkpoints and checkpoints and archives_checkpoints[0] is checkpoints[0]:
|
||||||
|
keep_checkpoints = checkpoints[:1]
|
||||||
|
else:
|
||||||
|
keep_checkpoints = []
|
||||||
|
checkpoints = set(checkpoints)
|
||||||
|
# ignore all checkpoint archives to avoid keeping one (which is an incomplete backup)
|
||||||
|
# that is newer than a successfully completed backup - and killing the successful backup.
|
||||||
|
archives = [arch for arch in archives_checkpoints if arch not in checkpoints]
|
||||||
|
keep = []
|
||||||
|
# collect the rule responsible for the keeping of each archive in this dict
|
||||||
|
# keys are archive ids, values are a tuple
|
||||||
|
# (<rulename>, <how many archives were kept by this rule so far >)
|
||||||
|
kept_because = {}
|
||||||
|
|
||||||
|
# find archives which need to be kept because of the keep-within rule
|
||||||
|
if args.within:
|
||||||
|
keep += prune_within(archives, args.within, kept_because)
|
||||||
|
|
||||||
|
# find archives which need to be kept because of the various time period rules
|
||||||
|
for rule in PRUNING_PATTERNS.keys():
|
||||||
|
num = getattr(args, rule, None)
|
||||||
|
if num is not None:
|
||||||
|
keep += prune_split(archives, rule, num, kept_because)
|
||||||
|
|
||||||
|
to_delete = (set(archives) | checkpoints) - (set(keep) | set(keep_checkpoints))
|
||||||
|
stats = Statistics(iec=args.iec)
|
||||||
|
with Cache(repository, key, manifest, lock_wait=self.lock_wait, iec=args.iec) as cache:
|
||||||
|
list_logger = logging.getLogger("borg.output.list")
|
||||||
|
# set up counters for the progress display
|
||||||
|
to_delete_len = len(to_delete)
|
||||||
|
archives_deleted = 0
|
||||||
|
pi = ProgressIndicatorPercent(total=len(to_delete), msg="Pruning archives %3.0f%%", msgid="prune")
|
||||||
|
for archive in archives_checkpoints:
|
||||||
|
if archive in to_delete:
|
||||||
|
pi.show()
|
||||||
|
if args.dry_run:
|
||||||
|
log_message = "Would prune:"
|
||||||
|
else:
|
||||||
|
archives_deleted += 1
|
||||||
|
log_message = "Pruning archive (%d/%d):" % (archives_deleted, to_delete_len)
|
||||||
|
archive = Archive(
|
||||||
|
repository, key, manifest, archive.name, cache, consider_part_files=args.consider_part_files
|
||||||
|
)
|
||||||
|
archive.delete(stats, forced=args.forced)
|
||||||
|
else:
|
||||||
|
if is_checkpoint(archive.name):
|
||||||
|
log_message = "Keeping checkpoint archive:"
|
||||||
|
else:
|
||||||
|
log_message = "Keeping archive (rule: {rule} #{num}):".format(
|
||||||
|
rule=kept_because[archive.id][0], num=kept_because[archive.id][1]
|
||||||
|
)
|
||||||
|
if args.output_list:
|
||||||
|
list_logger.info(
|
||||||
|
"{message:<40} {archive}".format(message=log_message, archive=format_archive(archive))
|
||||||
|
)
|
||||||
|
pi.finish()
|
||||||
|
if to_delete and not args.dry_run:
|
||||||
|
manifest.write()
|
||||||
|
repository.commit(compact=False, save_space=args.save_space)
|
||||||
|
cache.commit()
|
||||||
|
if args.stats:
|
||||||
|
log_multi(str(stats), logger=logging.getLogger("borg.output.stats"))
|
||||||
|
return self.exit_code
|
||||||
|
|
||||||
|
def build_parser_prune(self, subparsers, common_parser, mid_common_parser):
|
||||||
|
|
||||||
|
from .common import process_epilog
|
||||||
|
from .common import define_archive_filters_group
|
||||||
|
|
||||||
|
prune_epilog = process_epilog(
|
||||||
|
"""
|
||||||
|
The prune command prunes a repository by deleting all archives not matching
|
||||||
|
any of the specified retention options.
|
||||||
|
|
||||||
|
Important: Repository disk space is **not** freed until you run ``borg compact``.
|
||||||
|
|
||||||
|
This command is normally used by automated backup scripts wanting to keep a
|
||||||
|
certain number of historic backups. This retention policy is commonly referred to as
|
||||||
|
`GFS <https://en.wikipedia.org/wiki/Backup_rotation_scheme#Grandfather-father-son>`_
|
||||||
|
(Grandfather-father-son) backup rotation scheme.
|
||||||
|
|
||||||
|
Also, prune automatically removes checkpoint archives (incomplete archives left
|
||||||
|
behind by interrupted backup runs) except if the checkpoint is the latest
|
||||||
|
archive (and thus still needed). Checkpoint archives are not considered when
|
||||||
|
comparing archive counts against the retention limits (``--keep-X``).
|
||||||
|
|
||||||
|
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.
|
||||||
|
Otherwise, *all* archives in the repository are candidates for deletion!
|
||||||
|
There is no automatic distinction between archives representing different
|
||||||
|
contents. These need to be distinguished by specifying matching prefixes.
|
||||||
|
|
||||||
|
If you have multiple sequences of archives with different data sets (e.g.
|
||||||
|
from different machines) in one shared repository, use one prune call per
|
||||||
|
data set that matches only the respective archives using the -P option.
|
||||||
|
|
||||||
|
The ``--keep-within`` option takes an argument of the form "<int><char>",
|
||||||
|
where char is "H", "d", "w", "m", "y". For example, ``--keep-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.
|
||||||
|
|
||||||
|
A good procedure is to thin out more and more the older your backups get.
|
||||||
|
As an example, ``--keep-daily 7`` means to keep the latest backup on each day,
|
||||||
|
up to 7 most recent days with backups (days without backups do not count).
|
||||||
|
The rules are applied from secondly to yearly, and backups selected by previous
|
||||||
|
rules do not count towards those of later rules. The time that each backup
|
||||||
|
starts is used for pruning purposes. 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. As of borg
|
||||||
|
1.2.0, borg will retain the oldest archive if any of the secondly, minutely,
|
||||||
|
hourly, daily, weekly, monthly, or yearly rules was not otherwise able to meet
|
||||||
|
its retention target. This enables the first chronological archive to continue
|
||||||
|
aging until it is replaced by a newer archive that meets the retention criteria.
|
||||||
|
|
||||||
|
The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will
|
||||||
|
keep the last N archives under the assumption that you do not create more than one
|
||||||
|
backup archive in the same second).
|
||||||
|
|
||||||
|
When using ``--stats``, you will get some statistics about how much data was
|
||||||
|
deleted - the "Deleted data" deduplicated size there is most interesting as
|
||||||
|
that is how much your repository will shrink.
|
||||||
|
Please note that the "All archives" stats refer to the state after pruning.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
subparser = subparsers.add_parser(
|
||||||
|
"prune",
|
||||||
|
parents=[common_parser],
|
||||||
|
add_help=False,
|
||||||
|
description=self.do_prune.__doc__,
|
||||||
|
epilog=prune_epilog,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
help="prune archives",
|
||||||
|
)
|
||||||
|
subparser.set_defaults(func=self.do_prune)
|
||||||
|
subparser.add_argument("-n", "--dry-run", dest="dry_run", action="store_true", help="do not change repository")
|
||||||
|
subparser.add_argument(
|
||||||
|
"--force",
|
||||||
|
dest="forced",
|
||||||
|
action="store_true",
|
||||||
|
help="force pruning of corrupted archives, " "use ``--force --force`` in case ``--force`` does not work.",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-s", "--stats", dest="stats", action="store_true", help="print statistics for the deleted archive"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--list", dest="output_list", action="store_true", help="output verbose list of archives it keeps/prunes"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--keep-within",
|
||||||
|
metavar="INTERVAL",
|
||||||
|
dest="within",
|
||||||
|
type=interval,
|
||||||
|
help="keep all archives within this time interval",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--keep-last",
|
||||||
|
"--keep-secondly",
|
||||||
|
dest="secondly",
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
help="number of secondly archives to keep",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--keep-minutely", dest="minutely", type=int, default=0, help="number of minutely archives to keep"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-H", "--keep-hourly", dest="hourly", type=int, default=0, help="number of hourly archives to keep"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-d", "--keep-daily", dest="daily", type=int, default=0, help="number of daily archives to keep"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-w", "--keep-weekly", dest="weekly", type=int, default=0, help="number of weekly archives to keep"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-m", "--keep-monthly", dest="monthly", type=int, default=0, help="number of monthly archives to keep"
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"-y", "--keep-yearly", dest="yearly", type=int, default=0, help="number of yearly archives to keep"
|
||||||
|
)
|
||||||
|
define_archive_filters_group(subparser, sort_by=False, first_last=False)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--save-space", dest="save_space", action="store_true", help="work slower, but using less space"
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue