Repository: borgbackup/borg

Issue:
Borg2: ValueError on date-based matching
## 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 2.0.0b5

#### Operating system (distribution) and version.
Tested on WSL2 (Debian 11) and Kali 

#### How much data is handled by borg?
2 archives adding one new file each (total text 2 files)

#### Full borg commandline that lead to the problem (leave away excludes and passwords)
```
borg rlist --older 1d --repo XXX
```


## Describe the problem you're observing.

I'm getting ValueError on running `borg rlist --older 1d` on a new repo which does not have archives older than a day.

Introduced in #7272 

https://github.com/borgbackup/borg/pull/7272/files#diff-56ce27fae07c0d4ca00050b58f86125f91d88cb4016e38f7c2d5890de9a4e0f7R39-R40

archives_list is empty so min raises ValueError. Perhaps add a check?

```
Traceback (most recent call last):
...
  File "/home/xxx/Projects/borg/env/lib/python3.9/site-packages/borg/manifest.py", line 141, in list
    archives = filter_archives_by_date(archives, oldest=oldest, newest=newest, newer=newer, older=older)
  File "/home/xxx/Projects/borg/env/lib/python3.9/site-packages/borg/manifest.py", line 50, in filter_archives_by_date
    earliest_ts, latest_ts = get_first_and_last_archive_ts(archives)
  File "/home/xxx/Projects/borg/env/lib/python3.9/site-packages/borg/manifest.py", line 41, in get_first_and_last_archive_ts
    return min(timestamps), max(timestamps)
ValueError: min() arg is an empty sequence
```

#### Can you reproduce the problem? If so, describe how. If not, describe troubleshooting steps you took before opening the issue.
create a new repo with two archives (both adding one new file). Now run the rlist command with `--older 1d`


Discussion:
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/borgbackup/borg/pull/7544?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) Report
> Merging [#7544](https://app.codecov.io/gh/borgbackup/borg/pull/7544?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) (d7a545a) into [master](https://app.codecov.io/gh/borgbackup/borg/commit/cca8280393b50ba3241b9d33fd66b10b73add34a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) (cca8280) will **decrease** coverage by `0.31%`.
> The diff coverage is `0.00%`.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup)

```diff
@@            Coverage Diff             @@
##           master    #7544      +/-   ##
==========================================
- Coverage   83.82%   83.52%   -0.31%     
==========================================
  Files          67       67              
  Lines       11849    11851       +2     
  Branches     2162     2163       +1     
==========================================
- Hits         9933     9898      -35     
- Misses       1341     1370      +29     
- Partials      575      583       +8     
```


| [Impacted Files](https://app.codecov.io/gh/borgbackup/borg/pull/7544?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) | Coverage Δ | |
|---|---|---|
| [src/borg/manifest.py](https://app.codecov.io/gh/borgbackup/borg/pull/7544?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup#diff-c3JjL2JvcmcvbWFuaWZlc3QucHk=) | `92.96% <0.00%> (-0.95%)` | :arrow_down: |

... and [7 files with indirect coverage changes](https://app.codecov.io/gh/borgbackup/borg/pull/7544/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup)



Patch:
diff --git a/src/borg/manifest.py b/src/borg/manifest.py
index 4eb9b1e3..c6409f4b 100644
--- a/src/borg/manifest.py
+++ b/src/borg/manifest.py
@@ -39,6 +39,9 @@ def get_first_and_last_archive_ts(archives_list):
         timestamps = [x.ts for x in archives_list]
         return min(timestamps), max(timestamps)
 
+    if not archives:
+        return archives
+
     now = archive_ts_now()
     earliest_ts, latest_ts = get_first_and_last_archive_ts(archives)
 
@@ -46,6 +49,9 @@ def get_first_and_last_archive_ts(archives_list):
     from_ts = calculate_relative_offset(newer, now, earlier=True) if newer is not None else earliest_ts
     archives = [x for x in archives if from_ts <= x.ts <= until_ts]
 
+    if not archives:
+        return archives
+
     earliest_ts, latest_ts = get_first_and_last_archive_ts(archives)
     if oldest:
         until_ts = calculate_relative_offset(oldest, earliest_ts, earlier=False)
@@ -136,7 +142,7 @@ def list(
         regex = re.compile(regex + match_end)
         archives = [x for x in archives if regex.match(x.name) is not None]
 
-        if any([oldest, newest, older, newer]) and len(archives) > 0:
+        if any([oldest, newest, older, newer]):
             archives = filter_archives_by_date(archives, oldest=oldest, newest=newest, newer=newer, older=older)
         if not consider_checkpoints:
             archives = [x for x in archives if ".checkpoint" not in x.name]


Test patch:
diff --git a/src/borg/testsuite/archiver/check_cmd.py b/src/borg/testsuite/archiver/check_cmd.py
index 023a983d..9669ee7a 100644
--- a/src/borg/testsuite/archiver/check_cmd.py
+++ b/src/borg/testsuite/archiver/check_cmd.py
@@ -92,6 +92,13 @@ def test_date_matching(self):
         self.assert_in("archive2", output)
         self.assert_not_in("archive3", output)
 
+        # check for output when timespan older than earliest archive is given. Issue #1711
+        output = self.cmd(
+            f"--repo={self.repository_location}", "check", "-v", "--archives-only", "--older=9999m", exit_code=0
+        )
+        for archive in ("archive1", "archive2", "archive3"):
+            self.assert_not_in(archive, output)
+
     def test_missing_file_chunk(self):
         archive, repository = self.open_archive("archive1")
         with repository:
diff --git a/src/borg/testsuite/repository.py b/src/borg/testsuite/repository.py
index 8c44c00f..1a1af9d2 100644
--- a/src/borg/testsuite/repository.py
+++ b/src/borg/testsuite/repository.py
@@ -61,13 +61,13 @@ def setUp(self):
         self.repository.__enter__()
 
     def tearDown(self):
-        self.repository.close()
+        self.repository.__exit__(None, None, None)
         shutil.rmtree(self.tmppath)
 
     def reopen(self, exclusive=UNSPECIFIED):
-        if self.repository:
-            self.repository.close()
+        self.repository.__exit__(None, None, None)
         self.repository = self.open(exclusive=exclusive)
+        self.repository.__enter__()
 
     def add_keys(self):
         self.repository.put(H(0), fchunk(b"foo"))
@@ -353,9 +353,8 @@ def test_replay_of_missing_index(self):
             if name.startswith("index."):
                 os.unlink(os.path.join(self.repository.path, name))
         self.reopen()
-        with self.repository:
-            self.assert_equal(len(self.repository), 3)
-            self.assert_equal(self.repository.check(), True)
+        self.assert_equal(len(self.repository), 3)
+        self.assert_equal(self.repository.check(), True)
 
     def test_crash_before_compact_segments(self):
         self.add_keys()
@@ -365,9 +364,8 @@ def test_crash_before_compact_segments(self):
         except TypeError:
             pass
         self.reopen()
-        with self.repository:
-            self.assert_equal(len(self.repository), 3)
-            self.assert_equal(self.repository.check(), True)
+        self.assert_equal(len(self.repository), 3)
+        self.assert_equal(self.repository.check(), True)
 
     def test_crash_before_write_index(self):
         self.add_keys()
@@ -377,9 +375,8 @@ def test_crash_before_write_index(self):
         except TypeError:
             pass
         self.reopen()
-        with self.repository:
-            self.assert_equal(len(self.repository), 3)
-            self.assert_equal(self.repository.check(), True)
+        self.assert_equal(len(self.repository), 3)
+        self.assert_equal(self.repository.check(), True)
 
     def test_replay_lock_upgrade_old(self):
         self.add_keys()
@@ -415,17 +412,15 @@ def test_crash_before_deleting_compacted_segments(self):
         except TypeError:
             pass
         self.reopen()
-        with self.repository:
-            self.assert_equal(len(self.repository), 3)
-            self.assert_equal(self.repository.check(), True)
-            self.assert_equal(len(self.repository), 3)
+        self.assert_equal(len(self.repository), 3)
+        self.assert_equal(self.repository.check(), True)
+        self.assert_equal(len(self.repository), 3)
 
     def test_ignores_commit_tag_in_data(self):
         self.repository.put(H(0), LoggedIO.COMMIT)
         self.reopen()
-        with self.repository:
-            io = self.repository.io
-            assert not io.is_committed_segment(io.get_latest_segment())
+        io = self.repository.io
+        assert not io.is_committed_segment(io.get_latest_segment())
 
     def test_moved_deletes_are_tracked(self):
         self.repository.put(H(1), fchunk(b"1"))
@@ -548,11 +543,9 @@ def test_additional_free_space(self):
         self.repository.config.set("repository", "additional_free_space", "1000T")
         self.repository.save_key(b"shortcut to save_config")
         self.reopen()
-
-        with self.repository:
-            self.repository.put(H(0), fchunk(b"foobar"))
-            with pytest.raises(Repository.InsufficientFreeSpaceError):
-                self.repository.commit(compact=False)
+        self.repository.put(H(0), fchunk(b"foobar"))
+        with pytest.raises(Repository.InsufficientFreeSpaceError):
+            self.repository.commit(compact=False)
         assert os.path.exists(self.repository.path)
 
     def test_create_free_space(self):
@@ -576,16 +569,15 @@ def test_tracking(self):
         self.repository.commit(compact=False)
         assert self.repository.storage_quota_use == len(ch1) + len(ch2) + 2 * (41 + 8)  # we have not compacted yet
         self.reopen()
-        with self.repository:
-            # Open new transaction; hints and thus quota data is not loaded unless needed.
-            ch3 = fchunk(b"")
-            self.repository.put(H(3), ch3)
-            self.repository.delete(H(3))
-            assert self.repository.storage_quota_use == len(ch1) + len(ch2) + len(ch3) + 3 * (
-                41 + 8
-            )  # we have not compacted yet
-            self.repository.commit(compact=True)
-            assert self.repository.storage_quota_use == len(ch2) + 41 + 8
+        # Open new transaction; hints and thus quota data is not loaded unless needed.
+        ch3 = fchunk(b"")
+        self.repository.put(H(3), ch3)
+        self.repository.delete(H(3))
+        assert self.repository.storage_quota_use == len(ch1) + len(ch2) + len(ch3) + 3 * (
+            41 + 8
+        )  # we have not compacted yet
+        self.repository.commit(compact=True)
+        assert self.repository.storage_quota_use == len(ch2) + 41 + 8
 
     def test_exceed_quota(self):
         assert self.repository.storage_quota_use == 0
@@ -602,15 +594,14 @@ def test_exceed_quota(self):
             self.repository.commit(compact=False)
         assert self.repository.storage_quota_use == len(ch1) + len(ch2) + (41 + 8) * 2  # check ch2!?
         self.reopen()
-        with self.repository:
-            self.repository.storage_quota = 150
-            # Open new transaction; hints and thus quota data is not loaded unless needed.
-            self.repository.put(H(1), ch1)
-            assert (
-                self.repository.storage_quota_use == len(ch1) * 2 + (41 + 8) * 2
-            )  # we have 2 puts for H(1) here and not yet compacted.
-            self.repository.commit(compact=True)
-            assert self.repository.storage_quota_use == len(ch1) + 41 + 8  # now we have compacted.
+        self.repository.storage_quota = 150
+        # Open new transaction; hints and thus quota data is not loaded unless needed.
+        self.repository.put(H(1), ch1)
+        assert (
+            self.repository.storage_quota_use == len(ch1) * 2 + (41 + 8) * 2
+        )  # we have 2 puts for H(1) here and not yet compacted.
+        self.repository.commit(compact=True)
+        assert self.repository.storage_quota_use == len(ch1) + 41 + 8  # now we have compacted.
 
 
 class RepositoryAuxiliaryCorruptionTestCase(RepositoryTestCaseBase):
@@ -896,9 +887,8 @@ def test_crash_before_compact(self):
             self.repository.commit(compact=True)
             compact.assert_called_once_with(0.1)
         self.reopen()
-        with self.repository:
-            self.check(repair=True)
-            self.assert_equal(pdchunk(self.repository.get(H(0))), b"data2")
+        self.check(repair=True)
+        self.assert_equal(pdchunk(self.repository.get(H(0))), b"data2")
 
 
 class RepositoryHintsTestCase(RepositoryTestCaseBase):
@@ -912,14 +902,13 @@ def test_hints_persistence(self):
         # close and re-open the repository (create fresh Repository instance) to
         # check whether hints were persisted to / reloaded from disk
         self.reopen()
-        with self.repository:
-            # see also do_compact()
-            self.repository.put(H(42), fchunk(b"foobar"))  # this will call prepare_txn() and load the hints data
-            # check if hints persistence worked:
-            self.assert_equal(shadow_index_expected, self.repository.shadow_index)
-            self.assert_equal(compact_expected, self.repository.compact)
-            del self.repository.segments[2]  # ignore the segment created by put(H(42), ...)
-            self.assert_equal(segments_expected, self.repository.segments)
+        # see also do_compact()
+        self.repository.put(H(42), fchunk(b"foobar"))  # this will call prepare_txn() and load the hints data
+        # check if hints persistence worked:
+        self.assert_equal(shadow_index_expected, self.repository.shadow_index)
+        self.assert_equal(compact_expected, self.repository.compact)
+        del self.repository.segments[2]  # ignore the segment created by put(H(42), ...)
+        self.assert_equal(segments_expected, self.repository.segments)
 
     def test_hints_behaviour(self):
         self.repository.put(H(0), fchunk(b"data"))