Repository: borgbackup/borg

Issue:
prune - ignore .checkpoint archives
currently, pruning works just based on the archive timestamp.

it does not consider the archive name except for `--prefix`, so it does not specially treat the .checkpoint archives.

i think this can lead to unwanted behaviour in such a case:

```
backup-2015-12-31-21:00:00
backup-2015-12-31-23:00:00.checkpoint
```

Obviously, archive names are created here so that they include the datetime - a popular way to avoid duplicate names. The 21:00 backup worked, but it seems the 23:00 backup was interrupted.

As pruning always keeps the last backup of a day / week / month / year, it would keep the interrupted backup and delete the complete backup.

fix: ignore checkpoints in pruning.

branch: 1.0-maint (avoid data loss)



Discussion:

Resolving patch:
diff --git a/borg/archiver.py b/borg/archiver.py
--- a/borg/archiver.py
+++ b/borg/archiver.py
@@ -7,6 +7,7 @@
 import inspect
 import io
 import os
+import re
 import shlex
 import signal
 import stat
@@ -574,6 +575,10 @@ def do_prune(self, args, repository, manifest, key):
         archives = manifest.list_archive_infos(sort_by='ts', reverse=True)  # just a ArchiveInfo list
         if args.prefix:
             archives = [archive for archive in archives if archive.name.startswith(args.prefix)]
+        # 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.
+        is_checkpoint = re.compile(r'\.checkpoint(\.\d+)?$').search
+        archives = [archive for archive in archives if not is_checkpoint(archive.name)]
         keep = []
         if args.within:
             keep += prune_within(archives, args.within)
@@ -988,6 +993,9 @@ def build_parser(self, args=None, prog=None):
         traversing all paths specified. The archive will consume almost no disk space for
         files or parts of files that have already been stored in other archives.
 
+        The archive name needs to be unique. It must not end in '.checkpoint' or
+        '.checkpoint.N' (with N being a number), because these names are used for
+        checkpoints and treated in special ways.
 
         To speed up pulling backups over sshfs and similar network file systems which do
         not provide correct inode information the --ignore-inode flag can be used. This
diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py
--- a/borg/testsuite/archiver.py
+++ b/borg/testsuite/archiver.py
@@ -863,15 +863,22 @@ def test_prune_repository(self):
         self.cmd('init', self.repository_location)
         self.cmd('create', self.repository_location + '::test1', src_dir)
         self.cmd('create', self.repository_location + '::test2', src_dir)
+        # these are not really a checkpoints, but they look like some:
+        self.cmd('create', self.repository_location + '::test3.checkpoint', src_dir)
+        self.cmd('create', self.repository_location + '::test3.checkpoint.1', src_dir)
         output = self.cmd('prune', '-v', '--list', '--dry-run', self.repository_location, '--keep-daily=2')
-        self.assert_in('Keeping archive: test2', output)
         self.assert_in('Would prune:     test1', output)
+        # must keep the latest non-checkpoint archive:
+        self.assert_in('Keeping archive: test2', output)
         output = self.cmd('list', self.repository_location)
         self.assert_in('test1', output)
         self.assert_in('test2', output)
+        self.assert_in('test3.checkpoint', output)
+        self.assert_in('test3.checkpoint.1', output)
         self.cmd('prune', self.repository_location, '--keep-daily=2')
         output = self.cmd('list', self.repository_location)
         self.assert_not_in('test1', output)
+        # the latest non-checkpoint archive must be still there:
         self.assert_in('test2', output)
 
     def test_prune_repository_save_space(self):


Test patch: