diff --git a/CHANGES b/CHANGES index afcee93f2..f35bc8865 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,14 @@ Attic Changelog Here you can see the full list of changes between each Attic release. +Version 0.8 +----------- + +(feature release, released on X) + +- Detect and abort if repository is older than the cache + + Version 0.7 ----------- diff --git a/attic/archiver.py b/attic/archiver.py index 0d7b8a761..fbcc14f0f 100644 --- a/attic/archiver.py +++ b/attic/archiver.py @@ -492,6 +492,9 @@ def main(): except Archive.DoesNotExist as e: archiver.print_error('Error: Archive "%s" does not exist', e) exit_code = 1 + except Cache.RepositoryReplay: + archiver.print_error('Cache is newer than repository, refusing to continue') + exit_code = 1 except ConnectionClosed: archiver.print_error('Connection closed by remote host') exit_code = 1 diff --git a/attic/cache.py b/attic/cache.py index aaa0515f7..4b4b865e4 100644 --- a/attic/cache.py +++ b/attic/cache.py @@ -14,7 +14,12 @@ class Cache(object): """Client Side cache """ + class RepositoryReplay(Exception): + """ + """ + def __init__(self, repository, key, manifest): + self.timestamp = None self.txn_active = False self.repository = repository self.key = key @@ -24,6 +29,9 @@ class Cache(object): self.create() self.open() if self.manifest.id != self.manifest_id: + # If repository is older than the cache something fishy is going on + if self.timestamp and self.timestamp > manifest.timestamp: + raise self.RepositoryReplay() self.sync() self.commit() @@ -31,7 +39,7 @@ class Cache(object): self.close() def create(self): - """Create a new empty repository at `path` + """Create a new empty cache at `path` """ os.makedirs(self.path) with open(os.path.join(self.path, 'README'), 'w') as fd: @@ -59,6 +67,7 @@ class Cache(object): raise Exception('%s Does not look like an Attic cache') self.id = self.config.get('cache', 'repository') self.manifest_id = unhexlify(self.config.get('cache', 'manifest')) + self.timestamp = self.config.get('cache', 'timestamp', fallback=None) self.chunks = ChunkIndex(os.path.join(self.path, 'chunks').encode('utf-8')) self.files = None @@ -103,6 +112,7 @@ class Cache(object): if item[1][0] < 10 and item[1][3] < self._newest_mtime: msgpack.pack(item, fd) self.config.set('cache', 'manifest', hexlify(self.manifest.id).decode('ascii')) + self.config.set('cache', 'timestamp', self.manifest.timestamp) with open(os.path.join(self.path, 'config'), 'w') as fd: self.config.write(fd) self.chunks.flush() diff --git a/attic/helpers.py b/attic/helpers.py index b5241eb4b..2af7b6a21 100644 --- a/attic/helpers.py +++ b/attic/helpers.py @@ -34,13 +34,18 @@ class Manifest: if not m.get(b'version') == 1: raise ValueError('Invalid manifest version') manifest.archives = dict((k.decode('utf-8'), v) for k,v in m[b'archives'].items()) + manifest.timestamp = m.get(b'timestamp') + if manifest.timestamp: + manifest.timestamp = manifest.timestamp.decode('ascii') manifest.config = m[b'config'] return manifest, key def write(self): + self.timestamp = datetime.utcnow().isoformat() data = msgpack.packb({ 'version': 1, 'archives': self.archives, + 'timestamp': self.timestamp, 'config': self.config, }) self.id = self.key.id_hash(data)