2
0
Эх сурвалжийг харах

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.
Thomas Waldmann 10 жил өмнө
parent
commit
9f6840dddb

+ 20 - 4
attic/archive.py

@@ -150,12 +150,16 @@ class Archive:
             info = self.manifest.archives[name]
             info = self.manifest.archives[name]
             self.load(info[b'id'])
             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):
     def load(self, id):
         self.id = 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'))
         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.metadata[b'cmdline'] = [arg.decode('utf-8', 'surrogateescape') for arg in self.metadata[b'cmdline']]
         self.name = self.metadata[b'name']
         self.name = self.metadata[b'name']
@@ -335,6 +339,18 @@ class Archive:
             except OSError:
             except OSError:
                 pass
                 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):
     def delete(self, stats):
         unpacker = msgpack.Unpacker(use_list=False)
         unpacker = msgpack.Unpacker(use_list=False)
         for items_id, data in zip(self.metadata[b'items'], self.repository.get_many(self.metadata[b'items'])):
         for items_id, data in zip(self.metadata[b'items'], self.repository.get_many(self.metadata[b'items'])):

+ 26 - 0
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))
                 archive.extract_item(dirs.pop(-1))
         return self.exit_code
         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):
     def do_delete(self, args):
         """Delete an existing archive"""
         """Delete an existing archive"""
         repository = self.open_repository(args.archive, exclusive=True)
         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,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths to extract')
                                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("""
         delete_epilog = textwrap.dedent("""
         This command deletes an archive from the repository. Any disk space not
         This command deletes an archive from the repository. Any disk space not
         shared with any other existing archive is also reclaimed.
         shared with any other existing archive is also reclaimed.

+ 20 - 0
attic/testsuite/archiver.py

@@ -241,6 +241,26 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         with changedir('output'):
         with changedir('output'):
             self.attic('extract', self.repository_location + '::test', exit_code=1)
             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):
     def test_delete(self):
         self.create_regular_file('file1', size=1024 * 80)
         self.create_regular_file('file1', size=1024 * 80)
         self.create_regular_file('dir2/file2', size=1024 * 80)
         self.create_regular_file('dir2/file2', size=1024 * 80)