/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/transform.py

  • Committer: Jelmer Vernooij
  • Date: 2019-05-20 03:57:29 UTC
  • mto: This revision was merged to the branch mainline in revision 7328.
  • Revision ID: jelmer@jelmer.uk-20190520035729-9rxvefxkvbbivygy
use default_user_agent function.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
from breezy import (
34
34
    annotate,
35
35
    bencode,
36
 
    cleanup,
37
36
    controldir,
38
37
    commit,
39
38
    conflicts,
53
52
""")
54
53
from .errors import (DuplicateKey, MalformedTransform,
55
54
                     ReusingTransform, CantMoveRoot,
56
 
                     ImmortalLimbo, NoFinalPath)
 
55
                     ImmortalLimbo, NoFinalPath,
 
56
                     UnableCreateSymlink)
57
57
from .filters import filtered_output_bytes, ContentFilterContext
58
58
from .mutabletree import MutableTree
59
59
from .osutils import (
60
60
    delete_any,
61
61
    file_kind,
 
62
    has_symlinks,
62
63
    pathjoin,
63
64
    sha_file,
64
65
    splitpath,
65
 
    supports_symlinks,
66
66
    )
67
67
from .progress import ProgressPhase
68
68
from .sixish import (
71
71
    viewvalues,
72
72
    )
73
73
from .tree import (
74
 
    InterTree,
75
 
    TreeChange,
 
74
    find_previous_path,
76
75
    )
77
76
 
78
77
 
456
455
            return None
457
456
        # the file is old; the old id is still valid
458
457
        if self._new_root == trans_id:
459
 
            return self._tree.path2id('')
 
458
            return self._tree.get_root_id()
460
459
        return self._tree.path2id(path)
461
460
 
462
461
    def final_file_id(self, trans_id):
654
653
        conflicts = []
655
654
        for trans_id in self._new_id:
656
655
            kind = self.final_kind(trans_id)
657
 
            if kind == 'symlink' and not self._tree.supports_symlinks():
658
 
                # Ignore symlinks as they are not supported on this platform
659
 
                continue
660
656
            if kind is None:
661
657
                conflicts.append(('versioning no contents', trans_id))
662
658
                continue
1004
1000
                and from_parent == to_parent and from_name == to_name
1005
1001
                    and from_executable == to_executable):
1006
1002
                continue
1007
 
            results.append(
1008
 
                TreeChange(
1009
 
                    file_id, (from_path, to_path), modified,
1010
 
                    (from_versioned, to_versioned),
1011
 
                    (from_parent, to_parent),
1012
 
                    (from_name, to_name),
1013
 
                    (from_kind, to_kind),
1014
 
                    (from_executable, to_executable)))
 
1003
            results.append((file_id, (from_path, to_path), modified,
 
1004
                            (from_versioned, to_versioned),
 
1005
                            (from_parent, to_parent),
 
1006
                            (from_name, to_name),
 
1007
                            (from_kind, to_kind),
 
1008
                            (from_executable, to_executable)))
1015
1009
 
1016
 
        def path_key(c):
1017
 
            return (c.path[0] or '', c.path[1] or '')
 
1010
        def path_key(t):
 
1011
            paths = t[1]
 
1012
            return (paths[0] or '', paths[1] or '')
1018
1013
        return iter(sorted(results, key=path_key))
1019
1014
 
1020
1015
    def get_preview_tree(self):
1202
1197
            if kind == 'symlink':
1203
1198
                self.create_symlink(content.decode('utf-8'), trans_id)
1204
1199
 
1205
 
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1206
 
        """Schedule creation of a new file.
1207
 
 
1208
 
        :seealso: new_file.
1209
 
 
1210
 
        :param contents: an iterator of strings, all of which will be written
1211
 
            to the target destination.
1212
 
        :param trans_id: TreeTransform handle
1213
 
        :param mode_id: If not None, force the mode of the target file to match
1214
 
            the mode of the object referenced by mode_id.
1215
 
            Otherwise, we will try to preserve mode bits of an existing file.
1216
 
        :param sha1: If the sha1 of this content is already known, pass it in.
1217
 
            We can use it to prevent future sha1 computations.
1218
 
        """
1219
 
        raise NotImplementedError(self.create_file)
1220
 
 
1221
 
    def create_directory(self, trans_id):
1222
 
        """Schedule creation of a new directory.
1223
 
 
1224
 
        See also new_directory.
1225
 
        """
1226
 
        raise NotImplementedError(self.create_directory)
1227
 
 
1228
 
    def create_symlink(self, target, trans_id):
1229
 
        """Schedule creation of a new symbolic link.
1230
 
 
1231
 
        target is a bytestring.
1232
 
        See also new_symlink.
1233
 
        """
1234
 
        raise NotImplementedError(self.create_symlink)
1235
 
 
1236
 
    def create_hardlink(self, path, trans_id):
1237
 
        """Schedule creation of a hard link"""
1238
 
        raise NotImplementedError(self.create_hardlink)
1239
 
 
1240
 
    def cancel_creation(self, trans_id):
1241
 
        """Cancel the creation of new file contents."""
1242
 
        raise NotImplementedError(self.cancel_creation)
1243
 
 
1244
1200
 
1245
1201
class DiskTreeTransform(TreeTransformBase):
1246
1202
    """Tree transform storing its contents on disk."""
1247
1203
 
1248
 
    def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
 
1204
    def __init__(self, tree, limbodir, pb=None,
 
1205
                 case_sensitive=True):
1249
1206
        """Constructor.
1250
1207
        :param tree: The tree that will be transformed, but not necessarily
1251
1208
            the output tree.
1269
1226
        # List of transform ids that need to be renamed from limbo into place
1270
1227
        self._needs_rename = set()
1271
1228
        self._creation_mtime = None
1272
 
        self._create_symlinks = osutils.supports_symlinks(self._limbodir)
1273
1229
 
1274
1230
    def finalize(self):
1275
1231
        """Release the working tree lock, if held, clean up limbo dir.
1307
1263
 
1308
1264
    def _limbo_supports_executable(self):
1309
1265
        """Check if the limbo path supports the executable bit."""
1310
 
        return osutils.supports_executable(self._limbodir)
 
1266
        # FIXME: Check actual file system capabilities of limbodir
 
1267
        return osutils.supports_executable()
1311
1268
 
1312
1269
    def _limbo_name(self, trans_id):
1313
1270
        """Generate the limbo name of a file"""
1439
1396
        target is a bytestring.
1440
1397
        See also new_symlink.
1441
1398
        """
1442
 
        if self._create_symlinks:
 
1399
        if has_symlinks():
1443
1400
            os.symlink(target, self._limbo_name(trans_id))
 
1401
            unique_add(self._new_contents, trans_id, 'symlink')
1444
1402
        else:
1445
1403
            try:
1446
1404
                path = FinalPaths(self).get_path(trans_id)
1447
1405
            except KeyError:
1448
1406
                path = None
1449
 
            trace.warning(
1450
 
                'Unable to create symlink "%s" on this filesystem.' % (path,))
1451
 
        # We add symlink to _new_contents even if they are unsupported
1452
 
        # and not created. These entries are subsequently used to avoid
1453
 
        # conflicts on platforms that don't support symlink
1454
 
        unique_add(self._new_contents, trans_id, 'symlink')
 
1407
            raise UnableCreateSymlink(path=path)
1455
1408
 
1456
1409
    def cancel_creation(self, trans_id):
1457
1410
        """Cancel the creation of new file contents."""
1824
1777
                    child_pb.update(gettext('removing file'),
1825
1778
                                    num, total_entries)
1826
1779
                if trans_id == self._new_root:
1827
 
                    file_id = self._tree.path2id('')
 
1780
                    file_id = self._tree.get_root_id()
1828
1781
                else:
1829
1782
                    file_id = self.tree_file_id(trans_id)
1830
1783
                # File-id isn't really being deleted, just moved
2033
1986
        self._all_children_cache = {}
2034
1987
        self._path2trans_id_cache = {}
2035
1988
        self._final_name_cache = {}
2036
 
        self._iter_changes_cache = dict((c.file_id, c) for c in
 
1989
        self._iter_changes_cache = dict((c[0], c) for c in
2037
1990
                                        self._transform.iter_changes())
2038
1991
 
2039
 
    def supports_tree_reference(self):
2040
 
        # TODO(jelmer): Support tree references in _PreviewTree.
2041
 
        # return self._transform._tree.supports_tree_reference()
2042
 
        return False
2043
 
 
2044
1992
    def _content_change(self, file_id):
2045
1993
        """Return True if the content of this file changed"""
2046
1994
        changes = self._iter_changes_cache.get(file_id)
2047
 
        return (changes is not None and changes.changed_content)
 
1995
        # changes[2] is true if the file content changed.  See
 
1996
        # InterTree.iter_changes.
 
1997
        return (changes is not None and changes[2])
2048
1998
 
2049
1999
    def _get_repository(self):
2050
2000
        repo = getattr(self._transform._tree, '_repository', None)
2106
2056
        """This Tree does not use inventory as its backing data."""
2107
2057
        raise NotImplementedError(_PreviewTree.root_inventory)
2108
2058
 
 
2059
    def get_root_id(self):
 
2060
        return self._transform.final_file_id(self._transform.root)
 
2061
 
2109
2062
    def all_file_ids(self):
2110
2063
        tree_ids = set(self._transform._tree.all_file_ids())
2111
2064
        tree_ids.difference_update(self._transform.tree_file_id(t)
2126
2079
 
2127
2080
        return tree_paths
2128
2081
 
 
2082
    def _has_id(self, file_id, fallback_check):
 
2083
        if file_id in self._transform._r_new_id:
 
2084
            return True
 
2085
        elif file_id in {self._transform.tree_file_id(trans_id) for
 
2086
                         trans_id in self._transform._removed_id}:
 
2087
            return False
 
2088
        else:
 
2089
            return fallback_check(file_id)
 
2090
 
 
2091
    def has_id(self, file_id):
 
2092
        return self._has_id(file_id, self._transform._tree.has_id)
 
2093
 
 
2094
    def has_or_had_id(self, file_id):
 
2095
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
 
2096
 
2129
2097
    def _path2trans_id(self, path):
2130
2098
        # We must not use None here, because that is a valid value to store.
2131
2099
        trans_id = self._path2trans_id_cache.get(path, object)
2155
2123
            path = osutils.pathjoin(*path)
2156
2124
        return self._transform.final_file_id(self._path2trans_id(path))
2157
2125
 
2158
 
    def id2path(self, file_id, recurse='down'):
 
2126
    def id2path(self, file_id):
2159
2127
        trans_id = self._transform.trans_id_file_id(file_id)
2160
2128
        try:
2161
2129
            return self._final_paths._determine_path(trans_id)
2173
2141
        self._all_children_cache[trans_id] = children
2174
2142
        return children
2175
2143
 
 
2144
    def _iter_children(self, file_id):
 
2145
        trans_id = self._transform.trans_id_file_id(file_id)
 
2146
        for child_trans_id in self._all_children(trans_id):
 
2147
            yield self._transform.final_file_id(child_trans_id)
 
2148
 
2176
2149
    def extras(self):
2177
2150
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
2178
2151
                              in self._transform._tree.extras())
2223
2196
        for entry, trans_id in self._make_inv_entries(todo):
2224
2197
            yield entry
2225
2198
 
2226
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2227
 
        if recurse_nested:
2228
 
            raise NotImplementedError(
2229
 
                'follow tree references not yet supported')
2230
 
 
 
2199
    def iter_entries_by_dir(self, specific_files=None):
2231
2200
        # This may not be a maximally efficient implementation, but it is
2232
2201
        # reasonably straightforward.  An implementation that grafts the
2233
2202
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2251
2220
        path_entries.sort()
2252
2221
        return path_entries
2253
2222
 
2254
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
2255
 
                   recurse_nested=False):
 
2223
    def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2224
        """See WorkingTree.list_files."""
2257
 
        if recurse_nested:
2258
 
            raise NotImplementedError(
2259
 
                'follow tree references not yet supported')
2260
 
 
2261
2225
        # XXX This should behave like WorkingTree.list_files, but is really
2262
2226
        # more like RevisionTree.list_files.
2263
2227
        if from_dir == '.':
2278
2242
        else:
2279
2243
            if from_dir is None and include_root is True:
2280
2244
                root_entry = inventory.make_entry(
2281
 
                    'directory', '', ROOT_PARENT, self.path2id(''))
 
2245
                    'directory', '', ROOT_PARENT, self.get_root_id())
2282
2246
                yield '', 'V', 'directory', root_entry
2283
2247
            entries = self._iter_entries_for_dir(from_dir or '')
2284
2248
            for path, entry in entries:
2347
2311
            with self.get_file(path) as fileobj:
2348
2312
                return sha_file(fileobj)
2349
2313
 
2350
 
    def get_reference_revision(self, path):
2351
 
        trans_id = self._path2trans_id(path)
2352
 
        if trans_id is None:
2353
 
            raise errors.NoSuchFile(path)
2354
 
        reference_revision = self._transform._new_reference_revision.get(trans_id)
2355
 
        if reference_revision is None:
2356
 
            return self._transform._tree.get_reference_revision(path)
2357
 
        return reference_revision
2358
 
 
2359
2314
    def is_executable(self, path):
2360
2315
        trans_id = self._path2trans_id(path)
2361
2316
        if trans_id is None:
2453
2408
        if changes is None:
2454
2409
            get_old = True
2455
2410
        else:
2456
 
            changed_content, versioned, kind = (
2457
 
                changes.changed_content, changes.versioned, changes.kind)
 
2411
            changed_content, versioned, kind = (changes[2], changes[3],
 
2412
                                                changes[6])
2458
2413
            if kind[1] is None:
2459
2414
                return None
2460
2415
            get_old = (kind[0] == 'file' and versioned[0])
2567
2522
        return [(self.get_path(t), t) for t in trans_ids]
2568
2523
 
2569
2524
 
 
2525
def topology_sorted_ids(tree):
 
2526
    """Determine the topological order of the ids in a tree"""
 
2527
    file_ids = list(tree)
 
2528
    file_ids.sort(key=tree.id2path)
 
2529
    return file_ids
 
2530
 
 
2531
 
2570
2532
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2571
2533
               delta_from_tree=False):
2572
2534
    """Create working tree for a branch, using a TreeTransform.
2593
2555
    :param delta_from_tree: If true, build_tree may use the input Tree to
2594
2556
        generate the inventory delta.
2595
2557
    """
2596
 
    with cleanup.ExitStack() as exit_stack:
2597
 
        exit_stack.enter_context(wt.lock_tree_write())
2598
 
        exit_stack.enter_context(tree.lock_read())
 
2558
    with wt.lock_tree_write(), tree.lock_read():
2599
2559
        if accelerator_tree is not None:
2600
 
            exit_stack.enter_context(accelerator_tree.lock_read())
2601
 
        return _build_tree(tree, wt, accelerator_tree, hardlink,
2602
 
                           delta_from_tree)
 
2560
            accelerator_tree.lock_read()
 
2561
        try:
 
2562
            return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2563
                               delta_from_tree)
 
2564
        finally:
 
2565
            if accelerator_tree is not None:
 
2566
                accelerator_tree.unlock()
2603
2567
 
2604
2568
 
2605
2569
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2610
2574
    file_trans_id = {}
2611
2575
    top_pb = ui.ui_factory.nested_progress_bar()
2612
2576
    pp = ProgressPhase("Build phase", 2, top_pb)
2613
 
    if tree.path2id('') is not None:
 
2577
    if tree.get_root_id() is not None:
2614
2578
        # This is kind of a hack: we should be altering the root
2615
2579
        # as part of the regular tree shape diff logic.
2616
2580
        # The conditional test here is to avoid doing an
2618
2582
        # is set within the tree, nor setting the root and thus
2619
2583
        # marking the tree as dirty, because we use two different
2620
2584
        # idioms here: tree interfaces and inventory interfaces.
2621
 
        if wt.path2id('') != tree.path2id(''):
2622
 
            wt.set_root_id(tree.path2id(''))
 
2585
        if wt.get_root_id() != tree.get_root_id():
 
2586
            wt.set_root_id(tree.get_root_id())
2623
2587
            wt.flush()
2624
 
    tt = wt.get_transform()
 
2588
    tt = TreeTransform(wt)
2625
2589
    divert = set()
2626
2590
    try:
2627
2591
        pp.next_phase()
2628
 
        file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
 
2592
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2629
2593
        with ui.ui_factory.nested_progress_bar() as pb:
2630
2594
            deferred_contents = []
2631
2595
            num = 0
2665
2629
                            divert.add(file_id)
2666
2630
                    if (file_id not in divert
2667
2631
                        and _content_match(
2668
 
                            tree, entry, tree_path, kind, target_path)):
 
2632
                            tree, entry, tree_path, file_id, kind,
 
2633
                            target_path)):
2669
2634
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2670
2635
                        if kind == 'directory':
2671
2636
                            reparent = True
2723
2688
        new_desired_files = desired_files
2724
2689
    else:
2725
2690
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2726
 
        unchanged = [
2727
 
            change.path for change in iter
2728
 
            if not (change.changed_content or change.executable[0] != change.executable[1])]
 
2691
        unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
 
2692
                     in iter if not (c or e[0] != e[1])]
2729
2693
        if accelerator_tree.supports_content_filtering():
2730
2694
            unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2695
                         if not next(accelerator_tree.iter_search_rules([ap]))]
2774
2738
    return by_parent[old_parent]
2775
2739
 
2776
2740
 
2777
 
def _content_match(tree, entry, tree_path, kind, target_path):
 
2741
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2742
    if entry.kind != kind:
2779
2743
        return False
2780
2744
    if entry.kind == "directory":
2842
2806
        raise errors.BadFileKindError(name, kind)
2843
2807
 
2844
2808
 
2845
 
def create_from_tree(tt, trans_id, tree, path, chunks=None,
 
2809
def create_from_tree(tt, trans_id, tree, path, file_id=None, chunks=None,
2846
2810
                     filter_tree_path=None):
2847
2811
    """Create new file contents according to tree contents.
2848
2812
 
2887
2851
    """Revert a working tree's contents to those of a target tree."""
2888
2852
    pb = ui.ui_factory.nested_progress_bar()
2889
2853
    try:
2890
 
        with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
 
2854
        with target_tree.lock_read(), TreeTransform(working_tree, pb) as tt:
2891
2855
            pp = ProgressPhase("Revert phase", 3, pb)
2892
2856
            conflicts, merge_modified = _prepare_revert_transform(
2893
2857
                working_tree, target_tree, tt, filenames, backups, pp)
2938
2902
        skip_root = False
2939
2903
    try:
2940
2904
        deferred_files = []
2941
 
        for id_num, change in enumerate(change_list):
2942
 
            file_id = change.file_id
2943
 
            target_path, wt_path = change.path
2944
 
            target_versioned, wt_versioned = change.versioned
2945
 
            target_parent, wt_parent = change.parent_id
2946
 
            target_name, wt_name = change.name
2947
 
            target_kind, wt_kind = change.kind
2948
 
            target_executable, wt_executable = change.executable
 
2905
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
2906
                     kind, executable) in enumerate(change_list):
 
2907
            target_path, wt_path = path
 
2908
            target_versioned, wt_versioned = versioned
 
2909
            target_parent, wt_parent = parent
 
2910
            target_name, wt_name = name
 
2911
            target_kind, wt_kind = kind
 
2912
            target_executable, wt_executable = executable
2949
2913
            if skip_root and wt_parent is None:
2950
2914
                continue
2951
2915
            trans_id = tt.trans_id_file_id(file_id)
2952
2916
            mode_id = None
2953
 
            if change.changed_content:
 
2917
            if changed_content:
2954
2918
                keep_content = False
2955
2919
                if wt_kind == 'file' and (backups or target_kind is None):
2956
2920
                    wt_sha1 = working_tree.get_file_sha1(wt_path)
2957
 
                    if merge_modified.get(wt_path) != wt_sha1:
 
2921
                    if merge_modified.get(file_id) != wt_sha1:
2958
2922
                        # acquire the basis tree lazily to prevent the
2959
2923
                        # expense of accessing it when it's not needed ?
2960
2924
                        # (Guessing, RBC, 200702)
2961
2925
                        if basis_tree is None:
2962
2926
                            basis_tree = working_tree.basis_tree()
2963
2927
                            basis_tree.lock_read()
2964
 
                        basis_inter = InterTree.get(basis_tree, working_tree)
2965
 
                        basis_path = basis_inter.find_source_path(wt_path)
 
2928
                        basis_path = find_previous_path(
 
2929
                            working_tree, basis_tree, wt_path)
2966
2930
                        if basis_path is None:
2967
2931
                            if target_kind is None and not target_versioned:
2968
2932
                                keep_content = True
3001
2965
                        basis_tree = working_tree.basis_tree()
3002
2966
                        basis_tree.lock_read()
3003
2967
                    new_sha1 = target_tree.get_file_sha1(target_path)
3004
 
                    basis_inter = InterTree.get(basis_tree, target_tree)
3005
 
                    basis_path = basis_inter.find_source_path(target_path)
 
2968
                    basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2969
                    if (basis_path is not None and
3007
2970
                            new_sha1 == basis_tree.get_file_sha1(basis_path)):
3008
 
                        # If the new contents of the file match what is in basis,
3009
 
                        # then there is no need to store in merge_modified.
3010
 
                        if basis_path in merge_modified:
3011
 
                            del merge_modified[basis_path]
 
2971
                        if file_id in merge_modified:
 
2972
                            del merge_modified[file_id]
3012
2973
                    else:
3013
 
                        merge_modified[target_path] = new_sha1
 
2974
                        merge_modified[file_id] = new_sha1
3014
2975
 
3015
2976
                    # preserve the execute bit when backing up
3016
2977
                    if keep_content and wt_executable == target_executable:
3263
3224
    :param target_tree: Tree to change
3264
3225
    :param source_tree: Tree to hard-link from
3265
3226
    """
3266
 
    with target_tree.get_transform() as tt:
3267
 
        for change in target_tree.iter_changes(source_tree, include_unchanged=True):
3268
 
            if change.changed_content:
3269
 
                continue
3270
 
            if change.kind != ('file', 'file'):
3271
 
                continue
3272
 
            if change.executable[0] != change.executable[1]:
3273
 
                continue
3274
 
            trans_id = tt.trans_id_tree_path(change.path[1])
 
3227
    tt = TreeTransform(target_tree)
 
3228
    try:
 
3229
        for (file_id, paths, changed_content, versioned, parent, name, kind,
 
3230
             executable) in target_tree.iter_changes(source_tree,
 
3231
                                                     include_unchanged=True):
 
3232
            if changed_content:
 
3233
                continue
 
3234
            if kind != ('file', 'file'):
 
3235
                continue
 
3236
            if executable[0] != executable[1]:
 
3237
                continue
 
3238
            trans_id = tt.trans_id_tree_path(paths[1])
3275
3239
            tt.delete_contents(trans_id)
3276
 
            tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
 
3240
            tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)
3277
3241
        tt.apply()
 
3242
    finally:
 
3243
        tt.finalize()