3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import print_function
9 from collections import defaultdict
10 from itertools import groupby
11 from sys import stderr
12 from time import localtime, strftime, time
15 from bup import git, options
16 from bup.gc import bup_gc
17 from bup.helpers import die_if_errors, log, partition, period_as_secs
18 from bup.rm import bup_rm
21 def branches(refnames=()):
22 return ((name[11:], sha) for (name,sha)
23 in git.list_refs(refnames=('refs/heads/' + n for n in refnames),
26 def save_name(branch, utc):
27 return branch + '/' + strftime('%Y-%m-%d-%H%M%S', localtime(utc))
29 def classify_saves(saves, period_start):
30 """For each (utc, id) in saves, yield (True, (utc, id)) if the save
31 should be kept and (False, (utc, id)) if the save should be removed.
32 The ids are binary hashes.
35 def retain_oldest_in_region(region):
44 matches, rest = partition(lambda s: s[0] >= period_start['all'], saves)
48 tm_ranges = ((period_start['dailies'], lambda s: localtime(s[0]).tm_yday),
49 (period_start['monthlies'], lambda s: localtime(s[0]).tm_mon),
50 (period_start['yearlies'], lambda s: localtime(s[0]).tm_year))
52 for pstart, time_region_id in tm_ranges:
53 matches, rest = partition(lambda s: s[0] >= pstart, rest)
54 for region_id, region_saves in groupby(matches, time_region_id):
55 for action in retain_oldest_in_region(region_saves):
63 bup prune-older [options...] [BRANCH...]
65 keep-all-for= retain all saves within the PERIOD
66 keep-dailies-for= retain the oldest save per day within the PERIOD
67 keep-monthlies-for= retain the oldest save per month within the PERIOD
68 keep-yearlies-for= retain the oldest save per year within the PERIOD
69 wrt= end all periods at this number of seconds since the epoch
70 pretend don't prune, just report intended actions to standard output
71 gc collect garbage after removals [1]
72 gc-threshold= only rewrite a packfile if it's over this percent garbage [10]
73 #,compress= set compression level to # (0-9, 9 is highest) [1]
74 v,verbose increase log output (can be used more than once)
75 unsafe use the command even though it may be DANGEROUS
78 o = options.Options(optspec)
79 opt, flags, roots = o.parse(sys.argv[1:])
82 o.fatal('refusing to run dangerous, experimental command without --unsafe')
84 now = int(time()) if not opt.wrt else opt.wrt
85 if not isinstance(now, (int, long)):
86 o.fatal('--wrt value ' + str(now) + ' is not an integer')
89 for period, extent in (('all', opt.keep_all_for),
90 ('dailies', opt.keep_dailies_for),
91 ('monthlies', opt.keep_monthlies_for),
92 ('yearlies', opt.keep_yearlies_for)):
94 secs = period_as_secs(extent)
96 o.fatal('%r is not a valid period' % extent)
97 period_start[period] = now - secs
100 o.fatal('at least one keep argument is required')
102 period_start = defaultdict(lambda: float('inf'), period_start)
105 epoch_ymd = strftime('%Y-%m-%d-%H%M%S', localtime(0))
106 for kind in ['all', 'dailies', 'monthlies', 'yearlies']:
107 period_utc = period_start[kind]
108 if period_utc != float('inf'):
109 if not (period_utc > float('-inf')):
110 log('keeping all ' + kind)
113 when = strftime('%Y-%m-%d-%H%M%S', localtime(period_utc))
114 log('keeping ' + kind + ' since ' + when + '\n')
115 except ValueError as ex:
117 log('keeping %s since %d seconds before %s\n'
118 %(kind, abs(period_utc), epoch_ymd))
120 log('keeping %s since %d seconds after %s\n'
121 %(kind, period_utc, epoch_ymd))
123 log('keeping %s since %s\n' % (kind, epoch_ymd))
125 git.check_repo_or_die()
127 # This could be more efficient, but for now just build the whole list
128 # in memory and let bup_rm() do some redundant work.
131 for branch, branch_id in branches(roots):
133 saves = git.rev_list(branch_id.encode('hex'))
134 for keep_save, (utc, id) in classify_saves(saves, period_start):
135 assert(keep_save in (False, True))
136 # FIXME: base removals on hashes
138 print('+' if keep_save else '-', save_name(branch, utc))
140 removals.append(save_name(branch, utc))
144 bup_rm(removals, compression=opt.compress, verbosity=opt.verbose)
147 bup_gc(threshold=opt.gc_threshold,
148 compression=opt.compress,
149 verbosity=opt.verbose)