From 9f6840dddb21b9c32c70b65662c53a9a47726b26 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 24 Mar 2015 07:11:00 +0100 Subject: [PATCH] implement attic rename repo::oldname newname I extracted the inner part of Archive.load into a new _load_meta method that does not modify self and does not decode, so I could simply reuse it. --- attic/archive.py | 24 ++++++++++++++++++++---- attic/archiver.py | 26 ++++++++++++++++++++++++++ attic/testsuite/archiver.py | 20 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/attic/archive.py b/attic/archive.py index d78ce4b85..cc76c47b1 100644 --- a/attic/archive.py +++ b/attic/archive.py @@ -150,12 +150,16 @@ class Archive: info = self.manifest.archives[name] self.load(info[b'id']) + def _load_meta(self, id): + data = self.key.decrypt(id, self.repository.get(id)) + metadata = msgpack.unpackb(data) + if metadata[b'version'] != 1: + raise Exception('Unknown archive metadata version') + return metadata + def load(self, id): self.id = id - data = self.key.decrypt(self.id, self.repository.get(self.id)) - self.metadata = msgpack.unpackb(data) - if self.metadata[b'version'] != 1: - raise Exception('Unknown archive metadata version') + self.metadata = self._load_meta(self.id) decode_dict(self.metadata, (b'name', b'hostname', b'username', b'time')) self.metadata[b'cmdline'] = [arg.decode('utf-8', 'surrogateescape') for arg in self.metadata[b'cmdline']] self.name = self.metadata[b'name'] @@ -335,6 +339,18 @@ class Archive: except OSError: pass + def rename(self, name): + if name in self.manifest.archives: + raise self.AlreadyExists(name) + metadata = StableDict(self._load_meta(self.id)) + metadata[b'name'] = name + data = msgpack.packb(metadata, unicode_errors='surrogateescape') + new_id = self.key.id_hash(data) + self.cache.add_chunk(new_id, data, self.stats) + self.manifest.archives[name] = {'id': new_id, 'time': metadata[b'time']} + self.cache.chunk_decref(self.id, self.stats) + del self.manifest.archives[self.name] + def delete(self, stats): unpacker = msgpack.Unpacker(use_list=False) for items_id, data in zip(self.metadata[b'items'], self.repository.get_many(self.metadata[b'items'])): diff --git a/attic/archiver.py b/attic/archiver.py index 47650c2d4..7393e3908 100644 --- a/attic/archiver.py +++ b/attic/archiver.py @@ -223,6 +223,18 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") archive.extract_item(dirs.pop(-1)) return self.exit_code + def do_rename(self, args): + """Rename an existing archive""" + repository = self.open_repository(args.archive, exclusive=True) + manifest, key = Manifest.load(repository) + cache = Cache(repository, key, manifest) + archive = Archive(repository, key, manifest, args.archive.archive, cache=cache) + archive.rename(args.name) + manifest.write() + repository.commit() + cache.commit() + return self.exit_code + def do_delete(self, args): """Delete an existing archive""" repository = self.open_repository(args.archive, exclusive=True) @@ -590,6 +602,20 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths to extract') + rename_epilog = textwrap.dedent(""" + This command renames an archive in the repository. + """) + subparser = subparsers.add_parser('rename', parents=[common_parser], + description=self.do_rename.__doc__, + epilog=rename_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter) + subparser.set_defaults(func=self.do_rename) + subparser.add_argument('archive', metavar='ARCHIVE', + type=location_validator(archive=True), + help='archive to rename') + subparser.add_argument('name', metavar='NEWNAME', type=str, + help='the new archive name to use') + delete_epilog = textwrap.dedent(""" This command deletes an archive from the repository. Any disk space not shared with any other existing archive is also reclaimed. diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index 382fcc854..80140c7bb 100644 --- a/attic/testsuite/archiver.py +++ b/attic/testsuite/archiver.py @@ -241,6 +241,26 @@ class ArchiverTestCase(ArchiverTestCaseBase): with changedir('output'): self.attic('extract', self.repository_location + '::test', exit_code=1) + def test_rename(self): + self.create_regular_file('file1', size=1024 * 80) + self.create_regular_file('dir2/file2', size=1024 * 80) + self.attic('init', self.repository_location) + self.attic('create', self.repository_location + '::test', 'input') + self.attic('create', self.repository_location + '::test.2', 'input') + self.attic('extract', '--dry-run', self.repository_location + '::test') + self.attic('extract', '--dry-run', self.repository_location + '::test.2') + self.attic('rename', self.repository_location + '::test', 'test.3') + self.attic('extract', '--dry-run', self.repository_location + '::test.2') + self.attic('rename', self.repository_location + '::test.2', 'test.4') + self.attic('extract', '--dry-run', self.repository_location + '::test.3') + self.attic('extract', '--dry-run', self.repository_location + '::test.4') + # Make sure both archives have been renamed + repository = Repository(self.repository_path) + manifest, key = Manifest.load(repository) + self.assert_equal(len(manifest.archives), 2) + self.assert_in('test.3', manifest.archives) + self.assert_in('test.4', manifest.archives) + def test_delete(self): self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('dir2/file2', size=1024 * 80)