Skip to content

Commit 89c6a2c

Browse files
committed
Improves interval example
Makes specific notes on week numbers, clears some ambiguity in the yearly candidate wording, and more. The matching test was previously missing assertions on the weekly archives, now fixed along with better explanations.
1 parent 378f7c2 commit 89c6a2c

2 files changed

Lines changed: 42 additions & 18 deletions

File tree

docs/misc/prune-example-interval.txt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ to "thin out" over time while retaining most recent archives. Your backup
77
script runs `borg create`, immediately followed by `borg prune`.
88

99
Assume today is 2026-06-04 and you always start your backups at 16:00. You have
10-
been creating backup archives starting at 16:00 on most days going back to late
11-
2025. Today, `borg create` took a little longer than usual. It's 16:12 now and
12-
you run `borg prune`.
10+
been creating backup archives starting at 16:00, followed by pruning, on most
11+
days going back to late 2025. Today, `borg create` took a little longer than
12+
usual. It's 16:12 now and you run `borg prune`.
1313

1414
You want Borg to keep one archive per day for one week, four weekly archives,
1515
one archive per month for five months, and two yearly backups. For that, you
@@ -115,16 +115,22 @@ later reference time, e.g. `--since '2026-06-04 23:59:59'`.
115115
and `--keep-monthly`, too. The archive is effectively kept by `--keep-daily`,
116116
but how this affects other rules differs between *count*- and *interval*-based
117117
policies. For *interval*-based rules like `--keep-monthly 5m` it has no effect:
118-
The rule simply keeps one archive fewer.
118+
The rule simply keeps one archive fewer in that case.
119119

120120
For *count*-based rules like `--keep-weekly 4` it has an effect: The policy
121-
tells Borg to keep 4 weekly archives, and if 2026-05-31 is kept by another
122-
rule already, Borg compensates by keeping an older archive instead.
123-
Consequently, Borg will also keep the 2026-05-03 archive.
121+
tells Borg to keep 4 weekly archives. Weekly slots are identified by ISO week
122+
number; in this example the four kept archives cover weeks 18 through 21. If
123+
2026-05-31 is kept by another rule already, Borg compensates by keeping an
124+
older archive instead. Consequently, Borg will also keep the 2026-05-03 archive.
124125

125-
Since 2026-05-24 and 2026-03-31 were skipped, there are no perfect candidates
126-
for that week and month. Borg chooses the next best candidate, so it keeps
127-
2026-05-23 as the weekly and 2026-03-30 as the monthly candidate.
126+
The week 22 slot is consumed by that same 2026-05-31 archive. Any other
127+
archive from that week is not kept by weekly, even if it falls within the
128+
daily window boundary. This is why 2026-05-27 is pruned: it has just fallen
129+
out of the daily window, and no other rule covers it.
130+
131+
Since 2026-05-24 and 2026-03-31 were skipped, Borg substitutes the next best
132+
candidate for each: 2026-05-23 fills the week 21 weekly slot, and 2026-03-30
133+
fills the March monthly slot.
128134

129135
The implementation of `--keep-monthly 5m` is somewhat special: Borg defines a
130136
month as a fixed 31-day period, independent of the actual calendar dates
@@ -144,6 +150,6 @@ Intervals and counts can be mixed freely. Yearly retention in this example is
144150
done by retention count instead of intervals. A count rule paired with interval
145151
rules behaves just as it would if all preceding rules were also counts:
146152
Archives already kept by earlier rules are excluded from consideration. In this
147-
example there is only one "true" yearly candidate, so the oldest archive at
148-
2025-11-15 is kept. This oldest archive will be kept until the rolling backup
149-
scheme reaches "steady state" (when all retention rules are fully satisfied).
153+
example, every yearly slot is consumed by earlier rules; no true yearly
154+
candidate remains, so the oldest archive at 2025-11-15 is kept to preserve
155+
rolling backup semantics.

src/borg/testsuite/archiver/prune_cmd_test.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,19 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
123123

124124
# All timestamps are at exactly 16:00 UTC (matching the example).
125125
# Backups on most days from 2025-11-15 to 2026-06-04, with skips on
126-
# 2026-03-31 and 2026-06-03. At the inclusive interval boundaries,
127-
# 2026-05-28 (1w before --since) and 2025-12-31 (155d before --since)
128-
# are kept.
126+
# 2026-03-31, 2026-05-24, and 2026-06-03.
129127
archive_dates = [
130128
(2025, 11, 15),
131129
(2025, 12, 31),
132130
(2026, 1, 31),
133131
(2026, 2, 28),
134132
(2026, 3, 30),
135133
(2026, 4, 30),
134+
(2026, 5, 3),
135+
(2026, 5, 10),
136+
(2026, 5, 17),
137+
(2026, 5, 23),
138+
(2026, 5, 27),
136139
(2026, 5, 28),
137140
(2026, 5, 29),
138141
(2026, 5, 30),
@@ -153,6 +156,7 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
153156
"--dry-run",
154157
"--since=2026-06-04T16:00:00+00:00",
155158
"--keep-daily=1w",
159+
"--keep-weekly=4",
156160
"--keep-monthly=5m",
157161
"--keep-yearly=2",
158162
)
@@ -166,18 +170,27 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
166170
"backup_2026-05-31",
167171
"backup_2026-05-30",
168172
"backup_2026-05-29",
169-
# 2026-05-28 is at the inclusive boundary (exactly 1w before --since).
170173
"backup_2026-05-28",
171174
]
172175
for i, name in enumerate(daily_kept, 1):
173176
assert re.search(rf"Keeping archive \(rule: daily #{i}\):\s+{name}", output)
174177

178+
# Weekly W22 slot is consumed by 05-31 (already kept by daily),
179+
# so weekly reaches back to W18 to fill all 4 slots.
180+
weekly_kept = [
181+
"backup_2026-05-23", # W21 — no Sunday candidate (05-24 skipped)
182+
"backup_2026-05-17", # W20
183+
"backup_2026-05-10", # W19
184+
"backup_2026-05-03", # W18
185+
]
186+
for i, name in enumerate(weekly_kept, 1):
187+
assert re.search(rf"Keeping archive \(rule: weekly #{i}\):\s+{name}", output)
188+
175189
monthly_kept = [
176190
"backup_2026-04-30",
177191
"backup_2026-03-30",
178192
"backup_2026-02-28",
179193
"backup_2026-01-31",
180-
# 2025-12-31 is at the inclusive boundary (exactly 155d before --since).
181194
"backup_2025-12-31",
182195
]
183196
for i, name in enumerate(monthly_kept, 1):
@@ -186,6 +199,11 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
186199
# No true yearly candidates remain; only the oldest archive fills the slot.
187200
assert re.search(r"Keeping archive \(rule: yearly\[oldest\] #1\):\s+backup_2025-11-15", output)
188201

202+
# 05-27 was kept by daily yesterday (window started 05-26) but falls out
203+
# today (window shifted to 05-28). W22 slot is consumed by daily-held 05-31,
204+
# so weekly doesn't save it either.
205+
assert re.search(r"Would prune:\s+backup_2026-05-27", output)
206+
189207

190208
def test_prune_quarterly(archivers, request, backup_files):
191209
# Example worked through by hand when developing the quarterly

0 commit comments

Comments
 (0)