Repository: borgbackup/borg

Issue:
Error pruning when there are no archives
<!--
Thank you for reporting an issue.

*IMPORTANT* -  *before* creating a new issue please look around:
 - Borgbackup documentation: http://borgbackup.readthedocs.io/en/stable/index.html
 - FAQ: https://borgbackup.readthedocs.io/en/stable/faq.html
 and
 - open issues in Github tracker: https://github.com/borgbackup/borg/issues
  
If you cannot find a similar problem, then create a new issue.

Please fill in as much of the template as possible.
-->

## Have you checked borgbackup docs, FAQ, and open Github issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

Bug

## System information. For client/server mode post info for both machines.

#### Your borg version (borg -V).

Borg.bin 1.2.0b4.dev85+gbc700c008a55

## Describe the problem you're observing.

#### Can you reproduce the problem? If so, describe how. If not, describe troubleshooting steps you took before opening the issue.

With borg 1.2:

```bash
export BORG_PASSPHRASE=asdf
export BORG_REPO=/tmp/test-repo
rm -r /tmp/test-repo
/opt/borg/Borg.bin init --encryption=repokey
/opt/borg/Borg.bin prune --verbose --progress --stats --keep-daily=14
```

#### Include any warning/errors/backtraces from the system logs

```
Local Exception
Traceback (most recent call last):
  File "borg/archiver.py", line 4984, in main
  File "borg/archiver.py", line 4915, in run
  File "borg/archiver.py", line 183, in wrapper
  File "borg/archiver.py", line 1512, in do_prune
  File "borg/helpers/misc.py", line 62, in prune_split
UnboundLocalError: local variable 'a' referenced before assignment
```

This comes from #5332
ping @amikula


Discussion:
Yeah, good catch!

@amikula i had a quick look.

Guess using `a` outside the for-block is slightly dirty in general and as seen above, it does not work if the iteration is over an empty sequence. Didn't catch this back then when reviewing this.

Can you fix this?
Oops. Yeah, I'll take care of it. Most likely I can have a fix sometime
this weekend.

On Mon, Oct 18, 2021, 08:18 TW ***@***.***> wrote:

> Yeah, good catch!
>
> @amikula <https://github.com/amikula> i had a quick look.
>
> Guess using a outside the for-block is slightly dirty in general and as
> seen above, it does not work if the iteration is over an empty sequence.
> Didn't catch this back then when reviewing this.
>
> Can you fix this?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/borgbackup/borg/issues/6015#issuecomment-945884495>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAACB6EG5MNTGKUUF5RYYMDUHQ3DZANCNFSM5GFMUO2Q>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>


Resolving patch:
diff --git a/src/borg/helpers/misc.py b/src/borg/helpers/misc.py
--- a/src/borg/helpers/misc.py
+++ b/src/borg/helpers/misc.py
@@ -49,6 +49,8 @@ def prune_split(archives, rule, n, kept_because=None):
         kept_because = {}
     if n == 0:
         return keep
+
+    a = None
     for a in sorted(archives, key=attrgetter('ts'), reverse=True):
         period = to_localtime(a.ts).strftime(pattern)
         if period != last:
@@ -59,7 +61,7 @@ def prune_split(archives, rule, n, kept_because=None):
                 if len(keep) == n:
                     break
     # Keep oldest archive if we didn't reach the target retention count
-    if len(keep) < n and a.id not in kept_because:
+    if a is not None and len(keep) < n and a.id not in kept_because:
         keep.append(a)
         kept_because[a.id] = (rule+"[oldest]", len(keep))
     return keep
diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py
--- a/src/borg/testsuite/helpers.py
+++ b/src/borg/testsuite/helpers.py
@@ -439,6 +439,19 @@ def subset(lst, ids):
     assert kept_because[4][0] == "yearly"
 
 
+def test_prune_split_no_archives():
+    def subset(lst, ids):
+        return {i for i in lst if i.id in ids}
+
+    archives = []
+
+    kept_because = {}
+    keep = prune_split(archives, "yearly", 3, kept_because)
+
+    assert keep == []
+    assert kept_because == {}
+
+
 class IntervalTestCase(BaseTestCase):
     def test_interval(self):
         self.assert_equal(interval('1H'), 1)


Test patch: