Pārlūkot izejas kodu

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 gadi atpakaļ
vecāks
revīzija
9f6840dddb
3 mainītis faili ar 66 papildinājumiem un 4 dzēšanām
  1. 20 4
      attic/archive.py
  2. 26 0
      attic/archiver.py
  3. 20 0
      attic/testsuite/archiver.py

+ 20 - 4
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'])):

+ 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))
         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.

+ 20 - 0
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)