? dist/build ? duplicity/C.py Index: duplicity-bin =================================================================== RCS file: /sources/duplicity/duplicity/duplicity-bin,v retrieving revision 1.28 diff -u -r1.28 duplicity-bin --- duplicity-bin 26 Oct 2007 19:01:40 -0000 1.28 +++ duplicity-bin 27 Oct 2007 10:58:15 -0000 @@ -297,6 +297,13 @@ log.Warn(filestr + "\nRun duplicity again with the --force " "option to actually delete.") +def remove_all_but_n_full(col_stats): + "Remove backup files older than the last n full backups." + assert globals.keep_chains is not None + + globals.remove_time = col_stats.get_nth_last_full_backup_time(globals.keep_chains) + + return remove_old(col_stats) def remove_old(col_stats): """Remove backup files older than globals.remove_time from backend""" @@ -354,8 +361,9 @@ globals.archive_dir).set_values() log.Log("Collection Status\n-----------------\n" + str(col_stats), 8) - log.Log("Last full backup date: " + dup_time.timetopretty(col_stats.get_last_full_backup()), 4) - if action == "inc" and col_stats.get_last_full_backup() < globals.full_force_time: + last_full_time = col_stats.get_last_full_backup_time() + log.Log("Last full backup date: " + dup_time.timetopretty(last_full_time), 4) + if action == "inc" and last_full_time < globals.full_force_time: log.Log("Last full backup is too old, forcing full backup", 3) action = "full" @@ -385,6 +393,7 @@ elif action == "collection-status": print str(col_stats) elif action == "cleanup": cleanup(col_stats) elif action == "remove-old": remove_old(col_stats) + elif action == "remove-all-but-n-full": remove_all_but_n_full(col_stats) else: assert action == "inc" or action == "full", action if action == "full": full_backup(col_stats) Index: duplicity.1 =================================================================== RCS file: /sources/duplicity/duplicity/duplicity.1,v retrieving revision 1.27 diff -u -r1.27 duplicity.1 --- duplicity.1 26 Oct 2007 19:05:37 -0000 1.27 +++ duplicity.1 27 Oct 2007 10:58:15 -0000 @@ -39,6 +39,11 @@ .BI [ options ] .I target_url +.B duplicity remove-all-but-n-full +.I count +.BI [ options ] +.I target_url + .SH DESCRIPTION Duplicity incrementally backs up files and directory by encrypting tar-format volumes with GnuPG and uploading them to a @@ -159,6 +164,15 @@ section for more information. Note, this action cannot be combined with backup or other actions, such as cleanup. .TP +.BI "remove-all-but-n-full " count +Delete all backups sets that are older than the count:th last full +backup (in other words, keep the last +.I count +full backups and associated incremental sets). +.I count +must be larger than zero. A value of 1 means that only the single most +recent backup chain will be kept. +.TP .B verify Enter verify mode instead of restore. If the --file-to-restore option is given, restrict verify to that file or directory. duplicity will Index: duplicity/collections.py =================================================================== RCS file: /sources/duplicity/duplicity/duplicity/collections.py,v retrieving revision 1.19 diff -u -r1.19 collections.py --- duplicity/collections.py 26 Oct 2007 18:58:09 -0000 1.19 +++ duplicity/collections.py 27 Oct 2007 10:58:15 -0000 @@ -666,17 +666,43 @@ assert self.values_set return filter(lambda c: c.end_time < t, self.all_backup_chains) - def get_last_full_backup(self): - """Return the time of the last full backup of the collection, or 0""" - assert self.values_set - # Is there a nicer way to do that with a lambda function? - if len(self.all_backup_chains) == 0: + def get_last_full_backup_time(self): + """Return the time of the last full backup, or 0 if + there is none.""" + return self.get_nth_last_full_backup_time(1) + + def get_nth_last_full_backup_time(self, n): + """Return the time of the nth to last full backup, or 0 + if there is none.""" + chain = self.get_nth_last_backup_chain(n) + if chain is None: return 0 - last = self.all_backup_chains[0] - for bc in self.all_backup_chains: - if bc.get_first().time > last.get_first().time: - last = bc - return last.get_first().time + else: + return chain.get_first().time + + def get_last_backup_chain(self): + """Return the last full backup of the collection, or None + if there is no full backup chain.""" + return self.get_nth_last_backup_chain(1) + + def get_nth_last_backup_chain(self,n): + """Return the nth-to-last full backup of the collection, or None + if there is less than n backup chains. + + NOTE: n = 1 -> time of latest available chain (n = 0 is not + a valid input). Thus the second-to-last is obtained with n=2 + rather than n=1.""" + assert self.values_set + assert n > 0 + + if len(self.all_backup_chains) < n: + return None + + sorted = self.all_backup_chains[:] + sorted.sort(reverse = True, + key = lambda chain: chain.get_first().time) + + return sorted[n - 1] def get_older_than(self, t): """Returns a list of backup sets older than the given time t Index: duplicity/commandline.py =================================================================== RCS file: /sources/duplicity/duplicity/duplicity/commandline.py,v retrieving revision 1.30 diff -u -r1.30 commandline.py --- duplicity/commandline.py 26 Oct 2007 18:59:01 -0000 1.30 +++ duplicity/commandline.py 27 Oct 2007 10:58:15 -0000 @@ -35,6 +35,7 @@ "incremental", "list-current-files", "remove-older-than", + "remove-all-but-n-full", "verify", ] @@ -120,6 +121,17 @@ command_line_error("Missing time string for remove-older-than") globals.remove_time = dup_time.genstrtotime(arg) num_expect = 1 + elif cmd == "remove-all-but-n-full": + try: + arg = arglist.pop(0) + except: + command_line_error("Missing time string for remove-all-but-n-full") + globals.keep_chains = int(arg) + + if not globals.keep_chains > 0: + command_line_error("remove-all-but-n-full must be > 0") + + num_expect = 1 elif cmd == "verify": verify = True num_expect = 2 @@ -240,6 +252,7 @@ duplicity list-current-files [options] target_url duplicity cleanup [options] target_url duplicity remove-older-than time [options] target_url + duplicity remove-all-but-n-full count [options] target_url Backends and their URL formats: ssh://address@hidden:port/some_dir @@ -260,6 +273,7 @@ list-current-files restore remove-older-than